首页/数据/Redis 进阶速查/ZSet 排行榜:排名分页与同分处理
数据结构与选型42 条

ZSet 排行榜:排名分页与同分处理

用分数排序实现排行榜:ZREVRANGE 取前 N、ZREVRANK 查个人名次;同分时按成员字典序排列,需要更细的先后顺序时把时间戳编进分数。

Redis 进阶速查数据更新于 2026-10-13

ZSet 排行榜是什么

排行榜的本质是「按分数排序并支持按名次取数」,这正是 ZSet 的能力:成员唯一、分数可重复,内部按分数排序存储,因此取前 N 名、查某人名次、取某人分数都是高效操作。ZREVRANGE 从高到低取区间,ZREVRANK 返回从高到低的排名,ZSCORE 取单一成员的分数。同分时 ZSet 按成员字典序排列,这个默认规则往往与业务期望的「先到先得」不一致。

基本信息

写入ZADD board <score> <member>,分数可整数也可浮点
取榜ZREVRANGE board 0 9 WITHSCORES 取前 10 名
查名次ZREVRANK board member,从 0 开始,越大越靠后
查分数ZSCORE board member,成员不存在返回 nil
复杂度ZADD 与查询为 O(log N),区间取数为 O(log N + M)

典型使用场景

  • 积分榜、销售额榜、点赞榜等按数值排序的实时榜单,写入后立即可见
  • 个人维度查询:查我的名次与前后几名,用 ZREVRANK 加 ZREVRANGE 组合完成
  • 分段榜单:按日、周、月各建一个 ZSet,定时合并或分别展示,避免大 key
  • 带时间维度的排行:把时间戳编进分数小数位,实现同分按提交时间先后排序
  • 榜单数据源同步:离线算好的榜单一次性 ZADD 导入,在线读路径只做取数

常用示例

常用示例
redis-cli ZADD board 1200 u1 980 u2 1500 u3
redis-cli ZREVRANGE board 0 9 WITHSCORES      # 前 10 名与分数
redis-cli ZREVRANK board u2                   # u2 的名次,从 0 开始
redis-cli ZSCORE board u1                     # 单个成员分数
redis-cli ZINCRBY board 50 u2                 # 原子加 50 分
redis-cli ZREVRANGE board 0 9                # 只要成员,不要分数
redis-cli ZCARD board                         # 参与排名的总人数

排序规则与分数设计

需求分数设计说明
按分数降序直接存分数ZREVRANGE 取前 N
同分按时间先到先排分数整数部分放分值,小数部分放反向时间戳越早提交小数越大,排越前
同分按名称排序不加处理默认按成员字典序,中文与数字需确认编码顺序
只保留前 N 名ZADD 后用 ZREMRANGEBYRANK 裁剪控制榜单长度,避免无限增长
复合分数的写法与精度:双精度浮点约 15 到 16 位有效数字,常见做法是分值乘以较大系数后加时间戳的反向小数部分。分值本身超过千万级时小数位会被挤掉,需要先评估量级再决定是否使用复合分数。
分页的一致性:ZREVRANGE 按索引分页在数据持续变动时会出现重复或漏项,例如取第 11 到 20 名期间有人插入前排,原本的第 11 名会被挤到第 12。运营展示类页面可以接受,涉及发放奖励的结算页应改为按分数区间取或先固化快照。

易错点

  • 忽略同分时的字典序默认规则,导致同一分数下顺序看起来随机,需要明确业务语义后再决定是否改用复合分数
  • 把秒级时间戳直接加到大分值上:浮点精度不足会让同分时间差被抹平,反而丢掉了排序依据
  • 榜单不裁剪长度,长期累积成超大 ZSet,取榜与写入都变慢且内存持续上涨,须按业务只保留前 N 名
  • 用 ZSET 存全量历史明细做报表,成员数与内存开销远超需求,明细应落库、Redis 只保留榜单本身

数据来源与参考

  • Redis 官方文档:ZADD / ZREVRANGE / ZREVRANK 命令
​‌‌​​‌​​​‌‌​‌‌‌‌​‌‌​​​‌‌​‌‌‌​​‌‌​​‌​‌‌‌​​‌‌‌​​​‌​‌‌​‌​​‌​‌‌‌​​​‌​‌‌​‌​​‌​‌‌​​​‌‌​‌‌‌​‌​​​‌‌​‌‌‌‌​​‌​‌‌‌​​‌‌​​​‌‌​‌‌​‌‌‌‌​‌‌​‌‌​‌