不能只靠set+nx+ex解决击穿,因其仅在key不存在时生效,而击穿发生在key已过期(存在但失效)时,nx不触发,多个请求仍并发回源;必须用lua脚本原子封装“查缓存→加锁→查库→写缓存→释放锁”全流程。

Redis 7 本身没有提供“自动防热key击穿”的内置函数,所谓“优雅处理”必须靠组合使用 SET、GET、SETNX(或 SET 的 NX 选项)、EXPIRE 和 Lua 脚本能力,再配合客户端逻辑完成。直接依赖单个新函数是行不通的。
为什么不能只靠 SET + NX + EX 就解决击穿?
很多人以为用 SET key value NX EX 60 就能原子性地“加锁+设值+过期”,但这是误解:它只在 key 不存在时写入,而缓存击穿场景下,key 是存在但已过期(或刚被删),此时 NX 不生效,多个请求会同时穿透到 DB。
- 真正需要的是“尝试加锁 → 查 DB → 写缓存 → 释放锁”,三步缺一不可
-
SETNX或SET ... NX只负责第一步,后续必须由业务代码控制 - Redis 7 没有新增类似
GETORSETIFMISSING这种语义的原生命令
用 Lua 脚本封装“查缓存 → 加锁 → 回源 → 写缓存”原子链路
Redis 7 完全兼容 Lua,且支持 EVAL 和 EVALSHA,这是目前最可控、无竞态的方式。关键点不是“多炫酷”,而是让整个判断+写入过程不被中断:
if redis.call("exists", KEYS[1]) == 1 then
return redis.call("get", KEYS[1])
else
if redis.call("set", KEYS[2], "1", "nx", "ex", 5) == 1 then
local data = ARGV[1] -- 假设已从 DB 查询好
redis.call("setex", KEYS[1], 300, data)
redis.call("del", KEYS[2])
return data
else
-- 等待或返回空,由客户端决定重试策略
return nil
end
end
-
KEYS[1]是业务 key(如shop:1001),KEYS[2]是锁 key(如lock:shop:1001) - 脚本内两次
redis.call是原子执行的,不会被其他客户端打断 - 注意:Lua 中无法主动调用 DB,所以
ARGV[1]必须由客户端提前查好传入,或改用“返回锁失败 → 客户端回源 → 再调用写缓存脚本”两段式
别忽略 Redis 7 的 MEMORY USAGE 和 OBJECT FREQ 对热key定位的实际价值
击穿常和热key共存,而 Redis 7 的 OBJECT FREQ(需开启 maxmemory-policy allkeys-lfu)能真实反映 key 的访问热度,比旧版 hotkeys 更准:
- 执行
OBJECT FREQ shop:1001返回 0~255 的频次分值,值越高说明近期越热 - 配合
MEMORY USAGE shop:1001能快速识别“又大又热”的危险 key(比如含 800 字段的 Hash) - 这两条命令本身不阻塞,可安全集成进监控 agent,每分钟扫一次 top 20 key
- 发现后立刻触发预案:比如对
shop:1001自动拆成shop:1001:base、shop:1001:detail等子 key,而非硬扛
真正的难点从来不在“怎么写一行命令”,而在于:锁超时时间是否覆盖 DB 查询毛刺、本地缓存与 Redis 缓存 TTL 是否错位、大 key 拆分后一致性如何保障——这些细节一旦漏掉,再“优雅”的函数调用也挡不住雪崩。











