caffeine 本身不能防止缓存穿透,因其对不存在的 key 无感知且不拦截请求;真正防线是布隆过滤器、空值缓存等前置策略,caffeine 仅作为二级缓存加速已确认存在的热点数据。

本地缓存(Caffeine)不能单独“防止”穿透,但能显著降低 Redis 层被无效请求击穿的概率——关键在于它必须和布隆过滤器、空值缓存等策略配合使用,否则只是把压力从 Redis 转移到了本地内存和数据库。
为什么 Caffeine 本身不防穿透
Caffeine 是纯内存、进程内缓存,对“不存在的 key”完全无感知:它既不会拦截非法请求,也不会主动拒绝查询。如果一个恶意请求查 user:999999999(根本不存在),Caffeine 查不到就直接往下走,照样打到 Redis → 再打到数据库。它只在“存在且热点”的数据上起加速作用。
- 穿透请求不会命中 Caffeine,所以它不参与拦截逻辑
- 若错误地把所有缓存未命中都 fallback 到数据库,Caffeine 反而会放大穿透风险(比如多个实例各自加载失败再各自重试)
- Caffeine 的
maximumSize和expireAfterWrite对穿透零防护能力
Caffeine + Redis 多级结构中真正的穿透防线在哪
穿透防御必须前置,Caffeine 在这个架构里是“第二道缓冲”,不是第一道闸机。真正承担拦截任务的是:
Redis 缓存和数据结构管理技能。通过自然语言操作 Redis,支持 String、Hash、List、Set、ZSet、Stream 等数据结构操作。当用户提到 Redis、缓存、消息队列、会话存储时使用此技能。
-
BloomFilter:部署在网关或服务入口,用位数组快速判断product:12345是否可能存在于全量 ID 集合中;误判率可控(如 0.1%),但绝不会漏判 -
Redis SET或HyperLogLog预热集合:用于校验合法 ID 范围(适合 ID 规则明确的场景,如自增主键) -
空值缓存:Redis 中对user:999999999返回null后,仍写入cache:user:999999999 → "NULL"并设 TTL=30s,避免重复穿透
只有这些前置策略生效后,Caffeine 才能安心做它的本职工作:把那些“已确认存在、且高频访问”的数据(如 user:1001)稳稳接住,减少 Redis 查询频次。
Caffeine 的正确接入姿势:不雪上加霜,只锦上添花
在穿透防护链路中,Caffeine 的价值是“降低有效请求的延迟”,而非“过滤无效请求”。配置和使用时需注意:
- 禁用
refreshAfterWrite:它会在后台异步 reload,若数据不存在,reload 会反复触发数据库查询 - 设置短过期(如
expireAfterWrite(5, TimeUnit.SECONDS)):避免本地缓存长期保留陈旧空值或错误状态 - 不开启
recordStats()生产环境:统计开销在高并发下明显,穿透场景下更易成为瓶颈 - 与 Redis 缓存 key 保持语义一致:比如 Redis 存
user:1001,Caffeine 也用相同 key,避免双写不一致导致本地缓存“永远不更新”
容易被忽略的协同细节
两级缓存不是简单叠加,失效同步才是难点:
- 当数据库更新
user:1001,必须同步invalidate所有节点的 Caffeine 缓存 —— 不能只删 Redis;否则其他节点还在用旧本地值 - 推荐用 Redis Pub/Sub 发布
cache:evict:user:1001消息,各节点监听后调用caffeineCache.invalidate("user:1001") - 布隆过滤器的容量要预留余量:若商品总数 1 亿,
ScalableBloomFilter初始容量设为 1.2 亿,否则扩容时误判率飙升 - 空值缓存的 TTL 必须远小于正常数据 TTL(如 30s vs 2h),否则攻击者可批量刷空值占满 Redis 内存










