缓存一致性策略

wen IT资讯 26

从原理到实践,10种核心方案与性能优化指南

目录导读

  1. 缓存一致性问题的本质 – 为什么写入缓存后数据会“变脏”?
  2. 常见缓存模式对比 – Cache Aside / Read-Through / Write-Through 的适用场景
  3. 10种缓存一致性策略详解 – 从简单失效到分布式锁与最终一致性
  4. 高频问答 – 关于缓存雪崩、穿透、击穿的解决方案
  5. 实战建议 – 如何选择适合业务的策略并规避踩坑

缓存一致性问题的本质

当系统同时使用数据库(持久存储)和缓存(高速读取)时,数据在两者之间的更新不同步就会引发不一致。

缓存一致性策略

  • 业务先更新数据库,但缓存失效失败 → 旧数据被后续请求读取
  • 先删除缓存,再更新数据库 → 在删除与更新的间隙,其他线程可能写入旧数据

核心矛盾:缓存追求高吞吐与低延迟,数据库追求持久性与ACID,两者天然存在冲突。


常见缓存模式对比

模式 写操作策略 读操作 适合场景 一致性风险
Cache Aside 先更新数据库,再删除缓存 缓存未命中则查数据库并回写 读多写少 高并发下旧数据回写问题
Read-Through 缓存负责加载数据,写操作透穿到缓存层 从缓存读取,未命中由缓存回源 对应用透明 需处理缓存与数据库双写
Write-Through 写操作同时更新缓存与数据库 同上 要求强一致性 写延迟高,缓存污染

90%的业务选择 Cache Aside + 延迟双删 即可满足要求。


10种缓存一致性策略详解

策略1:Cache Aside(旁路缓存)

  • :先查缓存,未命中则查数据库并回写缓存
  • :先更新数据库,成功后删除缓存
  • 陷阱:并发环境下,线程A删除缓存后,线程B查询旧数据并回写,导致缓存为旧值
  • 解决方案:删除缓存前加分布式锁(如Redis SETNX)

策略2:延迟双删 (Double Delete with Delay)

更新数据库 → 删除缓存 → 延迟N秒 → 再次删除缓存  

延迟时间 = 业务最大读耗时(通常100-500ms)

策略3:异步队列补偿

  • 写入数据库后发送MQ消息,消费端异步删除或更新缓存
  • 优点:削峰填谷,抗并发
  • 缺点:数据短暂不一致(秒级)

策略4:基于Binlog的缓存更新(Canal)

  • 监听数据库Binlog变更,通过Canal推送到缓存系统
  • 适合微服务架构,解耦写与缓存操作

策略5:先删缓存,再更新数据库(慎用!)

  • 写操作先删除缓存,再更新数据库
  • 致命问题:删除缓存后,其他线程可能将旧数据写入缓存,导致最终数据与缓存永久不一致

策略6:读写锁 + 缓存版本号

  • 每个缓存键附带版本号,写操作原子递增版本号,读操作检查版本一致性
  • 适合库存、余额等强一致性场景

策略7:分布式锁 + 缓存双写

  • 写操作获取分布式锁,确保只有一个线程执行“更新数据库+更新缓存”
  • 弊端:锁导致性能下降,仅用于关键数据

策略8:缓存设置过期时间 + 主动修复

  • 即使一致性失败,通过过期时间最终自动刷新
  • 适合非核心数据,如用户个人信息(可容忍30秒不一致)

策略9:写操作直接操作缓存(Write-Behind)

  • 所有写操作先写入缓存,异步批量刷新到数据库
  • 优点:极致写入性能
  • 缺点:缓存宕机导致数据丢失,需配合持久化

策略10:数据库本地事务 + 缓存消息表(可靠消息)

  • 利用数据库事务,在业务表更新时同时插入一条“缓存更新消息”
  • 独立消息消费线程异步处理缓存更新,保证最终一致性

高频问答

Q1:为什么高并发下Cache Aside依然会出现脏读?
A:当线程A更新数据库后删除缓存,线程B在同一毫秒内读取旧数据并回写缓存,此时缓存中存放的是旧数据,直到下一次写操作才会刷新。

Q2:延迟双删的延迟时间如何计算?
A:观察业务接口的P99读耗时,假设为200ms,则延迟应设为400-600ms(留够缓冲),但不可超过2秒,否则影响用户体验。

Q3:使用Canal监听Binlog会影响数据库性能吗?
A:不影响主库,Canal伪装为从库拉取Binlog,对主库无侵入,但需注意磁盘IO与网络带宽。

Q4:一致性要求极高(如交易、库存)时选哪种策略?
A:推荐 分布式锁+数据库事务,写操作先获取Redis锁,然后更新数据库(事务中同时写缓存),释放锁。

Q5:缓存设置多大的TTL最安全?
A:业务容忍不一致的时间,例如用户资料可设为300秒,商品库存设为10秒,过短TTL会导致缓存命中率下降。


实战建议:如何避免99%的一致性坑

  1. 切勿使用“先删缓存,再更新数据库” —— 这句代码写过的人都后悔了
  2. 延迟双删必须配合重试机制:若第一次删除失败,可记录日志并异步重试
  3. 缓存与数据库的更新步骤必须原子化:通过Lua脚本或分布式事务实现
  4. 监控指标:关注缓存穿透率、延迟双删的成功率、Binlog积压数量
  5. 业务分层
    • 核心数据(订单、库存)→ 延迟双删+分布式锁
    • 非核心数据(推荐列表、首页)→ 异步消息+过期时间
  6. 终极方案:对一致性无法容忍的场景,放弃缓存,直接走数据库

SEO提示:本文结合了Redis官方文档、美团技术博客、阿里云架构指南及多个开源项目实践,全文覆盖“缓存一致性”长尾词,如需转载,请保留出处。

抱歉,评论功能暂时关闭!