文章目錄
- String類型介紹
- redis采用的字符集
- json類型介紹
- String類型的命令
- set key value + [EX seconds] + [NX|XX]
- incr key
- incr對操作的key對應的value類型有限制嗎?
- `incr key`操作的返回值是什么?
- incr操作的key可以不存在嗎?
- 多個客戶端同時針對同一個key進行incr操作時,存不存在“線程安全”問題?
- incrby key n
- incrby操作的key如果不存在,結果會怎樣?
- incrby key n中n可以是負數嗎?
- incrby操作的key對應的value類型可以不是整數嗎?
- decr key
- decryby key n
- incrbyfloat key float
- append key + Str
- 如何拼接兩個string類型的字符串?
- append key + Str 中,key可以不存在嗎?
- `append key3 你好` 其中key3在語句執行時不存在,請問redis執行完這條命令之后返回的結果是什么?
- getrange key [start] [end]
- 如何從一個字符串中切分出一個子串呢?
- Redis中start和end指定的區間,是左開右閉區間嗎?
- start和end可以傳負數嗎?
- getrange key start end中,key對應是value可以是string類型的漢字嗎?
- SETRANGE key [offset] [value]
- 我想把一個字符串從下標6開始往后,替換成我給的字符串str,我該怎么做?
- offset指定的string下標從0開始還是從1開始?
- setrange操作的key對應的value和替換用的value可以是中文字符串嗎?
- setrange操作的key可以不存在嗎?
- strlen key
- 如何獲得key對應的字符串的長度?
- strlen操作的key可以不存在嗎?
- strlen操作的key對應的value可以不是string類型嗎?
- 如何獲得一個string類型的value的內部編碼?
- string 內部哪些編碼方式?分別都是在什么情況下使用?
- 請問如果String類型的value中存的是“1.5”,那它內部的存儲方式是什么呢?
- 假如說現在有一個場景,有非常多的 key,類型都是 string。但是每個 value 的 string 長度都是 100 左右,請問string類型內部編碼方式應該選哪種?
- Redis中string類型的應用場景
- 1.Redis最典型的應用場景:充當mysql的Cache
- 偽代碼實現
- 什么是熱點數據?
- 上述策略有一個明顯的問題,隨著時間的推移, redis 中的數據肯定是越來越多,那Redis的容量其實是很有限的呀,容不下這么多數據怎么辦?
- Redis中為什么要給key加很多前綴?
- 2.計數功能
- 什么叫做,異步的方式將播放量同步到其他數據源?
- 3.共享session會話
- 為什么要共享session會話?
- 4.手機驗證碼
String類型介紹
redis中的string類型最大的特點就是,string中的字符串不僅僅可以用來存儲文本數據、整數和普通的文本字符串,還可以用來存JSON、xml、二進制數據(圖片,視頻,音頻…),因為對于任何類型的數據,redis都會把數據按UTF-8編碼成一串二進制存儲起來
為了防止音頻視頻 體積過大,Redis 限制string類型的value大小最大是 512M。
為啥要做這個限制呢,主要還是因為Redis 是單線程模型,不希望進行的操作太復雜,希望操作能比較快速~~
redis采用的字符集
講 mysql 的時候,我們知道 mysql 默認的字符集,是拉丁文。插入中文,就會失敗,而redis就不會,因為redis的字符集只有UTF-8一種
json類型介紹
JSON(JavaScript Object Notation)即 JavaScript 對象表示法 ,是一種輕量級的數據交換格式。它能夠以人類可讀的文本形式表示數據,結構清晰。比如 {“name”: “John”, “age”: 30} ,很容易理解其中表示的是一個人的姓名和年齡信息。
json字符串不僅可以表示簡單的鍵值對,也能嵌套表示復雜的對象和數組結構 。例如 {“students”: [{“name”: “Alice”, “score”: 90}, {“name”: “Bob”, “score”: 85}]} ,能表示多個學生的成績信息。
幾乎所有現代編程語言都有處理 JSON 的庫或內置支持,方便不同語言編寫的系統之間進行數據交互。
String類型的命令
set key value + [EX seconds] + [NX|XX]
Redis 的 SET
命令結合 EX
、NX
、XX
等選項可以實現更靈活的鍵值設置功能,具體格式和含義如下:
基本語法:
SET key value [EX seconds] [PX milliseconds] [NX|XX]
各選項含義:
EX seconds
:設置鍵的過期時間,單位為秒(例如EX 3600
表示 1 小時后過期)。NX
:全稱 “Not Exists”,僅當鍵不存在時才設置值(常用于分布式鎖場景,避免覆蓋已有值)。XX
:全稱 “Exists”,僅當鍵已存在時才設置值(常用于更新已有鍵)。
示例:
-
設置鍵
user:1001
的值為zhangsan
,并設置 1 小時過期:SET user:1001 zhangsan EX 3600
-
僅當
user:1001
不存在時,才設置值并過期 1 小時:SET user:1001 zhangsan EX 3600 NX
(若鍵已存在,命令返回
nil
,不做任何操作) -
僅當
user:1001
已存在時,才更新值并保持 1 小時過期:SET user:1001 lisi EX 3600 XX
(若鍵不存在,命令返回
nil
,不做任何操作)
注意:
NX
和XX
是互斥的,不能同時使用。- 若同時指定
EX
和PX
,以最后出現的選項為準。 - 當
set key value + ex + EX seconds
后面沒有加NX或XX
時- 如果 key 不存在,則會創建新的鍵值對。
- 如果 key 存在,則是讓新的 value 覆蓋舊的 value。可能會改變原來的數據類型。同時原來這個 key 的 ttl(生存時間)也會失效,即新的ttl也會覆蓋舊的ttl,即使新語句中沒有設置ttl,舊的ttl依然會失效
set key value ex 10
相當于set key value; expire key 10
,唯一區別在于前者是原子性操作,相當于把兩步合為一步SETNX + key value
相當于set key value NX
SETXX + key value
相當于set key value XX
SETEX + key value
相當于set key value EX + seconds
SETPX/PSETEX + key value
相當于set key value PX + pseconds
incr key
incr key
效果是讓key對應的value=value + 1
時間復雜度:O(1)
incr對操作的key對應的value類型有限制嗎?
此時key對應的value必須得是整數~(64位/8字節表示的整數. 相當于C++中的long long 或者Java中long 表示的范圍是非常非常大的)
incr key
操作的返回值是什么?
就是 + 1 之后的value
incr操作的key可以不存在嗎?
incr操作的key如果不存在,就會把這個key的value當做0來使用.
多個客戶端同時針對同一個key進行incr操作時,存不存在“線程安全”問題?
由于redis處理命令的時候,是單線程模型. 多個客戶端同時針對同一個key進行incr操作,不會引起“線程安全”問題.
incrby key n
效果是讓key對應的value=value + n
時間復雜度:O(1)
incrby操作的key如果不存在,結果會怎樣?
先創造一個<key ,0>的鍵值對,value = 0 + n = n
incrby key n中n可以是負數嗎?
可以
incrby操作的key對應的value類型可以不是整數嗎?
不行
decr key
效果是value=value - 1
時間復雜度:O(1)
特殊情況處理和incr 保持一致,故不再多說
decryby key n
效果是value=value - n
特殊情況處理和incr 保持一致,故不再多說
時間復雜度:O(1)
incrbyfloat key float
效果就是value=value+float
時間復雜度:O(1)
注意:
- 此時key對應的value必須得是浮點數~
- float值可以為負,可以通過加上負數的形式來實現減法~~
append key + Str
如何拼接兩個string類型的字符串?
格式為 append key + Str
,含義是將key對應的String類型的value
和后面的String類型的Str
,兩個字符串拼成一個字符串,str追加到value的后面
append key + Str 中,key可以不存在嗎?
可以的,執行完畢之后,key對應的value就是string類型的str
append key3 你好
其中key3在語句執行時不存在,請問redis執行完這條命令之后返回的結果是什么?
返回的結果是6,append key + Str
的返回值是追加后字符串的總長度(單位是字節)。在本題中追加后的字符串就是你好
,一個漢字用UTF-8去編碼,占3個字節,兩個漢字就是6字節
注意:
- 當前咱們的xshell終端,默認的字符編碼是utf8。在終端中輸入漢字之后,也就是按照utf8編碼的,而一個漢字在utf8字符集中,通常是3個字節~
- 在啟動redis客戶端的時候,加上一個-raw這樣的選項.就可以使redis客戶端能夠自動的把二進制數據嘗試翻譯.
- 操作linux的時候,千萬注意,不要亂按ctrl + s,ctrl + s在xshell中的作用是“凍結當前畫面”,ctrl + q解除凍結~~
getrange key [start] [end]
如何從一個字符串中切分出一個子串呢?
使用getrange key start end
其中key對應的value就是我們要切分的目標字符串
start就是我們切分的起點下標,end就是我們切分的終點下標,執行完getrange key start end之后,返回的結果就是將原字符串從start到end這中間的一段切分下來形成的子字符串(包括start和end)
Redis中start和end指定的區間,是左開右閉區間嗎?
不是,左右都是閉區間!redis 中指定的區間,都是閉區間!!!
C++ 和 Java 中,談到一個區間,大多都是前閉后開(左閉右開)
編程這個大圈子中,區間大多是前閉后開,但是確實有特殊情況
start和end可以傳負數嗎?
在 Redis 的 GETRANGE 命令中,start 和 end 參數可以傳入負數,表示從字符串的末尾開始計數,其中:
- -1 代表字符串的最后一個字符
- -2 代表倒數第二個字符,以此類推
getrange key start end中,key對應是value可以是string類型的漢字嗎?
可以是可以,但是切起來會比較奇怪
如果字符串中保存的是漢字,此時進行子串切分,很可能切出來的就不是完整的漢字了
上述的代碼,是強行切出了中間的四個字節.隨便這么一切,切出的結果在utf8碼表上不知道能查出啥了
上述問題,在C++中的substr中同樣存在,Java中就沒事,為什么呢?
- Java中字符串的基本單位,是字符(Java的字符,占2個字節的字符). Java中相當于String幫我們把漢字的編碼轉換都處理好了~~
- C++中字符串的基本單位是字節,C++這里頭對于漢字的處理,是沒那么完善的,就需要程序猿手動處理了
SETRANGE key [offset] [value]
我想把一個字符串從下標6開始往后,替換成我給的字符串str,我該怎么做?
SETRANGE key 6 str
當offset=6時,就會將key對應的字符串從下標6開始往后,替換成我給的str字符串
offset指定的string下標從0開始還是從1開始?
從0開始
setrange操作的key對應的value和替換用的value可以是中文字符串嗎?
當然可以,但是如果當前value是一個中文字符串,進行setrange的時候,是可能會搞出問題的!!
我們都知道一個漢字要用三個字節來存,如果你從下標為1的位置開始,將下標123替換為一個漢字,那其實后面解釋的時候,是會把012三個下標對應的三個字節解釋成一個漢字,這樣就肯定不是我們原來想換的漢字了,極有可能出現亂碼
setrange操作的key可以不存在嗎?
可以,setrange 針對不存在的 key 也是可以操作的。不過會把 offset 之前的內容填充成 0x00(注意這是一個字節,兩位16進制數據就是一個字節 ),下圖中\x00就是代表0x00
strlen key
如何獲得key對應的字符串的長度?
strlen key
注意:strlen的返回長度單位是字節,而MySQL中varchar(N)此處N的單位是字符. mysql中的一個字符,就可以是一個完整的漢字,所以mysql中的一個字符可能是多個字節
C++中,字符串的長度本身就是用字節為單位.Java中,字符串的長度則是以字符為單位的. Java中的一個char == 2字節,Java中的char基于unicode這樣的編碼方式 就能夠表示中文等符號~~
剛才說了半天,一個漢字通常是 3 個字節呀~~(編碼方式是 utf8)Java 里頭咋一個 2 字節的 char 就能表示漢字呢??
Java 中的 char 用的是 unicode編碼. 一個漢字使用兩個字節
Java 中的 String,則是用的 utf8. 一個漢字就是 3 個字節了.
Java 的標準庫內部,在進行上述的操作過程中,程序猿一般是感知不到編碼方式的變換的~~
strlen操作的key可以不存在嗎?
可以,如果是這樣,那么結果返回0
strlen操作的key對應的value可以不是string類型嗎?
不可以,也就是說如果value不是string類型,那么strlen key執行就會報錯
如何獲得一個string類型的value的內部編碼?
object encoding + key
string 內部哪些編碼方式?分別都是在什么情況下使用?
- int 64位/8字節的整數,若字符串中存儲的是一串數字,比如“1234545”,像這樣的字符串我們通常就用int編碼方式
- embstr 壓縮字符串. 適用于表示比較短的字符串.,當字符串的長度小于39時,通常用embstr編碼方式來存儲
- raw 普通字符串. 適用于表示更長的字符串,只是單純的持有字節數組,當字符串的長度大于39時,我們通常用raw編碼方式來存儲
請問如果String類型的value中存的是“1.5”,那它內部的存儲方式是什么呢?
實驗告訴我們是embstr,即redis 存儲小數,本質上還是當做字符串來存儲(string類型中embstr編碼方式)。這就和整數相比差別很大了。整數直接使用 int 來存(準確來說是一個 long long (C++) / long (Java)),比較方便進行算術運算。小數則是使用字符串來存。意味著每次進行算術運算,都需要把字符串轉成小數,進行運算,結果再轉回字符串保存……
假如說現在有一個場景,有非常多的 key,類型都是 string。但是每個 value 的 string 長度都是 100 左右,請問string類型內部編碼方式應該選哪種?
本來按道理來說,長度大于39,我們應該選擇raw,但是raw所占的空間更大,如果我們更關注整體的內存空間,這樣的字符串使用 embstr 來存儲也不是不能考慮。
那人家規定embstr最多只能存39字節,再多了存不下,你說怎么辦呢?
很簡單,改配置就行了,先看 redis 是否提供了對應的配置項,可以修改 39 這個數字,如果沒有提供配置型,就需要針對 redis 源碼進行魔改~
為啥很多大廠,往往是自己造輪子,而不是直接使用業界成熟的呢?
開源的組件,往往考慮的是通用性,但是大廠往往會遇到一些極端的業務場景。 往往就需要根據當前的極端業務,針對上述的開源組件進行定制化
網上也經常有種說法,大廠造輪子,一般是為了 kpi
Redis中string類型的應用場景
1.Redis最典型的應用場景:充當mysql的Cache
Redis+MYSQL架構下,數據的訪問策略是什么?應用服務器訪問數據的時候,會先查詢Redis,如果命中了,會怎么處理,如果沒有命中,又會怎么處理?
整體的思路:應用服務器訪問數據的時候, 先查詢 Redis.如果 Redis 上數據存在了, 就直接從 Redis 取數據交給應用服務器.不繼續訪問數據庫了.
如果 Redis 上數據不存在, 再讀取 MySQL. 把讀到的結果, 返回給應用服務器同時, 把這個數據也寫入到 Redis 中.
偽代碼實現
下面的偽代碼模擬了圖2-10的業務數據訪問過程:
1)假設業務是根據用戶uid獲取用戶信息
UserInfo getUserInfo(long uid) {...
}
2)首先從Redis獲取用戶信息,我們假設用戶信息保存在“user:info:”對應的鍵中:
// 根據uid得到Redis的鍵
String key = "user:info:" + uid;// 嘗試從Redis中獲取對應的值
String value = Redis 執行命令: get key;
// 如果緩存命中(hit)
if (value != null) {// 假設我們的用戶信息按照JSON格式存儲UserInfo userInfo = JSON 反序列化(value);return userInfo;
}
// 如果緩存未命中(miss)
if (value == null) {// 從數據庫中,根據uid獲取用戶信息UserInfo userInfo = MySQL 執行 SQL: select * from user_info where uid = <uid>// 如果表中沒有uid對應的用戶信息if (userInfo == null) {響應404return null;}// 將用戶信息序列化成JSON格式String value = JSON 序列化(userInfo);// 寫入緩存,為了防止數據腐爛(rot),設置過期時間為1小時(3600秒)Redis 執行命令: set key value ex 3600// 返回用戶信息return userInfo;
}
什么是熱點數據?
Redis 這樣的緩存,經常用來存儲 “熱點” 數據。所謂的熱點數據,也就是高頻被使用的數據,是如何定義的呢?(什么樣的數據叫做被高頻使用的數據?)
這個定義方式,結合業務場景有很多種方式的。剛才上述描述的過程,相當于是把最近使用到的數據作為熱點數據。
暗含了一層假設: 某個數據一旦被用到了,那么很可能在最近這段時間就會被反復用到,也就是時空局部性
上述策略有一個明顯的問題,隨著時間的推移, redis 中的數據肯定是越來越多,那Redis的容量其實是很有限的呀,容不下這么多數據怎么辦?
- 在把數據寫給 redis 的同時,給這個 key 設置一個過期時間。到了過期時間,這個鍵值對就會被redis刪除了
- Redis 也在內存不足的時候,提供了淘汰策略,比如我們機組中學到的LRUCache置換算法
Redis中為什么要給key加很多前綴?
主要就是為了區分不同種類的數據
mysql中,記錄是存在表中的,而redis中沒有表這樣的結構,那我們就通過前綴對鍵值對進行歸類
與 MySQL 等關系型數據庫不同的是,Redis 沒有表、字段這種命名空間,而且也沒有對鍵名有強制要求(除了不能使用一些特殊字符 )。但設計合理的鍵名,有利于防止鍵沖突和項目的可維護性,比較推薦的方式是使用 “業務名:對象名:唯一標識:屬性” 作為鍵名。
例如,MySQL 的數據庫名為 vs,用戶表名為 user_info,那么對應的鍵可以使用 “vs:user_info:6379”、“vs:user_info:6379:name”
來表示,如果當前 Redis 只會被一個業務使用,可以省略業務名 “vs:”
。如果鍵名過長,則可以使用團隊內部都認同的縮寫替代,例如 “user:6379:friends:messages:5217”
可以被 “u:6379:fr:m:5217”
代替。畢竟鍵名過長,還是會導致 Redis 的性能明顯下降的。
2.計數功能
許多應用都會使用Redis作為計數的基礎工具,它可以實現快速計數、查詢緩存的功能,同時數據可以異步處理或者落地到其他數據源。如下圖所示,例如視頻網站的視頻播放次數可以使用Redis來完成:用戶每播放一次視頻,相應的視頻播放數就會自增1。
企業為啥老樂意收集用戶的數據??
說人話就是,統計你喜歡看啥,統計出來之后,就給你多推送啥
統計 => 進一步明確用戶的需求 => 根據需求改進和迭代產品
Redis 并不擅長數據統計,比如,想在上述的 Redis 中,統計播放量前 100 的視頻有哪些,基于 Redis 搞就很麻煩,相比之下,如果是 mysql 來存儲上述數據, SELECT * FROM videos ORDER BY play_count DESC LIMIT 100;
一個 sql 就搞定了~~
什么叫做,異步的方式將播放量同步到其他數據源?
異步寫入的意思是,Redis把數據更改的信息傳過來之后,不用看著mysql將信息同步,再繼續工作,而是我給MYSQL發個信號,通知他一聲,然后我就去干下一份工去了
實際中要開發一個成熟、穩定的真實計數系統,要面臨的挑戰遠不止如此簡單:防作弊(識別出惡意刷票的情況,并屏蔽這種情況)、按照不同維度計數(不只看播放量)、避免單點問題、數據持久化到底層數據源等。
3.共享session會話
為什么要共享session會話?
就比如你去醫院看病。這個病一次瞧不好。需要多次治療,但是你每次去的時候不一定能夠確保都是同一個醫生給你看病。那如果你前兩次去的時候是甲醫生給你看,第三次去的時候是乙醫生給你看。那么乙醫生頭一次給你看病,不了解你的情況,工作就很難開展。那這個問題應該怎么解決呢?醫院的解決方式就是給你發個病歷,前兩次去看病的時候,甲大夫都會將這次看病的過程記錄在病歷中,第三次你再去看病的時候,一大夫就不用再重新問了。而是直接看病例就能快速的了解你的情況。那么醫院引入病例的操作就相當于redis中引入共享session會話
引入共享機制之前,對于某個用戶,每個應用服務器都會維護一份該用戶與當前服務器的會話數據,不同服務器之間的會話數據彼此之間是不同步的,比如我前兩次追番的記錄,都在1號服務器的session會話中,我第三次訪問到了2號服務器,那這個2號服務器中就沒有我的追番記錄。
引入共享session機制之后,所有的session信息統一由一個服務器來管理和存儲,此時所有的會話數據,就可以被所有服務器共享
4.手機驗證碼
這個過程很簡單,廠家首先將驗證碼發送到用戶的手機中,然后用戶把短信收到的驗證碼這一串數,再提交到系統中,系統進行驗證驗證碼是否正確
但是這個過程中也有一些要求,比如
- 限制驗證碼5分鐘內有效
- 限制用戶在 1分鐘之內,最多獲取 5 次驗證碼
這個主要還是怕用戶頻繁獲取驗證碼,對于我們的服務器壓力過大。或者 (每次獲取驗證碼必須間隔 30 秒~~ )
偽代碼
限制一分鐘內最多獲取五次驗證碼
驗證碼5分鐘內有效
將用戶輸入的驗證碼,與redis數據庫中的驗證碼進行比對,檢測輸入是否正確
String 發送驗證碼(phoneNumber) {key = "shortMsg:limit:" + phoneNumber;// 設置過期時間為 1 分鐘(60 秒)// 使用 NX,只在不存在 key 時才能設置成功bool r = Redis 執行命令: set key 1 ex 60 nxif (r == false) {// 說明之前設置過該手機的驗證碼了long c = Redis 執行命令: incr keyif (c > 5) {// 說明超過了一分鐘 5 次的限制了// 限制發送return null;}}// 走到這說明要么之前沒有設置過手機的驗證碼;要么次數沒有超過 5 次String validationCode = 生成隨機的 6 位數的驗證碼();validationKey = "validation:" + phoneNumber;// 驗證碼 5 分鐘(300 秒)內有效Redis 執行命令: set validationKey validationCode ex 300;// 返回驗證碼,隨后通過手機短信發送給用戶return validationCode;
}// 驗證用戶輸入的驗證碼是否正確
bool 驗證驗證碼(phoneNumber, validationCode) {validationKey = "validation:" + phoneNumber;String value = Redis 執行命令: get validationKey;if (value == null) {// 說明沒有這個手機的驗證碼記錄,驗證失敗return false;}if (value == validationCode) {return true;} else {return false;}
}