zincrby是实时更新排行榜分数的正确入口,因其原子性执行“读取当前分+加增量+写回”,避免并发写覆盖;zrevrange用于获取最高分在前的top榜单,需带withscores参数;防重复点赞须用lua脚本原子判断zscore后incrby;空榜单需设空标记防穿透。

ZINCRBY 是实时更新排行榜分数的正确入口,不是 ZADD 或手动 ZSCORE + ZADD 组合。
为什么必须用 ZINCRBY 更新分数
ZINCRBY 原子性地完成「读取当前分 + 加增量 + 写回」三步,避免并发写覆盖。
常见错误是先 ZSCORE 判断再 ZADD,中间存在竞态窗口:两个请求同时读到 0 分,都写入 1,结果只加了 1 而非预期的 2。
-
ZINCRBY的增量可以是正数、负数甚至小数(如权重衰减),但注意浮点精度问题:Redis 内部用 double 存 score,0.1 + 0.2可能存为0.30000000000000004 - 若业务要求整型计数(如点赞数),传整型增量并确保
member是字符串(如'123'),否则 PHP 自动类型转换可能截断或误判 - 不要对同一
member频繁调用ZINCRBY(比如每秒数百次),会引发写放大;高频场景建议先在内存聚合,定时批量写入
ZREVRANGE 和 ZRANGE 到底该用哪个
排行榜默认要“最高分在前”,必须用 ZREVRANGE。ZRANGE 是升序(score 小 → 大),返回的是垫底的几条,和业务预期相反。
- 获取 Top 10:用
$redis->zRevRange('rank:article', 0, 9, ['WITHSCORES' => true]) - 返回格式是关联数组:
['123' => '456', '456' => '321'],键是member(如文章 ID),值是当前score - 别漏掉
WITHSCORES参数,否则前端还得挨个查分,多出 N 次 Redis 请求 - 如果要带排名序号(第 1 名、第 2 名),PHP 层用
array_keys()+array_values()构造即可,Redis 不提供原生命令返回带 rank 的结构
并发去重点赞怎么不丢分也不重复
ZINCRBY 本身不防重复,它只是累加。同一个用户对同一篇文章多次点击,必须只加一次分。
错误做法:PHP 层先
ZSCORE判断是否存在,再决定是否ZINCRBY—— 中间有竞态
Redis Skill - 高性能缓存管理下载Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
-
正确做法:用 Lua 脚本原子执行判断与加分
示例脚本:if redis.call("zscore", KEYS[1], ARGV[1]) == false then return redis.call("zincrby", KEYS[1], 1, ARGV[1]) else return 0 end调用:$redis->eval($script, ['rank:article'], [$articleId])返回0表示已存在,非0是新总分 注意:Lua 脚本里不能用
redis.call("exists", ...)替代zscore == false,因为zscore返回false才代表 member 不存在,exists对 zset 永远返回 1
空榜单和缓存穿透怎么兜底
ZREVRANGE 查不到数据时返回空数组,不代表没数据,可能是真没人上榜,也可能是恶意刷不存在的 ID。
- 业务层不能直接把空数组当“无数据”渲染,得区分真假空
简单策略:查完为空,立刻写一个带过期时间的空标记,如empty:rank:article:999,TTL 设为 60 秒 - 标记值不能设成
""或0,因为它们可能是合法的 score 值,无法分辨 - 下次再查这个 ID,先
EXISTS empty:rank:article:999,命中就跳过 Redis 查询,直接返回空
真正的难点不在实现,而在识别哪些 key 是“合理不存在”——比如刚发布还没人互动的文章 ID,和攻击者构造的随机大数 ID,需要结合业务 ID 规则或布隆过滤器预筛。
php免费学习视频:立即使用
踏上前端学习之旅,开启通往精通之路!从前端基础到项目实战,循序渐进,一步一个脚印,迈向巅峰!










