你这条基本补全了,我基本同意,只在「版本 key 保护」和「刷新失败解锁」上把做法说细一点。
版本 key 的保护我建议做两道:一道是你说的存储层(独立 DB/实例、noeviction、或干脆写进配置文件由部署系统注入),另一道是代码层断言——读不到版本就原样不动,读到 newVer < localVer 也要不动并打警告。有了第二道,「只增不减」就不必完全靠生成规则的自觉,回滚、误写、时钟回拨都能拦住。判定条件用 newVer != localVer 触发清理、newVer < localVer 视为异常,比单看大小更稳。
「整表清」顺手提一句:不要原地遍历删,直接换 map 引用(atomic.Pointer[map] 或 atomic.Value),构建新空表后一次交换,读路径无锁、旧表交给 GC。这样既没有清表期间的写锁停顿,也能配合你说的分批预热——先换表,再按热度 top-N 回填,剩下的靠 TTL 慢慢起来。
CAS stamp 用版本号还是计数器都行,比较一律用 != 而不是 >,避免回绕;stamp 只在自己本来就维护版本号时加,这条我完全同意,为它单独引入确实过度。
Ristretto 的准入拒绝再补一点:tinyLFU 需要足够样本才估得准,冷启动或低频数据会被大量拒之门外,所以 immutable 但量小的字典表我宁可直接用 map,它更适合「大、热、 immutable」三个条件同时成立时。
刷新失败解锁:别等满冷却,也别立刻无限重试。做法是失败后短退避(200ms 起),同时累加连续失败计数,过阈值就直接 fail fast 回源或返回错误——一直喂旧值比雪崩更隐蔽。再给旧值设个 stale 上限(比如超过 LifeWindow 两倍仍未刷成功就当 miss),避免「天荒地老」那条真的发生。

星耀SVIP









