必须用refreshAfterWrite而非expireAfterWrite,因其在过期后异步刷新并返回旧值,避免高并发下多个线程同时穿透加载;而expireAfterWrite会导致entry失效后所有请求同步阻塞触发load,引发雪崩式并发洪峰。

本地缓存能直接绕过 Redis 单线程和网络 IO,把热 Key 的 QPS 压力从单节点 CPU/网卡转移到应用进程 CPU,实现横向分摊——但没配对 refreshAfterWrite 和异步加载,就等于开着门放洪峰进内存。
为什么 Caffeine/Guava 本地缓存必须用 refreshAfterWrite 而不是 expireAfterWrite
用 expireAfterWrite 会导致大量请求在过期瞬间同时穿透到 Redis 或 DB,形成“雪崩式并发洪峰”。而 refreshAfterWrite 在后台异步刷新,命中时仍返回旧值,天然平滑。
-
expireAfterWrite(2, TimeUnit.SECONDS):2 秒后整个 entry 失效,下一次 get 必然触发加载,高并发下多个线程会同时进入 load 方法 -
refreshAfterWrite(2, TimeUnit.SECONDS):2 秒后触发异步刷新,get 仍返回旧值,避免穿透 - 必须搭配
AsyncLoadingCache使用,否则CacheLoader同步阻塞会耗尽线程池 - value 中建议自带逻辑过期时间戳(如
data: {...}, expiresAt: 1748383200000),不依赖缓存物理过期
本地缓存 + Redis 多副本的读写一致性怎么保
本地缓存和 Redis 多副本之间没有自动同步机制,靠写操作主动广播通知。一旦漏掉清理或刷新,各实例本地缓存就会长期不一致。
- 写入时必须同步更新所有副本 key:
hot:news:202、hot:news:202:replica1、hot:news:202:replica2 - 每个副本 key 都要带 TTL(如
SETEX hot:news:202:replica1 60 "value"),不能永不过期;否则主 key 过期后副本变成脏数据源 - 本地缓存失效需通过 Pub/Sub 或消息队列广播,不能只清本机;例如发
PUBLISH cache:invalidate hot:news:202,各节点订阅后调用localCache.invalidate("hot:news:202") - 禁止用
SETNX lock:hot:news:202 1 EX 5以外的方式做更新锁,否则多实例并发写入可能写入不同旧值
redis-cli --hotkeys 为什么总返回空,以及它到底能信多少
redis-cli --hotkeys 不是实时采样,而是读取 Redis 内置 LFU 计数器快照,必须满足两个硬条件才有效——缺一不可。
- Redis 配置项
maxmemory-policy必须设为volatile-lfu或allkeys-lfu,其他策略(如allkeys-lru)下该命令无输出 - LFU 计数器有衰减机制,默认每分钟衰减一次,低频但突发的热点(如明星官宣前 30 秒)可能来不及累积计数就被衰减掉
- 它只反映“过去一段时间内访问最频繁的 key”,不区分读/写、不带时间窗口,无法定位秒级突增型热点
- 真正要监控突增型热点,得结合
MONITOR+ 日志流分析,或用京东 HotKey、美团 Redis-Faina 这类带滑动窗口的工具
本地缓存不是加个 Caffeine.newBuilder().build() 就完事——过期策略、刷新方式、跨实例通知、副本 TTL,四者缺一,缓存就从减压阀变成定时炸弹。尤其当多个服务实例共享同一份热数据时,逻辑过期+异步刷新+广播失效这三步,一步都不能跳。


















