Skip to main content

一致性问题:缓存更新策略

David LiuAbout 3 min

一致性问题:缓存更新策略

概览

内存淘汰超时剔除主动更新
说明不用自己维护,利用 Redis 内存淘汰机制,当内存不足时自动淘汰部分数据。下次查询时更新缓存。给缓存数据添加 TTL 时间,到期后自动删除缓存。下次查询时更新缓存。编写业务逻辑,再修改数据库的同时,更新缓存
一致性一般
维护成本

业务场景:

  • 低一致性需求:使用内存淘汰机制。例如店铺类型等查询。
  • 高一致性需求:主动更新,并以超时剔除作为兜底方案。例如店铺详情查询的缓存。

主动更新策略

Cache Aside Pattern

由缓存的调用者,在更新数据库的同时更新缓存。

需要开发者自己编码,但是可控性很高

需要考虑的问题

  1. 删除缓存还是更新缓存?

    • 更新缓存:每次更新数据库都更新缓存,无效读写多 ,且会不一致❌

      • 先更新数据库、再更新缓存

        图片

      • 先更新缓存、再更新数据库

        图片

    • 删除缓存:更新数据库时,让缓存失效,查询时再更新缓存🉑

  2. 如何保证缓存与数据库操作的同时成功或失败?

    • 单体系统,将缓存与数据库操作放在一个事务
    • 分布式系统,利用 TCC 等分布式事务方案
  3. 先操作缓存还是先操作数据库?多线程并发访问

    • 先删除缓存,再操作数据库 ❌

      图片

    • 先操作数据库,再删除缓存🉑

      图片

    一致性问题,操作数据库比操作 redis慢得多,所以先操作数据库出现一致性问题的概率更小

    且即使删除操作失败了,也有过期时间可以作为保障兜底(即使删除操作失败了,超时以后也可以删除)

    缓存删除失败的方案:异步操作缓存

    重试机制

    我们可以引入消息队列,将第二个操作(删除缓存)要操作的数据加入到消息队列,由消费者来操作数据。

    • 如果应用删除缓存失败,可以从消息队列中重新读取数据,然后再次删除缓存,这个就是重试机制。当然,如果重试超过的一定次数,还是没有成功,我们就需要向业务层发送报错信息了。
    • 如果删除缓存成功,就要把数据从消息队列中移除,避免重复操作,否则就继续重试。

    举个例子,来说明重试机制的过程。

    订阅 MySQL binlog 再操作缓存

    Canal

    图片

    IMG_B987FACCF2BA-1

Read/Write Through Pattern

方案是由某种第三方服务提供,但是这样的服务很少

Write Behind Caching Pattern 写回

调用者只操作缓存,由其他线程异步的将缓存数据持久化道数据库,保证最终一致性

一致性和可靠性都存在一定的问题。

MESI 协议是一个基于失效的缓存一致性协议,是支持写回(write-back)缓存的最常用协议。也称作伊利诺伊协议 (Illinois protocol,因为是在伊利诺伊大学厄巴纳-香槟分校被发明的[1])。与写直达(write through)缓存相比,回写缓冲能节约大量带宽。总是有“脏”(dirty)状态表示缓存中的数据与主存中不同。MESI 协议要求在缓存不命中(miss)且数据块在另一个缓存时,允许缓存到缓存的数据复制。与 MSI 协议相比,MESI 协议减少了主存的事务数量。这极大改善了性能。[2]