Redis 排行榜:ZSET 进阶(更新、分页、删除与复合排序)
有序集合(ZSET)是 Redis 做排行榜的秘密武器。它以 O(log N) 的复杂度插入元素,O(log N + M) 的复杂度进行范围查询。本指南从基础排名到高级模式全面覆盖。
问题场景
用关系型数据库做排行榜意味着:
- 每次请求都要
ORDER BY score DESC——大规模下很慢。 - 计算排名需要 COUNT 行数——每次查询
O(N)。 - 实时更新需要重新排序。
Redis ZSET 用内置排序数据和 O(log N) 的排名查找解决了这三个问题。
基础排行榜
添加分数
ZADD leaderboard 1500 "alice"
ZADD leaderboard 2300 "bob"
ZADD leaderboard 1800 "charlie"
ZADD leaderboard 2100 "diana"
ZADD leaderboard 1950 "eve"
Top N 玩家
ZREVRANGE leaderboard 0 2 WITHSCORES
返回分数最高的前 3 名玩家。
获取玩家排名
ZREVRANK leaderboard "charlie"
ZSCORE leaderboard "charlie"
ZREVRANK 返回从 0 开始的排名(0 = 第一名)。一次获取 Charlie 的排名和分数。
分数更新
增加分数
当玩家获得积分时,使用 ZINCRBY:
ZINCRBY leaderboard 500 "charlie"
ZSCORE leaderboard "charlie"
ZREVRANK leaderboard "charlie"
Charlie 的分数从 1800 变为 2300,排名自动更新。
替换分数
设置绝对分数(如重新计算后):
ZADD leaderboard 3000 "alice"
ZSCORE leaderboard "alice"
ZREVRANK leaderboard "alice"
如果成员已存在,ZADD 会更新分数。
分页
对于有数千名玩家的排行榜,你需要分页。
基于页码的分页
ZREVRANGE leaderboard 0 2 WITHSCORES
ZREVRANGE leaderboard 3 5 WITHSCORES
ZREVRANGE leaderboard 6 8 WITHSCORES
每页 3 条。第 1 页 = 索引 0–2,第 2 页 = 索引 3–5,以此类推。
获取总数
ZCARD leaderboard
用它计算总页数:ceil(ZCARD / page_size)。
基于分数的分页(游标)
对于超大排行榜,基于分数的分页避免了偏移量开销:
ZREVRANGEBYSCORE leaderboard +inf -inf LIMIT 0 3 WITHSCORES
然后用最后一个分数作为下一页的游标。
删除玩家
删除单个玩家
ZREM leaderboard "eve"
ZREVRANGE leaderboard 0 -1 WITHSCORES
按排名范围删除
删除排名最低的 2 名玩家:
ZREMRANGEBYRANK leaderboard 0 1
ZREVRANGE leaderboard 0 -1 WITHSCORES
注意:ZREMRANGEBYRANK 使用升序(最低分 = rank 0)。
按分数范围删除
删除所有分数低于 1000 的玩家:
ZREMRANGEBYSCORE leaderboard 0 999
高级模式
时间衰减排行榜
用于"热门"排行榜,近期活动权重更高:
ZADD trending 1739600100 "post:1"
ZADD trending 1739600200 "post:2"
ZADD trending 1739600050 "post:3"
ZREVRANGE trending 0 -1 WITHSCORES
用时间戳作为分数。更新的帖子排名更高。定期清理旧条目:
ZREMRANGEBYSCORE trending 0 1739500000
多条件排名(复合分数)
Redis ZSET 分数是 64 位浮点数。你可以编码多个条件:
score = 主分数 * 1000000 + (MAX_TIMESTAMP - 时间戳)
这样先按主分数排序,相同分数时按时间排序(更早 = 更高)。
ZADD composite 2300999900 "alice"
ZADD composite 2300999800 "bob"
ZADD composite 1800999950 "charlie"
ZREVRANGE composite 0 -1 WITHSCORES
Alice 和 Bob 主分数相同(2300),但 Alice 排名更高,因为她更早达到。
周/月排行榜
用带时间周期的 Key 命名:
ZADD leaderboard:2026:w07 500 "alice"
ZADD leaderboard:2026:w07 300 "bob"
ZINCRBY leaderboard:2026:w07 200 "alice"
EXPIRE leaderboard:2026:w07 604800
设置 TTL 自动清理旧排行榜。
合并聚合排行榜
将周排行榜合并为月视图:
ZADD week1 100 "alice" 200 "bob"
ZADD week2 150 "alice" 100 "bob"
ZUNIONSTORE monthly 2 week1 week2 AGGREGATE SUM
ZREVRANGE monthly 0 -1 WITHSCORES
常见坑点
- 分数精度:ZSET 分数是 IEEE 754 双精度浮点数。2^53 以内的整数是精确的。超过这个范围会丢失精度。
- 大 ZSET:百万级成员的 ZSET 运行正常,但对它执行
ZRANGE 0 -1会阻塞 Redis。务必分页。 - 并列处理:没有复合分数时,相同分数的玩家排序是未定义的。Redis 对相同分数按字典序排序,这可能不是你想要的。
- 原子更新:如果需要同时更新分数和元数据,使用事务或 Lua 脚本。分开的 ZADD + HSET 可能导致不一致状态。
在线编辑器试一试
前往 Redis 在线编辑器 构建排行榜:
ZADD leaderboard 1500 "alice" 2300 "bob" 1800 "charlie" 2100 "diana" 1950 "eve"
ZREVRANGE leaderboard 0 -1 WITHSCORES
ZREVRANK leaderboard "charlie"
ZINCRBY leaderboard 500 "charlie"
ZREVRANGE leaderboard 0 2 WITHSCORES
ZREM leaderboard "eve"
ZCARD leaderboard
观察 Charlie 在分数增加后是怎么爬升排名的。这就是零重排序开销的实时排名。