真正要加锁的热点Key是QPS突增、缓存命中率断崖下跌、过期时间集中的少数几十个key;可通过业务层日志告警(空返回且距过期≤200ms)和实时TTL验证精准定位。

怎么识别出真正要加锁的热点Key
别一上来就给所有 product:10086 这类 key 加锁,Redis 集群里 99% 的 key 根本不值得锁。真正在击穿时造成 DB 压垮的,往往只有几十个——它们有共同特征:QPS 突增 + 缓存命中率断崖下跌 + 过期时间集中。用 redis-cli --stat 只能看到总量,得结合监控打点才准。
推荐两个低成本定位方式:
- 在业务层
cache.Get()后加一行日志:当返回空且当前时间距 key 设置的过期时间 key 和ts - 用 Redis 自带的
MEMORY USAGE+OBJECT IDLETIME组合查:对命中率低但内存占用高的 key,再查它最近 5 分钟的INFO commandstats中cmdstat_get调用量突增情况
注意:不要依赖 KEYS * 扫全量,集群下会阻塞;也别信“访问量 Top 100”这种静态排名,突发热点(如热搜商品)根本不在里面。
SETNX 锁在 Redis 集群里为什么容易失效
直接用 SETNX lock:product:10086 1 EX 10 在集群模式下可能锁不住——因为 key lock:product:10086 和数据 key product:10086 可能落在不同 slot,而 SETNX 不保证原子跨 slot。结果就是:锁设在 slot 2,但数据在 slot 8,别的客户端照样能绕过去读 DB。
必须让锁 key 和数据 key 落在同一个 slot:
- 强制哈希标签:把锁 key 写成
lock:{product:10086},大括号内内容参与 CRC16 计算,确保和product:10086同 slot - 避免用随机字符串做锁 value,要用可识别来源的标识(如
service-a:pid-12345),方便排查谁没释放 - 超时时间别硬写 10 秒,得略大于你 DB 查询 +
SET缓存的 P99 耗时,建议先压测再定
加锁后返回旧值还是等新值,怎么选
这不是纯技术问题,是业务容忍度问题。秒杀商品详情页可以返回逻辑过期的旧值(比如库存显示“99”,实际已售完),但价格页绝对不行。
两种策略的实际取舍点:
- 返回旧值:需确保旧值本身没被其他线程误删(比如后台任务清缓存时没判断锁状态),且旧值过期时间字段得存在并可读(推荐把过期时间存进 value JSON 里,如
{"data":{...},"expire_at":1743870222}) - 等待新值:客户端最多等 300ms,超时直接降级(如返回兜底文案或空结构),别无限制重试——否则线程池会积压,变成雪崩前兆
最危险的是“半等半返回”:部分请求等、部分不等,导致前端渲染不一致,用户刷一次一个样。
预热失败时,为什么监控比加锁还重要
主动预热 product:10086 是防击穿的上策,但如果你的预热 SQL 没走索引,或者预热脚本连错从库,那它不仅没防住击穿,反而先把自己搞挂了。
必须盯死三项指标:
- 预热任务的执行耗时是否稳定(波动 > 200ms 就要告警)
- 预热后该 key 的
TTL是否真的被重置(用TTL product:10086实时验证) - 预热期间 DB 的慢查询日志里,有没有对应
SELECT ... WHERE id = 10086出现
很多团队卡在这儿:预热脚本每天跑,但从没人看日志,直到某天缓存集体过期,才发现半年前预热就静默失败了。


















