热点缓存一到期,所有请求一起回源很容易把依赖压满。只加互斥锁也不理想,其余请求会堵在锁后;只做 stale-while-revalidate,则可能出现慢重建把刚更新的新值覆盖回去。
一个比较小的做法是同时保存 soft_expire_at、hard_expire_at 和单调递增的 generation。软过期后,抢到短租约的请求负责重建,其他请求继续返回旧值;硬过期后不再返回旧值,只允许短暂等待一次重建,超时就明确降级。
entry = cache.get(key)
if now < entry.soft_expire_at: return entry.value
if now < entry.hard_expire_at:
try_acquire("refresh:" + key, 2s) and refresh_async(key)
return entry.value
return wait_refresh_or_fail(key, 200ms)
版本门禁放在重建写回处。重建开始时读取当前 generation,加载数据后再读一次;两次不相等就丢弃本次结果。业务更新时先递增 generation,再让缓存进入软过期。这样一次较慢的旧重建即使最后完成,也不能覆盖更新后的内容。锁只负责减少重复回源,版本号才负责保证写回顺序。
验收可以把源站延迟固定为 800ms,在软过期瞬间并发打入 200 个请求:回源次数应接近 1,其余请求延迟接近缓存命中。再在重建中途更新源数据并递增版本,确认旧结果没有写回。最后单测硬过期分支,确保系统不会无限返回陈旧值,也不会让等待请求无上限堆积。