缓存雪崩本质是大量key集中过期导致请求穿透,防范需错开过期时间、分层兜底与快速降级:redis加随机ttl偏移,caffeine用expireafteraccess+refreshafterwrite,空值缓存须同步写入且短时过期。

缓存雪崩的本质是过期时间撞车,不是缓存本身的问题
缓存雪崩不是 Caffeine 或 Redis 的缺陷,而是大量 key 在同一时间点集中过期,导致请求穿透到数据库。Java 17 下用 Caffeine + Redis 防雪崩,关键不在“加缓存”,而在“错开过期、分层兜底、快速降级”。单纯设置统一 TTL 是最常见也最危险的做法。
必须给 Redis key 加随机过期偏移,Caffeine 不能照搬 Redis 的 TTL
Redis 层若所有 key 都设 expireAfterWrite=10m,哪怕只差几毫秒,高并发下仍可能批量失效。Caffeine 层更不能直接复用这个值——本地缓存的生命周期应比 Redis 更短,且需引入访问驱动的刷新机制。
- Redis 写入时用
ops.set(key, value, Duration.ofMinutes(10).plusSeconds(ThreadLocalRandom.current().nextLong(60, 300))),让过期时间在 10~10.5 分钟间随机浮动 - Caffeine 不要设固定
expireAfterWrite,改用expireAfterAccess(如expireAfterAccess=5m),配合refreshAfterWrite=3m实现后台异步刷新 - 避免在
Caffeine.newBuilder()中写死maximumSize,改用maximumWeight+weigher,防止大对象挤爆堆内存
@Cacheable 注解无法直接支持多级,必须手写 Cache 接口实现
Spring 的 @Cacheable 默认只绑定单个 CacheManager,没法自动走“Caffeine → Redis → DB”链路。强行包装成一个 Cache 实例是唯一可靠路径,否则你写的“多级”只是逻辑上多级,运行时仍是单级调用。
- 定义
DoubleCache类,实现org.springframework.cache.Cache接口,重写get方法:先查caffeineCache.getIfPresent(key),未命中则调redisTemplate.opsForValue().get(key),查到后同步写入caffeineCache.put(key, value) - 写操作必须双写:
caffeineCache.invalidate(key)+redisTemplate.delete(key),顺序不能反;若用set而非delete,需确保 Redis 写成功后再清本地缓存,否则出现短暂不一致 - 不要在
get里嵌套数据库查询——那是 Cache-Aside 模式该干的事,DoubleCache只负责两级缓存协同,DB 查询应交给业务方法自己处理
空值缓存和布隆过滤器必须前置,不能等雪崩发生再补
雪崩常由穿透引发:大量无效 key 请求击穿两级缓存直奔 DB。Java 17 中 RedisBloom 客户端已稳定支持布隆过滤器,但很多人漏掉最关键一步——空值必须同时写入 Caffeine 和 Redis,且过期时间要明显短于正常数据(比如 2 分钟),否则空值长期驻留会污染本地缓存命中率。
- 查 Redis 返回
null时,立刻执行:caffeineCache.put(key, Optional.empty(), Expiry.afterWrite(2, TimeUnit.MINUTES)) - 布隆过滤器校验失败(
bloom.contains(key) == false)直接返回 404,不进任何缓存逻辑 - Redis 中空值 key 命名建议加前缀
empty:,比如empty:user:999999,便于监控和清理
最容易被忽略的是 Caffeine 的 Expiry 策略必须显式指定,它不会自动继承 Redis 的过期配置;而一旦空值在本地缓存里存太久,后续所有同 key 请求都会被拦截,连布隆过滤器都绕不过去。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











