淺談 Redis 數據類型
(一)String 類型
Redis 的 String 類型 是二進制安全的,可以用來存儲 文本字符串、int 類型數據和 bitmap 位圖 等數據。
1. 字符串操作
-
適用于存儲 文本、JSON、序列化數據 等任意二進制安全的內容
命令 作用 示例 SET
設置鍵值 SET name "Alice"
GET
獲取值 GET name
→"Alice"
APPEND
追加內容 APPEND name " Smith"
→"Alice Smith"
STRLEN
獲取字節長度 STRLEN name
→11
GETRANGE
截取子串 GETRANGE name 0 4
→"Alice"
SETRANGE
覆蓋部分內容 SETRANGE name 6 "Taylor"
→"Alice Taylor"
MSET
/MGET
批量操作 MSET k1 "v1" k2 "v2"
-
二進制安全:可存儲圖片、序列化對象等任意數據
-
自動編碼:短字符串用
embstr
編碼(內存連續),長字符串用raw
編碼
SETNX 操作實現分布式鎖
SETNX key value
-
當
key
不存在 時,設置其值為value
,并返回OK
(成功);若key
已存在,則不做任何操作,返回nil
(失敗) -
為防止鎖持有者崩潰后鎖無法釋放,需設置超時(通過
EXPIRE
) -
任務完成后,主動刪除鍵以釋放鎖(通過
DEL
) -
**【風險1】**若
SETNX
成功,但EXPIRE
未執行(如客戶端崩潰),鎖會永久占用【解決方案】使用 ??
SET
命令的NX
和EX
選項?? 原子性加鎖和設置過期時間SET key value NX EX 10 # 原子操作:僅當不存在時設置,并過期時間為 10 秒
-
**【風險2】**任務未在過期時間內完成,鎖被提前釋放
**【解決方案】**看門狗機制:啟動后臺線程定期續期
EVAL "if redis.call('GET', KEYS[1]) == ARGV[1] then return redis.call('EXPIRE', KEYS[1], ARGV[2]) else return 0 end" 1 key value 10 # 每 10 秒執行一次該 LUA 腳本以續期
2. 數值操作
- 當 String 的值是 整數或浮點數 時,Redis 提供專用命令進行原子增減
命令 | 作用 | 示例 |
---|---|---|
INCR | 原子 +1 | INCR counter → 1 |
INCRBY | 原子 +N | INCRBY counter 5 → 6 |
DECR | 原子 -1 | DECR counter → 5 |
DECRBY | 原子 -N | DECRBY counter 3 → 2 |
INCRBYFLOAT | 原子 +浮點數 | INCRBYFLOAT price 1.5 → "3.5" |
- 編碼優化:數值用
int
編碼(固定 8 字節),浮點數用raw
。 - 原子性:無需事務即可避免并發沖突。
3. bitmap 位圖操作
- Bitmap 是 String 的擴展,通過 位(bit)操作 實現布爾統計,可用于布隆過濾器、用戶狀態統計等場景
命令 | 作用 | 示例 |
---|---|---|
SETBIT | 設置某位為 0/1 | SETBIT login:2023 100 1 (用戶 100 登錄) |
GETBIT | 獲取某位的值 | GETBIT login:2023 100 → 1 |
BITCOUNT | 統計 1 的數量 | BITCOUNT login:2023 → 50 (50 人登錄) |
BITOP | 位運算(AND/OR/XOR) | BITOP AND result login:day1 login:day2 |
BITPOS | 查找第一個 0/1 位 | BITPOS login:2023 1 → 100 |
- 內存高效:1 億用戶登錄狀態僅需約 12MB
- 高性能:位運算復雜度 O(n),適合批量處理
4. 二進制安全與 Encoding 加速機制
- Redis 的 String 類型基于簡單動態字符串實現,數據按字節數組存儲,不預設編碼格式,讀寫時不會做任何轉換
- Redis 為 String 類型設計了多種編碼格式(
encoding
),根據數據內容動態選擇最優編碼以節省內存和提高性能。通過OBJECT ENCODING key
可查看編碼類型
編碼類型 | 適用場景 | 優化原理 |
---|---|---|
int | 存儲 64 位有符號整數(如 123 ) | 直接使用整數存儲,避免字符串轉換。 |
embstr | 短字符串(≤44 字節,Redis 7+) | 內存連續分配,減少碎片,CPU 緩存友好。 |
raw | 長字符串(>44 字節)或二進制數據 | 標準 SDS 動態分配,支持大容量數據。 |
(二) List 類型
Redis 的 List 類型 是一個雙向鏈表數據結構,支持在頭部和尾部高效插入、刪除元素,因此可以靈活實現 棧(Stack)、隊列(Queue)、數組(Array) 和 阻塞隊列(Blocking Queue) 的語義。
- 棧:
LPUSH
+LPOP
- 隊列:
RPUSH
+LPOP
- 數組:
LINDEX
+LSET
- 阻塞隊列:
RPUSH
+BRPOP
以下是詳細用法解析:
1. List 作為棧(Stack)
特點:后進先出(LIFO),通過 LPUSH
+ LPOP
實現
??適用場景??:函數調用棧、撤銷操作(Undo)記錄
??操作命令??:
# 入棧(左側插入)
LPUSH stack "task1"
LPUSH stack "task2" # 棧內順序: ["task2", "task1"]# 出棧(左側彈出)
LPOP stack # 返回 "task2",棧剩余: ["task1"]# 查看棧頂元素(不彈出)
LINDEX stack 0 # 返回 "task1"
2. List 作為隊列(Queue)
特點:先進先出(FIFO),通過 RPUSH
+ LPOP
實現
??適用場景??:任務隊列、消息緩沖
??操作命令??:
# 入隊(尾部插入)
RPUSH queue "msg1"
RPUSH queue "msg2" # 隊列順序: ["msg1", "msg2"]# 出隊(頭部彈出)
LPOP queue # 返回 "msg1",隊列剩余: ["msg2"]# 查看隊列長度
LLEN queue # 返回 1
3. List 作為數組(Array)
特點:支持按索引訪問和修改,通過 LINDEX
+ LSET
實現
??適用場景??:隨機訪問列表元素、動態數組
??操作命令??:
# 初始化數組
RPUSH array "a" "b" "c" # 數組: ["a", "b", "c"]# 按索引讀取(索引從0開始)
LINDEX array 1 # 返回 "b"# 按索引修改
LSET array 1 "B" # 數組變為: ["a", "B", "c"]# 獲取全部元素
LRANGE array 0 -1 # 返回 ["a", "B", "c"]
4. List 作為阻塞隊列(Blocking Queue)
特點:消費者阻塞等待新元素,通過 BRPOP
/BLPOP
實現
??適用場景??:實時消息系統、任務調度(避免輪詢)
??操作命令??:
# 生產者入隊(尾部插入)
RPUSH bqueue "job1" "job2"# 消費者阻塞出隊(從頭部獲取,超時時間5秒)
BRPOP bqueue 5
# 1) 如果隊列不為空,立即返回元素(如 "job1")
# 2) 如果隊列為空,阻塞 5 秒后返回nil(若期間有新元素插入則立即返回)
(三)Hash 類型
Redis 的 Hash 類型 是一個 鍵值對集合,適合存儲對象。它比 String 類型更節省內存,且支持 字段級操作(單獨讀寫某個字段而無需序列化整個對象)。
1. Hash 的底層結構
ziplist
(壓縮列表):當字段數和字段值較小時使用,內存連續,節省空間hashtable
(哈希表):字段較多或值較大時自動轉換,查詢效率 O(1)
2. 核心操作命令
(1)設置與獲取字段值
命令 | 作用 | 示例 |
---|---|---|
HSET | 設置字段值 | HSET user:1 name "Alice" age 30 |
HGET | 獲取字段值 | HGET user:1 name → "Alice" |
HMSET | 批量設置字段 | HMSET user:1 name "Alice" age 30 |
HMGET | 批量獲取字段 | HMGET user:1 name age → ["Alice", "30"] |
HGETALL | 獲取所有字段和值 | HGETALL user:1 → ["name", "Alice", "age", "30"] |
- 示例:
HSET product:1001 name "iPhone" price 999 stock 50
HGET product:1001 price # 返回 "999"
HGETALL product:1001 # 返回所有字段和值
(2)字段增減與刪除
命令 | 作用 | 示例 |
---|---|---|
HINCRBY | 字段值整數增減 | HINCRBY user:1 age 1 |
HINCRBYFLOAT | 字段值浮點數增減 | HINCRBYFLOAT account:1 balance 50.5 |
HDEL | 刪除字段 | HDEL user:1 age |
HEXISTS | 判斷字段是否存在 | HEXISTS user:1 name → 1 (存在) |
- 示例:
HINCRBY product:1001 stock -1 # 庫存減1
HDEL product:1001 price # 刪除價格字段
(3)查詢與統計
命令 | 作用 | 示例 |
---|---|---|
HKEYS | 獲取所有字段名 | HKEYS user:1 → ["name", "age"] |
HVALS | 獲取所有字段值 | HVALS user:1 → ["Alice", "30"] |
HLEN | 獲取字段數量 | HLEN user:1 → 2 |
HSTRLEN | 獲取字段值的字節長度 | HSTRLEN user:1 name → 5 ("Alice"占 5 字節) |
- 示例:
HKEYS product:1001 # 返回 ["name", "price", "stock"]
HLEN product:1001 # 返回 3
(4)原子操作
命令 | 作用 | 示例 |
---|---|---|
HSETNX | 字段不存在時才設置 | HSETNX user:1 email "alice@example.com" |
HSCAN | 增量迭代字段(大數據量時避免阻塞) | HSCAN user:1 0 |
- 示例:
HSETNX user:1 email "alice@example.com" # 僅當 email 不存在時設置
3. 對象的三種存儲策略:單 key 存儲、多 key 存儲、使用 hash 類型
(1)序列化為字符串(單 Key 存儲)
SET user:1000 '{"name":"Alice","age":30,"email":"alice@example.com"}'
(2)每個字段單獨存儲(多 Key 存儲)
SET user:1000:name "Alice"
SET user:1000:age 30
SET user:1000:email "alice@example.com"
(3)使用 Hash 類型
HSET user:1000 name "Alice" age 30 email "alice@example.com"
(4)三種策略比較
維度 | 序列化為字符串 | 多 Key 存儲 | Hash 類型 |
---|---|---|---|
讀寫效率 | 整體讀寫快,部分更新慢 | 部分讀寫快,整體查詢慢 | 部分和批量讀寫均快 |
內存占用 | 低(單個 Key) | 高(每個 Key 有元數據) | 中(小 Hash 用 zip list) |
原子性 | 高(整個對象原子操作) | 低(需事務) | 中(單字段操作原子) |
字段級 TTL | 不支持 | 支持 | 不支持 |
適用字段規模 | 任意 | 少量字段 | 中小規模字段 |
復雜結構支持 | 支持(JSON 等序列化方式) | 需額外設計 | 需序列化字段值 |
(四)Set 類型
Redis 的 Set 類型 是一個 無序、去重的集合,底層基于哈希表實現,支持插入、刪除元素以及多個集合的交并差集運算,同時提供了隨機返回指定個數元素的功能。
1. Set 的核心特性
- 無序性:元素沒有固定順序,遍歷時順序不確定
- 唯一性:自動去重,重復插入的元素會被忽略
2. 常用命令
(1)基本操作
命令 | 作用 | 示例 |
---|---|---|
SADD | 添加元素(自動去重) | SADD tags "redis" "db" |
SREM | 刪除元素 | SREM tags "db" |
SMEMBERS | 獲取所有元素 | SMEMBERS tags |
SISMEMBER | 判斷元素是否存在 | SISMEMBER tags "redis" → 1 (存在) |
SCARD | 獲取集合元素數量 | SCARD tags → 2 |
SRANDMEMBER | 隨機返回元素(可指定數量) | SRANDMEMBER tags 2 |
- 示例:
SADD users:1000:followers "user1" "user2" "user3"
SMEMBERS users:1000:followers # 返回 ["user1", "user2", "user3"]
SISMEMBER users:1000:followers "user1" # 返回 1(存在)
(2)集合運算
命令 | 作用 | 示例 |
---|---|---|
SINTER | 返回多個集合的交集 | SINTER set1 set2 |
SUNION | 返回多個集合的并集 | SUNION set1 set2 |
SDIFF | 返回第一個集合與其他集合的差集 | SDIFF set1 set2 |
SINTERSTORE | 存儲交集到新集合 | SINTERSTORE result set1 set2 |
SUNIONSTORE | 存儲并集到新集合 | SUNIONSTORE result set1 set2 |
SDIFFSTORE | 存儲差集到新集合 | SDIFFSTORE result set1 set2 |
- 示例:
SADD group1 "user1" "user2" "user3"
SADD group2 "user2" "user3" "user4"
SINTER group1 group2 # 返回 ["user2", "user3"](共同成員)
SDIFF group1 group2 # 返回 ["user1"](僅在 group1 中的成員)
(3)隨機返回元素
- Redis 的 Set 類型提供了 隨機返回元素 的功能,適用于 抽獎、隨機推薦、隨機選取樣本 等場景
SRANDMEMBER key count
key
:Set 的鍵名count
:指定返回的元素數量count > 0
:返回 不重復 的元素(最多返回整個 Set 的大小)count < 0
:返回元素可能 重復(適用于允許重復抽獎的場景)
(五)Sorted Set 類型
Redis 中的 Sorted Set 類型 也是一種集合類型,集合中的元素可以按照自定義分值排序,底層基于跳表實現。
1. 特點
- 唯一性:成員(member)不可重復,但分數(score)可重復
- 有序性:按 score 排序(默認升序),相同 score 按字典序排序
2. 核心命令
-
成員操作命令
命令 功能 ZADD key score member
添加/更新成員(支持 NX
/XX
/INCR
選項)ZREM key member
刪除指定成員 ZINCRBY key increment member
增加成員分數 ZSCORE key member
獲取成員分數 -
查詢命令
命令 功能 ZRANGE key start stop
按分數升序返回成員( WITHSCORES
顯示分數)ZREVRANGE key start stop
按分數降序返回成員 ZRANGEBYSCORE key min max
返回分數在 [min,max]
區間的成員 -
排名統計命令
命令 功能 ZRANK key member
獲取成員升序排名(從 0 開始) ZREVRANK key member
獲取成員降序排名 ZCARD key
返回成員總數 ZCOUNT key min max
統計分數范圍內的成員數 -
集合運算命令
計算多個有序集合的并集存儲(交集存儲)
ZUNIONSTORE/ZINTERSTORE destkey numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE SUM|MIN|MAX]
-
必選參數
destkey
:存儲結果的鍵名numkeys
:參與計算的集合數量(后續需對應數量的key
)key
:參與計算的集合鍵名(至少一個)
-
可選參數
WEIGHTS
:為每個集合的分值設置權重(默認權重為1
)AGGREGATE
:合并重復成員的分值策略(SUM
求和、MIN
取最小、MAX
取最大,默認SUM
)
-
示例
ZADD scores1 10 "Alice" 20 "Bob" ZADD scores2 5 "Alice" 30 "Charlie" ZUNIONSTORE result 2 scores1 scores2 WEIGHTS 2 3 SUM
-
Alice
的分值:10 * 2 + 5 * 3 = 35
-
Bob
的分值 =20 * 2 = 40
-
Charlie
的分值 =30 * 3 = 90
1) "Alice" 35 2) "Bob" 40 3) "Charlie" 90
-
3. 底層實現
Sorted Set 是 Redis 中最復雜的數據結構之一。它通過 跳表和哈希表 的混合實現,兼顧了高效查詢和動態排序的能力。
編碼方式 | 數據結構 | 觸發條件 | 特點 |
---|---|---|---|
ziplist | 壓縮列表 [member1, score1, member2, score2, ...] | 元素數量 ≤ zset-max-ziplist-entries (默認128)且所有元素長度 ≤ zset-max-ziplist-value (默認64字節) | 內存緊湊,但增刪效率低(O(n)) |
skiplist + dict | 跳表 + 哈希表 | 不滿足 ziplist 條件時自動轉換 | 支持高效查詢和范圍操作(O(log n)) |
-
哈希表與跳表的協同
跳表保證有序性,哈希表加速單成員查詢,兩者互補
-
內存與 CPU 的權衡
小數據用
ziplist
節省內存,大數據用skiplist
提升操作效率
(1)跳表的結構
-
跳表(Skip List)是一種基于 多層有序鏈表 的數據結構,通過 概率平衡 實現高效的動態操作(平均 O(log n) 時間復雜度)
-
跳表由多層鏈表組成,每層鏈表都是有序的,但高層鏈表是低層鏈表的【快速通道】:
-
最底層鏈表:包含所有元素的有序鏈表
-
高層鏈表:每層節點數約為下一層的一半,形成跳躍路徑
-
(2)查找原理:平均 O(log n)(最壞 O(n))
-
從最高層頭節點開始,向右遍歷:
-
若當前節點的下一個節點值 ≤ 目標值,則繼續向右
-
若下一個節點值 > 目標值,則向下移動到下一層
-
-
重復上述過程,直到最底層,找到目標節點或確認不存在
(3)插入原理:平均 O(log n)
- 查找插入位置,記錄每一層的前驅節點,即插入位置左側的節點
- 隨機生成層高,每個新插入節點的層數由 概率決定,通常采用 “拋硬幣”策略:
- 初始層高:新節點至少在第
0
層(最底層,包含所有節點) - 逐層晉升:每次“拋硬幣” 若為“正面”(概率通常為
50%
)則層數+1
;否則停止 - Redis 優化:降低晉升概率(
p=0.25
)和限制最大層數(32
),平衡性能與內存開銷
- 初始層高:新節點至少在第
- 在每一層(從生成的最高層到最底層)鏈表中插入新節點,并更新前后節點的指針