zincrby 原子累加防覆盖,zrevrange 降序取榜,同分需用时间戳编码 score 确保稳定排序,禁用二级 zset 和尾部索引。

用 ZINCRBY 累加分数,别手写 ZADD 覆盖
直接用 ZADD 每次覆盖分数,会导致“同用户多次提交”时丢失历史行为——比如用户刷题两次各得5分,你 ZADD scores 5 user1 两次,最终只记了5分,不是10分。ZINCRBY 才是正确姿势:它原子性累加,天然防并发覆盖。
-
ZINCRBY scores 5 user1→ 分数变成5 - 再执行一次 → 分数变成10(不是又被设成5)
- 如果用户初始没分,
ZINCRBY会自动初始化为0再加,无需预置
ZREVRANGE 和 ZRANGE 别搞反顺序
排行榜默认要“分数高者在前”,也就是降序。但 Redis 的 ZRANGE 是按 score 升序返回(最小→最大),而 ZREVRANGE 才是降序(最大→最小)。查前十名必须用 ZREVRANGE,否则拿到的是垫底的10个。
-
ZREVRANGE scores 0 9 WITHSCORES→ 正确:第1到第10名 -
ZRANGE scores 0 9 WITHSCORES→ 错误:最低分的10个(可能全是0分新用户) - 索引从0开始,
0 9就是10个元素,不是11个
同分情况怎么稳定排序?别信“插入顺序”
Redis ZSet 对同分元素不保证插入顺序,官方明确说明:同分时内部按字典序排 member 字符串。所以 ZADD scores 100 user001 和 ZADD scores 100 user999,谁排前面取决于字符串大小,不是时间先后。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 真要“同分时先到先得”,得把时间戳揉进 score:比如
score = 原始分 * 1e13 + (1e13 - timestamp) - 这样同分时,timestamp 小(更早)的组合分更大,
ZREVRANGE自然靠前 - 别用二级 ZSet(如
leaderboard:score:100),查一次要两轮网络+两次命令,延迟翻倍还难维护
查前十别用 ZCARD + ZRANGE 算偏移
有人想“总数减10再取”,写 ZCARD scores 得到 10000,然后 ZRANGE scores 9990 9999 ——这逻辑错在方向反了,而且极慢。ZSet 底层是跳表,ZRANGE 从头开始遍历,取最后10个要扫近万节点。
-
ZREVRANGE scores 0 9是 O(log N + M),M=10,毫秒级 -
ZRANGE scores 9990 9999是 O(N),N=10000,可能几十毫秒甚至超时 - 永远优先用
ZREVRANGE直接索引前段,而不是算尾段
真正容易被忽略的是同分排序的隐式依赖——很多业务上线半年才发现,两个同分用户排名每天来回跳。这不是 Redis 的 bug,是没把时间维度显式编码进 score。一旦用了组合分,后续所有读写都得对齐这个格式,改起来比加字段成本高得多。










