Skip to content

缓存策略与一致性

缓存以更低成本换取更快读取,但也引入“数据何时更新”的问题。没有读瓶颈或明确收益时,不要先加缓存;先让查询、索引和接口边界正确。

明确缓存的对象和目标

缓存对象常见目标需要格外注意
配置、字典数据降低重复读取更新频率与版本
热门详情、列表降低数据库读压力分页、权限、失效范围
会话、临时令牌快速验证身份过期与撤销
计算结果避免重复计算输入变化与结果有效期

缓存键应包含真正影响结果的维度,例如语言、租户、权限版本和筛选条件。缺少维度会造成数据串读;维度过多则命中率很低。

缓存旁路是常见起点

读取时先查缓存,未命中再查主数据源并回填;更新时先写主数据源,再删除或更新缓存。

text
读取:cache → miss → database → cache
更新:database → invalidate cache

“删除缓存”通常比直接更新更简单,因为复杂对象的局部更新容易遗漏字段。但删除与下次读取之间仍可能发生并发竞态,需要根据重要性评估是否接受短暂旧值。

为异常情况提前设计

  • 缓存穿透:反复查询不存在的数据。可缓存短期空值,并对异常输入限流。
  • 缓存击穿:热点键刚失效时大量请求同时回源。使用互斥、请求合并或预热。
  • 缓存雪崩:大量键同一时间失效。给 TTL 加随机扰动,并准备回源保护。
  • 缓存不可用:服务必须能降级到主数据源,或明确拒绝非关键请求。

常见错误:缓存键缺少维度导致串读

缓存键没把所有影响结果的维度都包含进去,是缓存最危险的问题——不会报错,只会悄悄返回错误数据:

text
// 多租户系统,key 只用了文章 id
key: "article:18"  → 存的是 A 租户、中文版的内容

症状:B 租户的用户打开了同一篇文章,看到的却是 A 租户的内容;英文用户看到中文;普通用户看到了管理员才能看的版本。这类 bug 极难发现——因为数据"看起来正常",只是张冠李戴,往往要等用户投诉"看到了别人的数据"才暴露。

为什么错:缓存键是命中缓存的唯一依据。只要 key 相同,就返回同一份缓存,不管请求实际来自哪个租户、哪种语言、什么权限。漏掉一个维度,不同输入就会命中同一份结果。

正确做法:key 必须包含所有影响结果的维度:

text
key: "article:18:tenant:B:lang:en:perm:v2"
// 文章 id + 租户 + 语言 + 权限版本

拿不准要不要加某个维度时,问一句:"这个维度的不同值,会产生不同的结果吗?" 会,就必须进 key。

一致性是业务选择

库存、余额、权限等强敏感数据,不能因为缓存快就接受陈旧值;推荐内容、统计数字等可根据业务容忍度使用短暂最终一致。把“允许旧多久、错了怎么办”写成规则,缓存才不会成为隐形风险。

为复用而记录,为理解而整理。