缓存和数据库的最终一致性方案

1.为什么要用缓存

  • 在读多写少的业务场景,为了提高系统的响应能力,减少数据库访问压力,提升数据读写性能,从而提升业务的稳定性和访问体验。
  • 根据 CAP 原理,分布式系统在可用性、一致性和分区容错性上无法兼得,通常由于分区容错无法避免,所以一致性和可用性难以同时成立。对于缓存系统来说,如何保证其数据一致性是一个在应用缓存的同时不得不解决的问题。
  • 根据 CAP 原理,分布式系统在可用性、一致性和分区容错性上无法兼得,通常由于分区容错无法避免,所以一致性和可用性难以同时成立。对于缓存系统来说,如何保证其数据一致性是一个在应用缓存的同时不得不解决的问题。

2.怎么用缓存——读

在这里插入图片描述
在读请求中,首先请求缓存,若缓存命中( cache hit ),则直接返回缓存中的数据;若缓存未命中( cache miss ),则查询数据库并将查询结果更新至缓存,然后返回查询出的数据( demand-filled look-aside )

3.怎么用缓存——写

3.1先更新数据库,再删除缓存

在这里插入图片描述
推荐方案:这种方式出现不一致的场景,不仅需要缓存失效且读写并发执行,而且还需要读请求查询数据库的执行早于写请求更新数据库,同时读请求的执行完成晚于写请求。足以见得,这种不一致场景产生的条件非常严格,在实际的生产中出现的可能性较小。
首先来自线程 1 的读请求在未命中缓存的情况下查询数据库( 步骤1 ),接着来自线程 2 的写请求更新数据库( 步骤2 ),但由于一些极端原因,线程 1 中读请求的更新缓存操作晚于线程 2 中写请求的删除缓存的操作( 步骤4 晚于 步骤3 ),那么这样便会导致最终写入缓存中的是来自线程 1 的旧值,而写入数据库中的是来自线程 2 的新值,即缓存落后于数据库,此时再有读请求命中缓存( 步骤 5 ),读取到的便是旧值。

3.2先更新数据库,再更新缓存

在这里插入图片描述
问题:首先来自线程 1 的写请求更新了数据库( 步骤1 ),接着来自线程 2 的写请求再次更新了数据库(步骤2 ),但由于网络延迟等原因,线程 1 可能会晚于线程 2 更新缓存( 步骤4 晚于 步骤 3 ),那么这样便会导致最终写入数据库的结果是来自线程 2 的新值,写入缓存的结果是来自线程 1 的旧值,即缓存落后于数据库,此时再有读请求命中缓存,读取到的便是旧值。

3.3先删除缓存,再更新数据库

在这里插入图片描述首先来自线程 1 的写请求删除了缓存( 步骤 1 ),接着来自线程 2 的读请求由于缓存的删除导致缓存未命中,根据 Cache-Aside 模式,线程 2 继而查询数据库(步骤2 ),但由于写请求通常慢于读请求,线程 1 更新数据库的操作可能会晚于线程 2 查询数据库后更新缓存的操作(步骤4 晚于步骤3 ),那么这样便会导致最终写入缓存的结果是来自线程 2 中查询到的旧值,而写入数据库的结果是来自线程 1 的新值,即缓存落后于数据库,此时再有读请求命中缓存,读取到的便是旧值

3.3延迟双删

在这里插入图片描述

为了避免“先删除缓存,再更新数据库”这一方案在读写并发时可能带来的缓存脏数据,业界又提出了延时双删的策略,即在更新数据库之后,延迟一段时间再次删除缓存,为了保证第二次删除缓存的时间点在读请求更新缓存之后,这个延迟时间的经验值通常应稍大于业务中读请求的耗时。延迟的实现可以在代码中 sleep 或采用延迟队列。显而易见的是,无论这个值如何预估,都很难和读请求的完成时间点准确衔接,这也是延时双删被诟病的主要原因。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值