@Cacheable(sync=true)通过为相同key加本地同步锁,确保仅一个线程执行数据库查询并写入缓存,其余线程等待复用结果,从而防止缓存击穿;但仅限单机场景,集群需Redisson等分布式锁支持。

直接结论:用 Redisson 的 @Cacheable(sync = true) 是最省事且生产可用的方案,前提是已接入 Redisson 并正确配置 RedissonSpringCacheManager。
为什么 @Cacheable 默认会触发缓存击穿?
Spring Boot 原生的 @Cacheable 在缓存 miss 时,所有并发线程都会执行被注解的方法——比如查数据库。它不加锁、不协调,只做“缓存存在就返回,不存在就执行并写入”。
典型错误现象:50 个并发请求同一个刚过期的 key,日志里出现 50 条相同 SQL 执行记录,数据库连接数瞬间拉满。
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
- 不是 bug,是设计使然:它假设业务层自己处理并发控制
- 单机场景下可用
synchronized或ReentrantLock,但集群环境完全失效 -
CacheManager默认实现(如ConcurrentMapCacheManager)根本不支持跨 JVM 同步
如何让 @Cacheable 自动加分布式锁?
关键在替换默认缓存管理器为 RedissonSpringCacheManager,并启用 sync = true 参数。
- 必须添加依赖:
redisson-spring-boot-starter(版本建议3.27.2及以上) - 手动注入
RedissonClient,否则自动配置可能读错配置项(spring.redisvsspring.data.redis) - 方法上写法必须是:
@Cacheable(value = "user", key = "#id", sync = true)——漏掉sync = true就等于没加锁 - 底层靠 Redisson 的
RLock+ Lua 脚本实现原子加锁,失败线程会阻塞等待,而非重试或穿透
容易被忽略的三个坑
很多项目集成后仍出现击穿,问题往往出在这些细节:
-
sync = true只对Cacheable生效,@CachePut和@CacheEvict不参与锁机制 - 锁的 key 是由 cacheName + key 拼接生成的,如果多个方法共用同一个
value = "user"但参数结构不同(比如#idvs#email),可能意外竞争同一把锁 - Redisson 的看门狗默认 30 秒续期,如果数据库查询耗时超过这个值(比如慢 SQL 或网络抖动),锁可能提前释放,导致第二个线程再次击穿——务必监控
getById类方法的 P99 耗时,必要时调大lockWatchdogTimeout
真正要稳住的,不是加锁本身,而是锁的生命周期必须严丝合缝地覆盖住整个“查库 + 写缓存”过程。任何一环超时,都可能让防护形同虚设。

















