Redis中SortedSet类型的实现示例
作者:咖啡の猫
本文主要介绍了Redis中SortedSet类型的实现示例,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧
一、前言:ZSet 不是“排序列表”,而是有序集合引擎
Redis 的 Sorted Set(有序集合,简称 ZSet) 是 Redis 最强大的数据类型之一。
它结合了 Set 的唯一性 和 Score 的排序能力,每个成员(member)关联一个双精度浮点数分数(score),并按 score 自动排序。
典型应用场景包括:
- ✅ 实时排行榜(如游戏积分、热销商品)
- ✅ 延迟队列(订单超时取消、消息定时发送)
- ✅ 带权重的任务调度
- ✅ 滑动窗口限流(时间戳作为 score)
本文将系统讲解 ZSet 的核心命令,并通过真实业务案例展示其强大能力。
二、ZSet 核心命令速查表
| 命令 | 作用 | 时间复杂度 |
|---|---|---|
| ZADD key [NX|XX] [CH] [INCR] score member [score member ...] | 添加或更新成员 | O(log N) 每个成员 |
| ZREM key member [member ...] | 移除成员 | O(log N) 每个成员 |
| ZSCORE key member | 获取成员的分数 | O(1) |
| ZRANK key member | 获取成员正序排名(从 0 开始) | O(log N) |
| ZREVRANK key member | 获取成员倒序排名 | O(log N) |
范围查询命令
| 命令 | 作用 | 时间复杂度 |
|---|---|---|
| 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] | 按分数范围查询 | O(log N + M) |
| ZREVRANGEBYSCORE key max min [WITHSCORES] [LIMIT offset count] | 按分数范围倒序查询 | O(log N + M) |
集合操作 & 统计
| 命令 | 作用 | 时间复杂度 |
|---|---|---|
| ZCARD key | 获取集合大小 | O(1) |
| ZCOUNT key min max | 统计分数在 [min, max] 的成员数 | O(log N) |
| ZINCRBY key increment member | 对成员分数加减 | O(log N) |
| ZPOPMIN key [count] | 弹出分数最小的成员 | O(log N × count) |
| ZPOPMAX key [count] | 弹出分数最大的成员 | O(log N × count) |
| BZPOPMIN key [key ...] timeout | 阻塞式弹出最小成员 | O(log N) |
| BZPOPMAX key [key ...] timeout | 阻塞式弹出最大成员 | O(log N) |
💡 关键特性:
- 成员唯一(重复添加会更新 score)
- 按 score 自动排序(支持相同 score,此时按字典序排)
- 支持高效范围查询与弹出
三、常用命令详解与示例
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 # 成员已存在,仅更新 # 仅当成员不存在时添加(NX) 127.0.0.1:6379> ZADD game:rank NX 2000 "user:1004"
3.2 排行榜查询:ZREVRANGE(倒序 = 从高到低)
# 获取 TOP 3(带分数) 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)
✅ 这是实现“游戏排行榜”、“热销榜”的标准做法!
3.3 分数范围查询:ZRANGEBYSCORE
# 查询积分在 1500~2000 的用户 127.0.0.1:6379> ZRANGEBYSCORE game:rank 1500 2000 WITHSCORES 1) "user:1001" 2) "1600" 3) "user:1002" 4) "1800" # 分页查询(每页 10 条) 127.0.0.1:6379> ZREVRANGEBYSCORE game:rank +inf -inf LIMIT 10 10
🔍 注意:
- +inf 表示正无穷,-inf 表示负无穷
- LIMIT offset count 支持分页,但大数据量下 offset 大时性能下降
3.4 延迟队列:ZADD + BZPOPMIN
# 添加延迟任务(score = 执行时间戳) 127.0.0.1:6379> ZADD delay_queue 1700000000 "order:cancel:1001" 127.0.0.1:6379> ZADD delay_queue 1700003600 "email:send:2001" # 消费者:阻塞等待可执行任务 127.0.0.1:6379> BZPOPMIN delay_queue 5 1) "delay_queue" 2) "order:cancel:1001" 3) "1700000000"
✅ 优势:天然支持定时、去重、可靠消费
❌ 局限:不支持 ACK(需配合 Lua 或状态标记)
四、ZSet 的内部编码
Redis 对小 ZSet 使用 ziplist 编码,大 ZSet 使用 skiplist(跳跃表) + dict(哈希表):
# redis.conf 默认配置 zset-max-ziplist-entries 128 zset-max-ziplist-value 64
- ziplist:内存紧凑,适合小集合
- skiplist + dict:
- skiplist 支持 O(log N) 范围查询
- dict 支持 O(1) 成员查找(如 ZSCORE)
💡 建议:
- 排行榜、延迟队列等场景性能极佳
- 避免单个 ZSet 超过 100 万元素
五、实战应用场景
场景 1:实时游戏排行榜
// 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);
📌 优化:对超大排行榜,可分桶(如每 1 万一名一个 ZSet)
场景 2:订单超时取消(延迟队列)
import time
import redis
r = redis.Redis()
# 创建订单时加入延迟队列(30分钟后取消)
expire_time = int(time.time()) + 1800
r.zadd("order:delay_cancel", {f"order:{order_id}": expire_time})
# 后台消费者
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)✅ 更优方案:使用 BZPOPMIN 实现阻塞消费,避免轮询
场景 3:带权重的任务调度
# 任务权重:高优先级任务 score 更小(先执行) ZADD task_queue 10 "urgent_task_1" ZADD task_queue 50 "normal_task_1" ZADD task_queue 100 "low_task_1" # 消费者取最高优先级任务 BZPOPMIN task_queue 0
六、常见误区与最佳实践
❌ 误区 1:用ZRANGE 0 -1获取全量数据
- 问题:大数据量阻塞主线程
- 建议:分页查询 + 限制总量(如只查 TOP 1000)
❌ 误区 2:在 ZSet 中存储大 Value
- 问题:member 过大会增加内存和网络开销
- 建议:member 用 ID,详情存 Hash 或 DB
✅ 最佳实践
- score 设计合理:
- 排行榜:直接用分数
- 延迟队列:用 Unix 时间戳(秒或毫秒)
- 设置 TTL:临时 ZSet(如活动排行榜)加过期时间
- 监控大小:通过 ZCARD + 告警防止膨胀
- 避免大 offset 分页:改用游标或分桶
七、ZSet vs 其他类型选型建议
| 需求 | 推荐类型 |
|---|---|
| 实时排行榜 | ZSet |
| 延迟/定时任务 | ZSet(score=时间戳) |
| 唯一性集合 | Set |
| 顺序敏感队列 | List |
| 对象属性 | Hash |
📌 ZSet 的核心价值:排序 + 唯一 + 范围查询
八、结语
到此这篇关于Redis中SortedSet类型的实现示例的文章就介绍到这了,更多相关Redis SortedSet类型内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
