Redis 有序集合(sorted set)

Redis 有序集合(Sorted Set / ZSet)完全攻略

“排行榜 + 延迟队列 + 优先级任务” 三合一神器

唯一带分数的集合,自动排序,O(log N) 插入,O(1) 查询排名!


一、ZSet 核心特点

特性说明
底层结构跳表(skiplist) + 哈希表
有序score 升序排列
成员唯一同一个 member 只能出现一次
分数可重复多个 member 可有相同 score
最大成员数2^32 – 1(约 42 亿)
核心操作ZADD O(log N),ZRANGE O(log N + M)
内部编码小集合 → ziplist,大集合 → skiplist

二、ZSet 命令全表(共 25 个)

命令说明示例
ZADD key [NX|XX] [CH] [INCR] score member添加/更新ZADD rank 100 "张三"
ZINCRBY key increment member加分ZINCRBY rank 10 "张三"
ZSCORE key member获取分数ZSCORE rank "张三"
ZRANGE key 0 -1 WITHSCORES顺序查看ZRANGE rank 0 9 WITHSCORES
ZREVRANGE key 0 2 WITHSCORES倒序查看ZREVRANGE rank 0 2 WITHSCORES
ZRANK key member排名(从小到大)ZRANK rank "李四"
ZREVRANK key member倒排(从大到小)ZREVRANK rank "李四"
ZREM key member删除成员ZREM rank "王五"
ZREMRANGEBYRANK key 0 99按排名删ZREMRANGEBYRANK rank 0 99
ZREMRANGEBYSCORE key -inf 80按分数删ZREMRANGEBYSCORE rank -inf 80
ZCARD key成员数量ZCARD rank
ZCOUNT key min max分数区间数量ZCOUNT rank 90 100
ZLEXCOUNT key [a [z字典序数量ZLEXCOUNT rank [a [z
ZRANGEBYSCORE key 90 100分数区间ZRANGEBYSCORE rank 90 100
ZREVRANGEBYSCORE key 100 90倒序区间ZREVRANGEBYSCORE rank 100 90
ZRANGEBYLEX key [a [z字典序区间ZRANGEBYLEX rank [a [z
ZSCAN key cursor [MATCH p]渐进式遍历ZSCAN rank 0 MATCH z*
ZPOPMIN key [count]弹出最小ZPOPMIN rank 1
ZPOPMAX key [count]弹出最大ZPOPMAX rank 1
BZPOPMIN key timeout阻塞弹出最小BZPOPMIN delay 0

三、核心实战场景


1. 实时排行榜(游戏、热度)

# 用户得分
ZADD game:score 1500 "player1" 2300 "player2" 1800 "player3"

# 前10名
ZREVRANGE game:score 0 9 WITHSCORES

# 玩家排名
ZREVRANK game:score "player1"   → 1(第2名)

# 加分
ZINCRBY game:score 50 "player1"

2. 延迟队列(定时任务)

# 任务在 5 秒后执行(score = 当前时间戳 + 延迟)
ZADD delay:queue 1734028860 "send_email:1001"

# 轮询执行到期任务
current = time.time()
tasks = ZRANGEBYSCORE delay:queue -inf $current
for task in tasks:
    ZREM delay:queue task
    # 执行任务

更推荐 Redis 7.0+ ZMPOP + 阻塞


3. 优先级任务队列

# 高优先级任务 score 越小越优先
ZADD tasks 1 "backup_db" 3 "send_report" 10 "clean_logs"

# 取出最高优先级任务
task = ZPOPMIN tasks

4. 热点数据排序(文章热度)

# 文章浏览 = 1,点赞 = 5,评论 = 10
ZINCRBY hot:articles 1 "article:1001"   # 浏览
ZINCRBY hot:articles 5 "article:1001"   # 点赞
ZINCRBY hot:articles 10 "article:1001"  # 评论

# 热榜前10
ZREVRANGE hot:articles 0 9 WITHSCORES

5. 时间窗口统计(滑动窗口限流)

# 记录用户请求时间戳
ZADD rate:limit:user:1001 {timestamp} "req1"

# 清理 60 秒前的数据
ZREMRANGEBYSCORE rate:limit:user:1001 -inf {now-60}

# 统计 60 秒内请求数
ZCARD rate:limit:user:1001 > 100 → 限流

四、ZSet vs Set vs List 对比

场景推荐
需要排序/排名ZSet
只需去重Set
保持插入顺序List
延迟/优先级队列ZSet
高频中间插入List

五、内部编码优化(ziplist vs skiplist)

# 小 ZSet → ziplist(省内存)
ZADD small 1 a 2 b 3 c
OBJECT ENCODING small   → "ziplist"

# 大 ZSet → skiplist
ZADD large {1..1000 members}
OBJECT ENCODING large   → "skiplist"

切换阈值

配置默认值说明
zset-max-ziplist-entries128成员 > 128 → skiplist
zset-max-ziplist-value64value > 64 字节 → skiplist

生产建议:调大到 256 提升性能


六、性能与内存优化建议

建议说明
控制成员数 < 10万避免 skiplist 退化
避免大 member 字符串>1KB 建议用 ID
ZREVRANGE 替代 ZRANGE + 逆序更高效
设置 TTLEXPIRE rank:game 86400
ZSCAN 替代 ZRANGE 0 -1大集合安全遍历
# 安全遍历
ZSCAN rank 0 MATCH "player:*" COUNT 100

七、大 ZSet 排查与拆分

排查大 ZSet

redis-cli --bigkeys
# Biggest zset found "leaderboard:global" with 1000000 members

拆分方案

# 按时间分片
ZADD rank:20251112 1500 "p1"
ZADD rank:20251113 1600 "p1"

# 按游戏/地区分片
ZADD rank:game1:cn 1500 "p1"
ZADD rank:game1:us 1400 "p2"

八、一键速查表

# 排行榜
ZADD r 100 a 200 b 150 c
ZREVRANGE r 0 2 WITHSCORES
ZREVRANK r b
ZINCRBY r 50 a

# 延迟队列
ZADD delay 1734028800 "task1"
ZRANGEBYSCORE delay -inf {now}
ZREM delay "task1"

# 管理
ZCARD r
ZCOUNT r 100 200
ZREMRANGEBYRANK r 0 99
ZPOPMIN r 1

# 遍历
ZSCAN r 0 MATCH a*

九、客户端代码示例

Python (redis-py)

import redis
import time
r = redis.Redis()

# 排行榜
r.zadd('game:score', {'player1': 1500, 'player2': 2300})
r.zincrby('game:score', 100, 'player1')

# 前3名
top3 = r.zrevrange('game:score', 0, 2, withscores=True)
print(top3)  # [(b'player2', 2300.0), (b'player1', 1600.0)]

# 延迟任务
r.zadd('delay:queue', {'send_sms': time.time() + 5})
# 轮询执行...

Go (go-redis)

ctx := context.Background()
rdb := redis.NewClient(&redis.Options{Addr: "localhost:6379"})

rdb.ZAdd(ctx, "rank", redis.Z{Score: 100, Member: "p1"})
score, _ := rdb.ZScore(ctx, "rank", "p1").Result()

十、常见问题与解决方案

问题原因解决方案
内存暴涨ZSet 无限增长加 TTL + 分片
ZRANGE 0 -1 卡顿成员 > 100万改用 ZSCAN
排名不准并发加分ZINCRBY 原子操作
分数溢出使用整数double

十一、ZSet 在高并发中的终极用法

1. 原子扣库存 + 排行榜

EVAL "
  local stock = redis.call('zscore', KEYS[1], KEYS[2])
  if stock and stock >= 1 then
    redis.call('zincrby', KEYS[1], -1, KEYS[2])
    return 1
  end
  return 0
" 2 stock:goods:1001 user:order:001

2. 阻塞延迟队列(Redis 7.0+)

# 消费者阻塞等待到期任务
task = BZPOPMIN delay:queue 0

完成!你已精通 Redis ZSet!

# 一行命令体验 ZSet 全功能
redis-cli <<EOF
ZADD r 1 a 2 b 3 c
ZINCRBY r 5 a
ZREVRANGE r 0 -1 WITHSCORES
ZREVRANK r b
ZPOPMIN r
ZCARD r
EOF

下一步推荐

  1. Redis Stream 高级队列
  2. ZSet + Lua 实现秒杀系统
  3. 排行榜架构设计(分片 + 合并)

需要我送你

  • “全球百万玩家实时排行榜方案”
  • “精准延迟队列(ZSet + BZPOPMIN)”
  • “热点文章排名(多维度加权)”

回复:全球榜 | 延迟队列 | 热点榜 即可!

文章已创建 2481

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注

相关文章

开始在上面输入您的搜索词,然后按回车进行搜索。按ESC取消。

返回顶部