目錄
一.常見命令?
1.1.SET
1.2.GET
1.3.MGET ?
1.4.MSET ?
1.5.SETNX
二.計數命令
2.1.INCR ?
2.2.INCRBY
2.3.DECR
2.4.DECYBY
2.5.INCRBYFLOAT
三 . 其他命令
3.1.APPEND
3.2.GETRANGE
3.3.SETRANGE
3.4.STRLEN ?
四. 字符串類型內部編碼
五. 典型使用場景
5.1.緩存(Cache)功能
5.2.計數(Counter)功能
5.3.共享會話(Session)
5.4.?機驗證碼
字符串(String)是?Redis 最基礎、最核心的數據類型,理解其特性至關重要:
-
基礎構建塊:
-
鍵的類型:?Redis 中所有的鍵 (Key) 本質上都是字符串類型。
-
值的基石:?其他復雜數據結構(列表 List、集合 Set、有序集合 Sorted Set、哈希 Hash)的元素值 (Value)?也必須是字符串類型。這使得字符串成為學習和理解 Redis 整個數據模型的基礎。
-
-
靈活的值類型 (二進制安全):
-
Redis 字符串類型存儲的值 (Value)?具有極高的靈活性,可以是:
-
文本數據:?普通字符串、JSON、XML、CSV 等格式的文本。
-
數值數據:?整數或浮點數(Redis 提供專門的命令如?
INCR
,?DECR
,?INCRBYFLOAT
?來操作數值)。 -
二進制數據:?如圖片、音頻、視頻文件或任何序列化的字節流。
-
-
關鍵特性:二進制安全 (Binary Safe)
-
Redis?內部存儲和操作字符串值完全基于原始字節序列(二進制流)。
-
這意味著?Redis 本身不感知、也不處理任何字符集編碼(如 UTF-8, GBK)問題。
-
客戶端存入什么編碼的字節流,Redis 就原樣存儲;客戶端讀取時,也將得到完全相同的字節流。字符集的解析完全由客戶端負責。
-
-
容量限制:?單個字符串值的最大容量為?512MB。
-
核心價值與應用場景:
-
緩存 (Cache):?存儲 HTML 片段、用戶會話信息、序列化對象等。
-
計數器 (Counter):?利用?
INCR
/DECR
?命令實現點擊計數、庫存計數等。 -
分布式鎖 (Distributed Lock):?配合?
SET
?命令的?NX
/PX
?等選項實現簡單鎖機制。 -
配置存儲:?存儲應用配置項。
-
二進制數據存儲:?存儲小型圖片、驗證碼、序列化數據等(需注意 512MB 限制)。
總結:?Redis 字符串類型不僅是鍵的載體,更是所有復雜數據結構元素的基石。其?二進制安全?的特性賦予了它存儲任意數據的強大能力,而?512MB 的上限?和?豐富的操作命令?使其成為 Redis 最常用、最靈活的數據類型之一。
一.常見命令?
1.1.SET
功能:?將字符串類型的值設置到指定的鍵中。
-
覆蓋規則:?如果鍵已存在,無論其原先存儲的數據類型是什么(字符串、列表、哈希等),都會被覆蓋為新的字符串值。
-
TTL 處理:?原先與該鍵關聯的任何生存時間(TTL)都會被清除失效。
語法:
SET key value [expiration EX seconds|PX milliseconds] [NX|XX]
版本要求:?1.0.0 起可用
時間復雜度:?O(1)
選項:
SET
?命令提供多種選項來控制其行為:
-
EX seconds
: 設置鍵的過期時間,單位是秒。 -
PX milliseconds
: 設置鍵的過期時間,單位是毫秒。 -
NX
:?僅當鍵不存在 (Not eXists)?時才執行設置操作。如果鍵已存在,則設置操作不會執行。 -
XX
:?僅當鍵存在 (eXists)?時才執行設置操作。如果鍵不存在,則設置操作不會執行。
功能整合:?上面這些新選項使得單個?SET
?命令能夠完全覆蓋?SETNX
、SETEX
、PSETEX
?的功能:
-
SET key value NX
?等價于?SETNX key value
-
SET key value EX seconds
?等價于?SETEX key seconds value
-
SET key value PX milliseconds
?等價于?PSETEX key milliseconds value
重要說明:
-
互斥性:?
NX
?和?XX
?選項是互斥的,同一命令中只能指定其中一個。 -
命令演變:?由于?
SET
?命令結合選項 (NX
,?XX
,?EX
,?PX
) 可以完全替代?SETNX
、SETEX
、PSETEX
?等獨立命令的功能,在 Redis 的后續版本中,這些獨立命令的實現已被整合到?SET
?命令中。雖然這些獨立命令目前通常仍保留可用(以兼容舊腳本或習慣),但推薦優先使用帶選項的?SET
?命令,因為它功能更統一、強大。
返回值:
-
如果設置操作成功執行,返回字符串?
"OK"
。 -
如果因為指定了?
NX
?或?XX
?選項但條件不滿足(即?NX
?時鍵已存在,或?XX
?時鍵不存在),導致設置操作未執行,則返回?(nil)
。
話不多說,我們看看例子
NX選項
可以看到啊!!上面這個SET mykey "World" NX并沒有生效啊!!!這個是因為
NX
:?僅當鍵不存在 (Not eXists)?時才執行設置操作。如果鍵已存在,則設置操作不會執行。
?我們看看它生效的樣子
XX選項
?
我們看到:上面這個SET mykey "World" XX并沒有生效啊!!!這個是因為
XX
:?僅當鍵存在 (eXists)?時才執行設置操作。如果鍵不存在,則設置操作不會執行。
我們來看看它執行的樣子
EX選項
至于這個PX選項,由于時間太短了,我們就不演示了。
1.2.GET
獲取 key 對應的 value。
如果 key 不存在,返回 nil。
如果 value 的數據類型不是 string,會報錯。
語法:
GET key
- 命令有效版本:1.0.0 之后
- 時間復雜度:O(1)
- 返回值:key 對應的 value,或者 nil 當 key 不存在。
1.3.MGET ?
一次性獲取多個 key 的值。
如果對應的 key 不存在或者對應的數據類型不是 string,返回 nil。
語法:
MGET key [key ...]
- 命令有效版本:1.0.0 之后 ?
- 時間復雜度:O(N) ,N 是我們這個命令里要獲取的?key 數量? ,不是redis里面所有key的數量
- 返回值:對應 value 的列表
我們來對比一下這個get命令和mget命令啊!!!
很明顯啊,聰明人都會選項mget來獲取value。
我們看看怎么使用
1.4.MSET ?
一次性設置多個 key 的值。 ?
覆蓋規則:
-
如果指定的?
key
?已存在,無論其原先存儲的數據類型是什么(字符串、列表、哈希、集合、有序集合等),MSET
?都會將其覆蓋為新的字符串值。 -
這是由?
MSET
?的設計目標決定的:它是一個原子性的批量設置字符串值的命令。
TTL 處理:
-
對于?
MSET
?命令中指定的、已經存在的?key
,原先與該鍵關聯的任何生存時間(TTL)都會被清除失效。執行?MSET
?后,這些鍵將變成永久的(沒有過期時間),除非后續顯式地為它們設置新的 TTL(例如使用?EXPIRE
?或?SET
?的?EX
/PX
?選項)。 -
對于?
MSET
?命令中指定的、之前不存在的?key
,設置后會成為永久鍵(沒有 TTL),除非后續顯式設置。
語法: ?
MSET key value [key value ...] ?
- 命令有效版本:1.0.1 之后 ?
- 時間復雜度:O(N)
- N 是 key 數量 ?
- 返回值:永遠是 OK ?
多說無益,我們直接上手看例子
這就很好的說明了:如果指定的?key
?已存在,無論其原先存儲的數據類型是什么(字符串、列表、哈希、集合、有序集合等),MSET
?都會將其覆蓋為新的字符串值。
接下來我們接著看
對于?MSET
?命令中指定的、之前不存在的?key
,設置后會成為永久鍵(沒有 TTL),除非后續顯式設置。
1.5.SETNX
設置key-value但只允許在key之前不存在的情況下。
語法:
SETNX key value
命令有效版本:1.0.0之后
時間復雜度:O(1)
返回值:1表?設置成功。0表?沒有設置。
話不多說,我們直接看例子
這個就是沒有設置成功的情況,我們接下來看看設置成功的情況
SET、SETNX、SETXX執?流程
二.計數命令
2.1.INCR ?
功能:
將鍵?key
?中存儲的整數值增加 1。
關鍵行為:
-
如果鍵?
key
?不存在,則在執行遞增操作前,先將該鍵的值設置為 0。 -
如果鍵?
key
?存儲的值不是整數類型(無法解析為整數),則命令返回錯誤。 -
如果鍵?
key
?存儲的值是整數,但遞增后超出 64 位有符號整數的表示范圍(即 C/C++ 中的?long long
?類型,范圍從?-9223372036854775808
?到?9223372036854775807
),則命令返回錯誤。
原子性:
此命令是原子操作。在分布式環境下,多個客戶端同時對同一個鍵執行?INCR
?時,Redis 的單線程執行模型確保該命令不會出現競態條件,每個操作都會順序執行,因此非常適合實現高并發場景下的計數器。
與浮點數的區別:
INCR
?命令僅適用于整數值。若需操作浮點數,請使用?INCRBYFLOAT
?命令。
語法:
INCR key
- 命令有效版本:1.0.0 之后 ?
- 時間復雜度:O(1) ?
- 返回值:integer 類型的加完后的數值。
錯誤情況:
-
當?
key
?存儲的不是字符串類型時,返回類型錯誤。 -
當?
key
?存儲的字符串不能表示為整數,或操作后導致整數溢出時,返回錯誤。
話不多說,我們直接看例子
將鍵?key
?中存儲的整數值增加 1。
如果鍵?key
?不存在,則在執行遞增操作前,先將該鍵的值設置為 0。
當?key
?存儲的字符串不能表示為整數,返回錯誤
當?key
?存儲的字符串操作后導致整數溢出時,返回錯誤
2.2.INCRBY
將 key 對應的 string 表示的數字加上對應的值。
如果 key 不存在,則視為 key 對應的 value 是 0。
如果 key 對應的 string 不是一個整型或者范圍超過了 64 位有符號整型,則報錯。
語法:
INCRBY key decrement
- 命令有效版本:1.0.0 之后
- 時間復雜度:O(1)
- 返回值:integer 類型的加完后的數值。
錯誤情況:
-
decrement不是整數時
-
當?
key
?存儲的不是字符串類型時 -
當?
key
?存儲的字符串不能表示為整數時 -
當操作結果超出 64 位有符號整數范圍時
話不多說,我們直接看例子
2.3.DECR
將 key 對應的 string 表示的數字減一。
如果 key 不存在,則視為 key 對應的 value 是 0。
如果 key 對應的 string 不是一個整型或者范圍超過了 64 位有符號整型,則報錯。
語法:
DECR key
- 命令有效版本:1.0.0 之后
- 時間復雜度:O(1)
- 返回值:integer 類型的減完后的數值。
話不多說,我們直接看例子
2.4.DECYBY
將 key 對應的 string 表示的數字減去對應的值。
如果 key 不存在,則視為 key 對應的 value 是 0。
如果 key 對應的 string 不是一個整型或者范圍超過了 64 位有符號整型,則報錯。
語法:
DECBBY key decrement
- 命令有效版本:1.0.0 之后
- 時間復雜度:O(1)
- 返回值:integer 類型的減完后的數值。
2.5.INCRBYFLOAT
功能:
對鍵?key
?存儲的浮點數值執行加減操作。
核心行為:
-
數值操作:
-
將鍵對應的字符串值解析為浮點數
-
加上指定的增量值
-
若增量為負數,則執行減法操作
-
-
鍵不存在時:
-
自動視為該鍵的值為?
0.0
-
-
科學計數法:
-
支持使用科學計數法表示浮點數(如?
1.23e4
)
-
-
錯誤條件:
-
鍵存在但非字符串類型?→ 報錯
-
鍵值無法解析為浮點數?→ 報錯
-
語法:
INCRBYFLOAT key increment
- 命令有效版本:2.6.0 之后
- 時間復雜度:O(1)
- 返回值:加/減完后的數值。
話不多說,我們直接看例子
很多存儲系統和編程語言內部使用 CAS 機制實現計數功能,會有一定的 CPU 開銷,但在 Redis 中完全不存在這個問題,因為 Redis 是單線程架構,任何命令到了 Redis 服務端都要順序執行。
Redis里面整數和小數的存儲方式
Redis 在存儲數值類型時,對整數和小數采取了不同的底層處理方式。
-
整數:?直接使用高效的整型數據結構(在 Redis 內部通常表示為?
long
?類型,對應 C 語言的?long long
?或 Java 的?long
,即 64 位有符號整數)。這使得整數的算術運算(如?INCR
,?DECR
,?INCRBY
)非常高效,因為它們直接在內存中的二進制數值上進行操作。 -
小數:?則是以序列化后的字符串形式存儲的(例如,
"3.14"
)。這意味著每次需要對小數進行任何算術運算時(無論是 Redis 內置命令如?INCRBYFLOAT
,還是客戶端應用邏輯),都必須:-
將存儲的字符串解析為編程語言中的浮點數類型(如?
double
)。 -
在浮點數上執行運算。
-
將運算結果重新序列化為字符串。
-
最后再將字符串寫回?Redis。
-
這種“字符串-數值-字符串”的轉換過程,相比于整數的直接二進制運算,會帶來顯著的額外開銷(CPU 用于解析/序列化,內存用于存儲字符串)。選擇這種設計主要是因為:
-
保持精度和可移植性:?字符串表示可以精確地記錄用戶輸入的小數值(尤其是涉及特定精度時),避免了二進制浮點數固有的精度損失問題(如 0.1 無法精確表示)。它也確保了不同客戶端或系統之間傳輸時值的一致性和可讀性。
-
簡化實現:?統一用字符串存儲簡化了 Redis 內部對值類型的處理邏輯。
因此,雖然 Redis 提供了?INCRBYFLOAT
?等命令支持小數運算,但其底層實現機制決定了其性能開銷遠大于整數的等效操作。在設計需要頻繁進行數值計算的數據結構時,應充分考慮這種差異。
三 . 其他命令
3.1.APPEND
如果 key 已經存在并且是一個 string,命令會將 value 追加到原有 string 的后邊。
如果 key 不存在,則效果等同于 SET 命令。
語法:
APPEND KEY VALUE
- 命令有效版本:2.0.0 之后
- 時間復雜度:O(1). 追加的字符串一般長度較短, 可以視為 O(1).
- 返回值:追加完成之后 string 的長度。返回的是字節數!!!
注意事項
append 返回值, 長度的單位是 字節!!
redis 的字符串, 不會對字符編碼做任何處理. (redis 不認識字符, 只認識字節)
當前咱們的 xshell 終端, 默認的字符編碼是 utf8.
在終端中輸入漢字之后, 也就是按照 utf8 編碼的~~
一個漢字在 utf8 字符集中, 通常是 3 個字節的~
3.2.GETRANGE
功能:
返回鍵?key
?對應字符串值的子串,子串范圍由?start
?和?end
?下標確定(包含兩端字符)。
索引規則:
-
正數索引:從左向右計數(0 表示第一個字符)
-
負數索引:從右向左計數
-
-1
:倒數第一個字符 -
-2
:倒數第二個字符 -
其他負數依此類推
-
-
范圍處理:
-
若?
start
?超出字符串左邊界,視為 0 -
若?
end
?超出字符串右邊界,視為最后一個字符 -
若?
start > end
,返回空字符串
-
語法:
GETRANGE key start end
- 命令有效版本:2.4.0 之后
- 時間復雜度:O(N). N 為 [start, end] 區間的長度. 由于 string 通常比較短, 可以視為是 O(1)
- 返回值:string 類型的子串、
閉區間包含
負數索引
越界處理
空鍵處理
完整實例
3.3.SETRANGE
功能:
從指定偏移量?offset
?開始,覆蓋鍵?key
?存儲的字符串值的部分內容。
注意這里的偏移量可是以字節為單位的。
核心行為:
-
覆蓋規則:
-
從?
offset
?位置開始,用?value
?覆蓋原字符串 -
若?
offset
?大于原字符串長度,自動用空字節(\x00
)填充中間空缺
-
-
鍵不存在時:
-
視為空字符串?
""
?處理 -
等同于在指定位置創建新字符串
-
-
長度變化:
-
操作后字符串長度 = max(原長度, offset + value長度)
-
可能擴展字符串長度
-
語法:
SETRANGE key offset value
- 命令有效版本:2.2.0 之后
- 時間復雜度:O(N),N 為 value 的長度。由于一般給的 value 比較短,通常視為 O(1)。
- 返回值:替換后的 string 的長度。
話不多說,直接看例子:
基礎覆蓋:從指定偏移量?offset
?開始,覆蓋鍵?key
?存儲的字符串值的部分內容。
超出長度處理:若?offset
?大于原字符串長度,自動用空字節(\x00
)填充中間空缺
空鍵操作:鍵不存在時,視為空字符串?""
?處理,偏移量前面的全部自動用空字節(\x00
)填充
注意這里的偏移量可是以字節為單位的。
如果我們當前的value是中文字符串,進行?SETRANGE操作的話可能會出現問題的。因為一個中文字符通常不止一個字節。
我們看看
3.4.STRLEN ?
獲取 key 對應的 string 的長度。也就是對應string的字節數。
當 key 存放的類似不是 string 時,報錯。
語法:
STRLEN key
- 命令有效版本:2.2.0 之后 ?
- 時間復雜度:O(1) ?
- 返回值:string 的長度。或者當 key 不存在時,返回 0。
注意事項:
1.字節長度
無論字符串使用何種編碼(如 ASCII、UTF-8、GBK),STRLEN
?始終返回字符串占用的?字節數。
例如:
- 字符串?
"hello"
(ASCII)→ 5 字節 →?返回 5 - 字符串?
"你好"
(UTF-8 編碼)→ 每個漢字占 3 字節 → 總 6 字節 →?返回 6
2.非字符串類型報錯
若 Key 存儲的不是字符串類型(如 List/Hash/Set),Redis 會返回錯誤:(error) WRONGTYPE Operation against a key holding the wrong kind of value
3.Key 不存在:若 Key 不存在,返回 0(視為空字符串)。
話不多說,我們直接看例子
字符串?"你好"
(注意我們現在是使用了UTF-8 編碼)→ 每個漢字占 3 字節 → 總 6 字節 →?返回 6
在編程和數據庫設計中,理解字符串長度和字符編碼至關重要,不同環境下的處理方式存在顯著差異:
長度單位:
C++:?
std::string
?的?length()
?或?size()
?方法返回的是字節數。Java:?
String
?的?length()
?方法返回的是?字符數。MySQL:?
VARCHAR(N)
?定義中的?N
?指定的是字符數。一個字符可以是一個英文字母、一個漢字或其他語言符號。
四. 字符串類型內部編碼
Redis 為了優化內存使用和性能,會根據字符串值的類型和長度動態選擇三種不同的內部編碼表示:
- int:8個字節的?整型。
- embstr:?于等于39個字節的字符串。
- raw:?于39個字節的字符串。
Redis 會根據當前值的類型和?度動態決定使?哪種內部編碼實現。
int
?(整型):
當字符串值可以表示為?8 字節長整型 (long)?時使用。
優勢:?內存占用小,操作效率高(支持數值計算)。
示例:
值 '6379' 被識別為整數,使用 int 編碼
embstr
?(嵌入式字符串):
用于存儲長度?小于或等于 44 字節?的字符串值?(注意:Redis 版本演進中,這個閾值從早期的 39 字節提高到了 5.0 及以后版本的 44 字節)。
特點:?RedisObject 對象頭和數據本身存儲在一塊連續的內存中。
優勢:?內存分配和釋放只需一次操作,緩存局部性好,訪問效率高。
示例:
?短字符串 "hello" 使用 embstr 編碼
raw
?(原始字符串):
用于存儲長度?大于 44 字節?的字符串值。
特點:?RedisObject 對象頭和數據本身存儲在兩塊獨立分配的內存中。
原因:?對于長字符串,分配連續的大塊內存代價更高且不靈活。
示例:
"raw"? # 長字符串使用 raw 編碼
忠告
?不建議大家去記, 長度大于 39 這樣的數字~~
給大家講個情景吧:
對于某個業務場景, 有很多很多的 key , 類型都是 string
但是每個 value 的 string 長度都是 100 左右~~ 更關注與整體的內存空間.
因此的話, 這樣的字符串使用 embstr 來存儲也不是 不能考慮~~
上述效果具體怎么實現?
- 先看 redis 是否提供了對應的配置項, 可以修改 39 這個數字~~
- 如果沒有提供配置型, 就需要針對 redis 源碼進行魔 改~
為啥很多大廠, 往往是自己造輪子, 而不是直接使用業 界成熟的呢?
開源的組件, 往往考慮的是通用性~~ 但是大廠往往會遇到一些極端的業務場景~~ 往往就需要根據當前的極端業務, 針對上述的開源組件 進行定制化~~
五. 典型使用場景
5.1.緩存(Cache)功能
????????緩存(Cache)功能是?較典型的緩存使?場景,其中Redis作為緩沖層,MySQL作為存儲層,絕?部分請 求的數據都是從Redis中獲取。
緩存讀取的核心流程:
-
優先查詢 Redis:?當應用服務器需要數據時,首先嘗試從 Redis 緩存中讀取。
-
緩存命中 (Cache Hit):?如果所需數據存在于 Redis 中,則直接將其返回給應用服務器。此時不會訪問后端數據庫,顯著提升響應速度和降低數據庫負載。
-
緩存未命中 (Cache Miss):
-
如果 Redis 中不存在所需數據,則應用服務器轉而查詢后端數據庫 (如 MySQL)。
-
從數據庫獲取到結果后,將其返回給應用服務器。
-
同時,將這個查詢結果寫入 Redis 緩存,以備后續相同的請求使用。
-
Redis 與“熱點數據”:
-
Redis 緩存最典型的應用場景就是存儲高頻訪問的數據,即“熱點數據”。
-
上述緩存策略(最近查詢到的數據寫入緩存)本質上是將最近被訪問的數據視為潛在的熱點數據。
-
這隱含了一個假設:某個數據一旦被訪問,它在近期內很可能被再次訪問(時間局部性原理)。這種策略(如 LRU - Least Recently Used 的變體)能有效捕捉并加速訪問這類熱點數據。
潛在問題:緩存膨脹與解決方案
上述策略存在一個明顯的問題:隨著時間推移,越來越多的 Key 會因為首次訪問而從數據庫加載并寫入 Redis。如果不加控制,Redis 中存儲的數據量會持續增長,最終可能導致:
-
內存耗盡:?Redis 主要依賴內存存儲,無限增長的數據必然耗盡內存資源。
-
性能下降:?內存不足可能導致 Redis 自身操作變慢,甚至觸發更嚴重的問題。
核心解決方案:
-
設置過期時間 (TTL - Time To Live):
-
在將數據寫入 Redis 時,必須為其設置一個合理的過期時間。例如:
SET key value EX 3600
?(設置 key 在 3600 秒后過期)。 -
這確保了即使數據不再被訪問,也會在過期后被自動刪除,釋放內存空間。
-
過期時間的選擇需要結合業務場景(數據更新頻率、重要性、容忍的陳舊度等)。
-
-
配置內存淘汰策略 (Eviction Policies):
-
當 Redis 內存使用達到配置的最大限制 (
maxmemory
) 時,它會根據設定的淘汰策略自動刪除部分 Key 以釋放空間,保證新數據可以寫入。 -
常見的淘汰策略包括:
-
volatile-lru
?/?allkeys-lru
: 淘汰最近最少使用的 Key(有過期時間的 / 所有Key)。 -
volatile-lfu
?/?allkeys-lfu
: 淘汰最不經常使用的 Key(有過期時間的 / 所有Key)。 -
volatile-ttl
: 淘汰即將過期的 Key。 -
volatile-random
?/?allkeys-random
: 隨機淘汰 Key(有過期時間的 / 所有Key)。 -
noeviction
: 不淘汰,新寫入操作會報錯(通常不推薦用于緩存場景)。
-
-
選擇合適的淘汰策略對于平衡內存使用和緩存命中率至關重要。
-
由于Redis具有?撐?并發的特性,所以緩存通常能起到加速讀寫和降低后端壓?的作?。
下?的偽代碼模擬了業務數據訪問過程:
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;
}
3)如果沒有從 Redis 中得到用戶信息,及緩存 miss,則進一步從 MySQL 中獲取對應的信息,隨后寫入緩存并返回:
// 如果緩存未命中(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;
}
通過增加緩存功能,在理想情況下,每個用戶信息,一個小時期間只會有一次 MySQL 查詢,極大地提升了查詢效率,也降低了 MySQL 的訪問數。
與 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 的性能明顯下降的。
5.2.計數(Counter)功能
許多應用都會使用 Redis 作為計數的基礎工具,它可以實現快速計數、查詢緩存的功能,同時數據可以異步處理或者落地到其他數據源。
如圖所示,例如視頻網站的視頻播放次數可以使用 Redis 來完成:用戶每播放一次視頻,相應的視頻播放數就會自增 1。
// 在 Redis 中統計某視頻的播放次數
long incrVideoCounter(long vid) {key = "video:" + vid;long count = Redis 執行命令:incr keyreturn counter;
}
實際上要開發一個成熟、穩定的真實計數系統,要面臨的挑戰遠不止如此簡單:防作弊、按照不同維度計數、避免單點問題、數據持久化到底層數據源等。
Redis 并不擅長數據統計!
為什么呢??Redis 的核心優勢在于快速的鍵值存取和豐富的數據結構操作,但它缺乏內置的復雜聚合計算能力,特別是像排序、分組、多鍵關聯統計這類操作。
典型例子:?比如,你想在上面提到的 Redis 緩存(或存儲)中,統計出播放量排名前 100 的視頻有哪些。要實現這個需求:
-
基于 Redis 搞就很麻煩!?你可能需要:
-
遍歷所有相關的視頻 Key(如果數據量大,
KEYS
?命令是災難性的,SCAN
?也慢)。 -
逐個獲取它們的播放量(多次?
GET
/HGET
?命令,網絡開銷大)。 -
在客戶端(應用服務器)內存中對所有視頻的播放量進行排序。
-
最后取出前 100 名。
這個過程不僅性能開銷巨大(O(N) 復雜度),容易阻塞 Redis(如果遍歷不當),而且實現邏輯復雜,代碼量多。
-
相比之下,MySQL 的優勢就顯現了:
-
如果上述播放量數據是存儲在 MySQL 數據庫中的(比如存在一個?
videos
?表里,有?video_id
?和?play_count
?字段)。 -
那么,一個簡單的 SQL 查詢就搞定了!?例如:
SELECT video_id, play_count FROM videos ORDER BY play_count DESC LIMIT 100;
-
MySQL 強大的查詢優化引擎會高效地處理排序 (
ORDER BY
) 和限制結果集 (LIMIT
),通常利用索引就能快速完成,復雜度遠低于 Redis 的 O(N) 遍歷,而且代碼極其簡潔。
5.3.共享會話(Session)
如圖所示,一個分布式 Web 服務將用戶的 Session 信息(例如用戶登錄信息)保存在各自的服務器中,但這樣會造成一個問題:出于負載均衡的考慮,分布式服務會將用戶的訪問請求均衡到不同的服務器上,并且通常無法保證用戶每次請求都會被均衡到同一臺服務器上,這樣當用戶刷新一次訪問是可能會發現需要重新登錄,這個問題是用戶無法容忍的。?
為了解決這個問題,可以使?Redis將??的Session信息進?集中管理,如圖2-13所?,在這種模 式下,只要保證Redis是?可?和可擴展性的,?論??被均衡到哪臺Web服務器上,都集中從 Redis 中查詢、更新Session信息。?
總結一下,就是下面這樣子:
分布式 Session 問題
在分布式 Web 服務中,若用戶 Session 信息(如登錄狀態)存儲在各自服務器內存中,當負載均衡將用戶請求分配到不同服務器時,用戶可能因訪問不同服務器而丟失 Session 狀態(例如刷新頁面后需重新登錄)。這種體驗是用戶無法容忍的。
解決方案:Redis 集中管理
-
統一存儲:所有服務器將 Session 數據(鍵值對結構)集中存儲到 Redis。
-
高可用保障:通過 Redis 集群(主從復制+哨兵)或 Redis Cluster 確保服務連續性。
-
全局訪問:用戶請求攜帶?Session ID(通常存于瀏覽器 Cookie),無論分配到哪臺服務器,均從 Redis 查詢/更新狀態。
→?實現效果:用戶登錄狀態、購物車等會話數據在分布式環境下保持全局一致。
醫療場景類比
原始案例
"我前段時間生病,聲帶發炎到完全說不出話~~
掛號看專家,做喉鏡、霧化理療,開了一周藥量。醫生囑托:'先搞一周看看,一周后復查~~'
一周后復查,發現首診醫生不在!!! 新醫生未接觸過我的病例,不了解情況~~
硬著頭皮就診,新醫生刷我的就診卡,電腦上立即顯示完整病史~~"
技術映射
-
病人=客戶端:需要連續醫療服務
-
醫生=Web服務器:多實例并行服務
-
醫生憑個人記憶=服務器本地存儲Session:導致切換服務器時狀態丟失
-
就診卡=Session ID:唯一標識患者身份(通過Cookie傳遞)
-
醫院病例系統=Redis:集中存儲病史(會話狀態),實現醫生(服務器)間數據共享
核心結論
會話(Session)本質是客戶端與服務器交互中產生的專屬中間狀態,分布式系統必須通過集中存儲(如Redis)解決狀態一致性問題,如同醫院依賴共享病例系統保障連續診療。
5.4.?機驗證碼
????????很多應?出于安全考慮,會在每次進?登錄時,讓??輸??機號并且配合給?機發送驗證碼, 然后讓??再次輸?收到的驗證碼并進?驗證,從?確定是否是??本?。
為了短信接?不會頻繁訪 問,會限制??每分鐘獲取驗證碼的頻率,例如?分鐘不能超過5次,如圖所?。
此功能可以用以下偽代碼說明基本實現思路:
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;}
}
以上介紹了使用 Redis 的字符串數據類型可以使用的幾個場景,但其適用場景遠不止于此,開發人員可以結合字符串類型的特點以及提供的命令,充分發揮自己的想象力,在自己的業務中去找到合適的場景去使用 Redis 的字符串類型。