缓存击穿的本质是热点 key 过期瞬间大量请求击穿至数据库,需结合分布式锁(如 SETNX+Lua)与逻辑过期机制解决;锁应按 key 哈希分片、及时清理,避免串行化和内存泄漏。

缓存击穿的本质是:某个热点 key 过期瞬间,大量并发请求同时打到数据库,造成瞬时压力飙升。Java 中单纯用 Redis + get/set 无法解决,必须配合线程同步机制和合理的过期策略。
用 synchronized 锁住 key 粒度的重建逻辑
直接锁整个方法或类会导致吞吐量骤降;应缩小锁范围,按 key 计算哈希后映射到固定数量的锁对象上,避免不同 key 互相阻塞。
- 不要用
synchronized(this)或synchronized(Service.class),会串行化所有请求 - 推荐用
ConcurrentHashMap预热锁池:private static final Map<string object> lockMap = new ConcurrentHashMap();</string> - 获取锁时:
Object lock = lockMap.computeIfAbsent(key, k -> new Object()); synchronized(lock) { ... } - 重建完成后记得
lockMap.remove(key),防止内存泄漏(尤其 key 量大时)
用 Redis 的 SETNX + 过期时间实现分布式锁
单机 synchronized 在集群环境下失效,必须升级为分布式锁。但别手写 SETNX + DEL 组合——存在锁误删风险。
- 务必使用原子命令:
SET key value NX PX 30000(value 建议用 UUID 防误删) - 解锁不能简单
DEL,要用 Lua 脚本比对 value 再删:if redis.call("get",KEYS[1]) == ARGV[1] then return redis.call("del",KEYS[1]) else return 0 end - 业务逻辑执行超时要设置合理重试或降级,否则锁一直不释放
- 注意 Redis 主从异步复制可能导致锁丢失,生产环境建议用
Redlock或Redisson的看门狗机制
加互斥锁时必须配合逻辑过期(不是 Redis TTL)
只靠锁解决“重建期间其他请求等待”问题,但没解决“重建失败后反复冲击 DB”的隐患。必须引入本地逻辑过期时间。
立即学习“Java免费学习笔记(深入)”;
- 从 DB 查出数据后,封装成
CacheData<T>,含data和expireTime字段(比如System.currentTimeMillis() + 2 * 60 * 1000) - 写入 Redis 时用永久 TTL(如
SET key value EX 999999999),实际过期由代码判断expireTime < System.currentTimeMillis() - 若发现逻辑过期,才去抢锁重建;否则直接返回旧值(允许短暂脏读,但保 DB 安全)
- 这样即使锁失效或 DB 查询失败,也不会导致后续请求全部穿透
真正难的不是加锁动作本身,而是锁的粒度、生命周期、与业务超时的配合。一个没清理的锁对象、一次没校验的 value、一段没兜底的逻辑过期判断,都可能让缓存击穿在高并发下重现。

















