缓存策略与一致性
缓存以更低成本换取更快读取,但也引入“数据何时更新”的问题。没有读瓶颈或明确收益时,不要先加缓存;先让查询、索引和接口边界正确。
明确缓存的对象和目标
| 缓存对象 | 常见目标 | 需要格外注意 |
|---|---|---|
| 配置、字典数据 | 降低重复读取 | 更新频率与版本 |
| 热门详情、列表 | 降低数据库读压力 | 分页、权限、失效范围 |
| 会话、临时令牌 | 快速验证身份 | 过期与撤销 |
| 计算结果 | 避免重复计算 | 输入变化与结果有效期 |
缓存键应包含真正影响结果的维度,例如语言、租户、权限版本和筛选条件。缺少维度会造成数据串读;维度过多则命中率很低。
缓存旁路是常见起点
读取时先查缓存,未命中再查主数据源并回填;更新时先写主数据源,再删除或更新缓存。
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。
一致性是业务选择
库存、余额、权限等强敏感数据,不能因为缓存快就接受陈旧值;推荐内容、统计数字等可根据业务容忍度使用短暂最终一致。把“允许旧多久、错了怎么办”写成规则,缓存才不会成为隐形风险。