网络宝典
第二套高阶模板 · 更大气的阅读体验

Guava Cache缓存失效策略怎么选?几个实用参考场景

发布时间:2026-04-25 06:31:41 阅读:5 次

做后端开发时,缓存用得顺不顺手,关键看失效策略设得对不对。Guava Cache 是 Java 里轻量又靠谱的选择,但它不像 Redis 那样自带一堆淘汰命令,它的失效逻辑全靠配置——选错一个参数,可能缓存一直不刷新,或者刚放进去就没了。

三种常见失效方式,别混着用

Guava Cache 支持三种主要失效机制:基于时间、基于大小、基于引用。实际项目里,多数人只配一种,但有时得组合着来。

1. 过期时间(expireAfterWrite)

最常用。比如查用户资料,5 分钟内允许旧数据,之后必须重新加载:

Cache<String, User> userCache = Caches.newBuilder()
    .expireAfterWrite(5, TimeUnit.MINUTES)
    .build(key -> loadUserFromDB(key));

注意:这个“5 分钟”是从写入或刷新那一刻开始算的,不是从首次访问起。如果某条数据被反复 get,但没 put 或 refresh,它照样过期。

2. 访问过期(expireAfterAccess)

适合“热数据保活、冷数据自动清理”的场景。比如商品详情页缓存,用户频繁刷同一个商品,就一直留着;隔半天没人点,就清掉:

Cache<Long, Product> productCache = Caches.newBuilder()
    .expireAfterAccess(30, TimeUnit.MINUTES)
    .build(key -> loadProduct(key));

小心坑:如果某个 key 被反复 get(比如监控脚本轮询),它永远不会过期,容易占满内存。

3. 容量限制(maximumSize)

硬性兜底。哪怕时间没到、也没人访问,缓存太多也得踢掉老的:

Cache<String, String> configCache = Caches.newBuilder()
    .maximumSize(1000)
    .expireAfterWrite(1, TimeUnit.HOURS)
    .build(key -> loadConfig(key));

Guava 默认用 LRU 策略淘汰,也就是最近最少使用的那个先走。如果你的业务有明显冷热区分,这个很管用。

刷新比失效更温柔

有时候你不想让请求卡在重建缓存那一下,可以用 refreshAfterWrite

Cache<String, List<Order>> orderCache = Caches.newBuilder()
    .refreshAfterWrite(10, TimeUnit.MINUTES)
    .build(key -> loadRecentOrders(key));

效果是:10 分钟后,下次 get 会异步触发 reload,当前请求仍返回旧值。用户体验更平滑,但要注意 reload 方法不能阻塞太久,否则线程池可能被拖垮。

真实踩过的坑

有次上线后发现用户头像总显示旧图,排查发现用了 expireAfterAccess,但头像接口被前端轮询调用(每 3 秒一次),导致永远不过期;后来改成 expireAfterWrite + 主动触发 refresh,问题立马解决。

还有一次缓存暴涨,是因为把 maximumSize 设成了 0 —— Guava 里 0 表示不限大小,结果 OOM 报警半夜响了三次。

一句话参考口诀

查库慢、更新少 → 用 expireAfterWrite;
数据有热度差异 → 加 maximumSize;
不能接受空转等待 → 上 refreshAfterWrite;
别同时开 expireAfterAccess 和 refreshAfterWrite,逻辑容易打架。