Redis中SortedSet類型的實(shí)現(xiàn)示例
一、前言:ZSet 不是“排序列表”,而是有序集合引擎
Redis 的 Sorted Set(有序集合,簡(jiǎn)稱 ZSet) 是 Redis 最強(qiáng)大的數(shù)據(jù)類型之一。
它結(jié)合了 Set 的唯一性 和 Score 的排序能力,每個(gè)成員(member)關(guān)聯(lián)一個(gè)雙精度浮點(diǎn)數(shù)分?jǐn)?shù)(score),并按 score 自動(dòng)排序。
典型應(yīng)用場(chǎng)景包括:
- ? 實(shí)時(shí)排行榜(如游戲積分、熱銷商品)
- ? 延遲隊(duì)列(訂單超時(shí)取消、消息定時(shí)發(fā)送)
- ? 帶權(quán)重的任務(wù)調(diào)度
- ? 滑動(dòng)窗口限流(時(shí)間戳作為 score)
本文將系統(tǒng)講解 ZSet 的核心命令,并通過(guò)真實(shí)業(yè)務(wù)案例展示其強(qiáng)大能力。
二、ZSet 核心命令速查表
| 命令 | 作用 | 時(shí)間復(fù)雜度 |
|---|---|---|
| ZADD key [NX|XX] [CH] [INCR] score member [score member ...] | 添加或更新成員 | O(log N) 每個(gè)成員 |
| ZREM key member [member ...] | 移除成員 | O(log N) 每個(gè)成員 |
| ZSCORE key member | 獲取成員的分?jǐn)?shù) | O(1) |
| ZRANK key member | 獲取成員正序排名(從 0 開始) | O(log N) |
| ZREVRANK key member | 獲取成員倒序排名 | O(log N) |
范圍查詢命令
| 命令 | 作用 | 時(shí)間復(fù)雜度 |
|---|---|---|
| ZRANGE key start stop [WITHSCORES] | 按正序獲取范圍成員 | O(log N + M) |
| ZREVRANGE key start stop [WITHSCORES] | 按倒序獲取范圍成員 | O(log N + M) |
| ZRANGEBYSCORE key min max [WITHSCORES] [LIMIT offset count] | 按分?jǐn)?shù)范圍查詢 | O(log N + M) |
| ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count] | 按分?jǐn)?shù)范圍倒序查詢 | O(log N + M) |
集合操作 & 統(tǒng)計(jì)
| 命令 | 作用 | 時(shí)間復(fù)雜度 |
|---|---|---|
| ZCARD key | 獲取集合大小 | O(1) |
| ZCOUNT key min max | 統(tǒng)計(jì)分?jǐn)?shù)在 [min, max] 的成員數(shù) | O(log N) |
| ZINCRBY key increment member | 對(duì)成員分?jǐn)?shù)加減 | O(log N) |
| ZPOPMIN key [count] | 彈出分?jǐn)?shù)最小的成員 | O(log N × count) |
| ZPOPMAX key [count] | 彈出分?jǐn)?shù)最大的成員 | O(log N × count) |
| BZPOPMIN key [key ...] timeout | 阻塞式彈出最小成員 | O(log N) |
| BZPOPMAX key [key ...] timeout | 阻塞式彈出最大成員 | O(log N) |
?? 關(guān)鍵特性:
- 成員唯一(重復(fù)添加會(huì)更新 score)
- 按 score 自動(dòng)排序(支持相同 score,此時(shí)按字典序排)
- 支持高效范圍查詢與彈出
三、常用命令詳解與示例
3.1 添加與更新:ZADD
# 添加玩家積分(score=積分,member=用戶ID) 127.0.0.1:6379> ZADD game:rank 1500 "user:1001" 1800 "user:1002" 1200 "user:1003" (integer) 3 # 更新用戶積分(覆蓋舊值) 127.0.0.1:6379> ZADD game:rank 1600 "user:1001" (integer) 0 # 成員已存在,僅更新 # 僅當(dāng)成員不存在時(shí)添加(NX) 127.0.0.1:6379> ZADD game:rank NX 2000 "user:1004"
3.2 排行榜查詢:ZREVRANGE(倒序 = 從高到低)
# 獲取 TOP 3(帶分?jǐn)?shù)) 127.0.0.1:6379> ZREVRANGE game:rank 0 2 WITHSCORES 1) "user:1002" 2) "1800" 3) "user:1001" 4) "1600" 5) "user:1003" 6) "1200" # 獲取用戶排名(從 0 開始) 127.0.0.1:6379> ZREVRANK game:rank "user:1001" (integer) 1 # 第 2 名(0-indexed)
? 這是實(shí)現(xiàn)“游戲排行榜”、“熱銷榜”的標(biāo)準(zhǔn)做法!
3.3 分?jǐn)?shù)范圍查詢:ZRANGEBYSCORE
# 查詢積分在 1500~2000 的用戶 127.0.0.1:6379> ZRANGEBYSCORE game:rank 1500 2000 WITHSCORES 1) "user:1001" 2) "1600" 3) "user:1002" 4) "1800" # 分頁(yè)查詢(每頁(yè) 10 條) 127.0.0.1:6379> ZREVRANGEBYSCORE game:rank +inf -inf LIMIT 10 10
?? 注意:
- +inf 表示正無(wú)窮,-inf 表示負(fù)無(wú)窮
- LIMIT offset count 支持分頁(yè),但大數(shù)據(jù)量下 offset 大時(shí)性能下降
3.4 延遲隊(duì)列:ZADD + BZPOPMIN
# 添加延遲任務(wù)(score = 執(zhí)行時(shí)間戳) 127.0.0.1:6379> ZADD delay_queue 1700000000 "order:cancel:1001" 127.0.0.1:6379> ZADD delay_queue 1700003600 "email:send:2001" # 消費(fèi)者:阻塞等待可執(zhí)行任務(wù) 127.0.0.1:6379> BZPOPMIN delay_queue 5 1) "delay_queue" 2) "order:cancel:1001" 3) "1700000000"
? 優(yōu)勢(shì):天然支持定時(shí)、去重、可靠消費(fèi)
? 局限:不支持 ACK(需配合 Lua 或狀態(tài)標(biāo)記)
四、ZSet 的內(nèi)部編碼
Redis 對(duì)小 ZSet 使用 ziplist 編碼,大 ZSet 使用 skiplist(跳躍表) + dict(哈希表):
# redis.conf 默認(rèn)配置 zset-max-ziplist-entries 128 zset-max-ziplist-value 64
- ziplist:內(nèi)存緊湊,適合小集合
- skiplist + dict:
- skiplist 支持 O(log N) 范圍查詢
- dict 支持 O(1) 成員查找(如 ZSCORE)
?? 建議:
- 排行榜、延遲隊(duì)列等場(chǎng)景性能極佳
- 避免單個(gè) ZSet 超過(guò) 100 萬(wàn)元素
五、實(shí)戰(zhàn)應(yīng)用場(chǎng)景
場(chǎng)景 1:實(shí)時(shí)游戲排行榜
// Java (Lettuce) String rankKey = "game:season_2025:rank"; // 玩家得分更新 redis.zadd(rankKey, score, "player:" + playerId); // 獲取 TOP 50 List<ScoredValue<String>> top50 = redis.zrevrangeWithScores(rankKey, 0, 49); // 獲取玩家排名 Long rank = redis.zrevrank(rankKey, "player:" + playerId);
?? 優(yōu)化:對(duì)超大排行榜,可分桶(如每 1 萬(wàn)一名一個(gè) ZSet)
場(chǎng)景 2:訂單超時(shí)取消(延遲隊(duì)列)
import time
import redis
r = redis.Redis()
# 創(chuàng)建訂單時(shí)加入延遲隊(duì)列(30分鐘后取消)
expire_time = int(time.time()) + 1800
r.zadd("order:delay_cancel", {f"order:{order_id}": expire_time})
# 后臺(tái)消費(fèi)者
while True:
now = int(time.time())
# 彈出所有已到期訂單
expired = r.zrangebyscore("order:delay_cancel", 0, now)
if expired:
for order in expired:
cancel_order(order)
r.zrem("order:delay_cancel", order)
time.sleep(1)? 更優(yōu)方案:使用 BZPOPMIN 實(shí)現(xiàn)阻塞消費(fèi),避免輪詢
場(chǎng)景 3:帶權(quán)重的任務(wù)調(diào)度
# 任務(wù)權(quán)重:高優(yōu)先級(jí)任務(wù) score 更?。ㄏ葓?zhí)行) ZADD task_queue 10 "urgent_task_1" ZADD task_queue 50 "normal_task_1" ZADD task_queue 100 "low_task_1" # 消費(fèi)者取最高優(yōu)先級(jí)任務(wù) BZPOPMIN task_queue 0
六、常見誤區(qū)與最佳實(shí)踐
? 誤區(qū) 1:用ZRANGE 0 -1獲取全量數(shù)據(jù)
- 問(wèn)題:大數(shù)據(jù)量阻塞主線程
- 建議:分頁(yè)查詢 + 限制總量(如只查 TOP 1000)
? 誤區(qū) 2:在 ZSet 中存儲(chǔ)大 Value
- 問(wèn)題:member 過(guò)大會(huì)增加內(nèi)存和網(wǎng)絡(luò)開銷
- 建議:member 用 ID,詳情存 Hash 或 DB
? 最佳實(shí)踐
- score 設(shè)計(jì)合理:
- 排行榜:直接用分?jǐn)?shù)
- 延遲隊(duì)列:用 Unix 時(shí)間戳(秒或毫秒)
- 設(shè)置 TTL:臨時(shí) ZSet(如活動(dòng)排行榜)加過(guò)期時(shí)間
- 監(jiān)控大?。和ㄟ^(guò) ZCARD + 告警防止膨脹
- 避免大 offset 分頁(yè):改用游標(biāo)或分桶
七、ZSet vs 其他類型選型建議
| 需求 | 推薦類型 |
|---|---|
| 實(shí)時(shí)排行榜 | ZSet |
| 延遲/定時(shí)任務(wù) | ZSet(score=時(shí)間戳) |
| 唯一性集合 | Set |
| 順序敏感隊(duì)列 | List |
| 對(duì)象屬性 | Hash |
?? ZSet 的核心價(jià)值:排序 + 唯一 + 范圍查詢
八、結(jié)語(yǔ)
到此這篇關(guān)于Redis中SortedSet類型的實(shí)現(xiàn)示例的文章就介紹到這了,更多相關(guān)Redis SortedSet類型內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
redis數(shù)據(jù)結(jié)構(gòu)之String詳解
Redis以String為基礎(chǔ)類型,因C字符串效率低、非二進(jìn)制安全等問(wèn)題,采用SDS動(dòng)態(tài)字符串實(shí)現(xiàn)高效存儲(chǔ),通過(guò)RedisObject封裝,支持多種編碼方式(如RAW、EMBSTR、INT)和內(nèi)存優(yōu)化策略,靈活管理數(shù)據(jù)結(jié)構(gòu)與內(nèi)存使用2025-08-08
Redis源碼環(huán)境構(gòu)建過(guò)程詳解
這篇文章主要介紹了Redis源碼環(huán)境構(gòu)建過(guò)程,本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2021-07-07
Redis教程(十五):C語(yǔ)言連接操作代碼實(shí)例
這篇文章主要介紹了Redis教程(十五):C語(yǔ)言連接操作代碼實(shí)例,本篇博客是該系列博客中的最后一篇,在這里將給出基于Redis客戶端組件訪問(wèn)并操作Redis服務(wù)器的代碼示例,需要的朋友可以參考下2015-05-05
解決redis-cli報(bào)錯(cuò)Could not connect to Redis&
這篇文章主要介紹了解決redis-cli報(bào)錯(cuò)Could not connect to Redis at 127.0.0.1:6379: Connection refused,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2025-04-04
Redis分布式鎖的使用和實(shí)現(xiàn)原理詳解
這篇文章主要給大家介紹了關(guān)于Redis分布式鎖的使用和實(shí)現(xiàn)原理的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-11-11
Redis中3種特殊的數(shù)據(jù)類型(BitMap、Geo和HyperLogLog)
這篇文章主要給大家介紹了關(guān)于Redis中3種特殊的數(shù)據(jù)類型(BitMap、GEOADD和GEODIST)的相關(guān)資料,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧。2018-03-03
redis requires ruby version2.2.2的解決方案
本文主要介紹了redis requires ruby version2.2.2的解決方案,文中通過(guò)示例代碼介紹的非常詳細(xì),需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2021-07-07
shell腳本批量導(dǎo)出redis key-value方式
為避免keys全量掃描導(dǎo)致Redis卡頓,可先通過(guò)dump.rdb備份文件在本地恢復(fù),再使用scan命令漸進(jìn)導(dǎo)出key-value,通過(guò)CNT和INTERVAL參數(shù)控制負(fù)載,且scan不指定游標(biāo)可減少阻塞2025-08-08
redisson中RRateLimiter分布式限流器的使用
Redisson Ratelimiter是Redisson框架中的一種限流算法,用于限制對(duì)資源的訪問(wèn)頻率,本文主要介紹了redisson中RRateLimiter分布式限流器的使用,感興趣的可以了解一下2024-06-06

