缓存和缓存更新策略

缓存更新策略

一般来说,缓存层指的是分布式中间件Redis或者Memcached,在服务层和数据库层直接加上一层分布式缓存中间件,实现高并发的写入、读取。从而提高性能,缓解数据库压力。

常见的三种缓存模式

  • 旁路缓存模式(最常用)
  • 读写穿透模式
  • 异步回写模式

旁路缓存模式

在这种模式中,读写缓存,数据库和更新缓存的操作都在应用程序中完成

具体来说:读取的过程就是

  • 接收到数据查询的请求
  • 先去缓存中进行查询
  • 如果缓存中存在则直接返回
  • 如果不存在,则去数据库进行查询,并且添加记录到缓存。

写的过程:

  • 接收用户的数据写入请求
  • 先写入数据库
  • 再写入缓存

同时,根据数据读取的时机,又分为懒汉模式(延迟加载)和饿汉模式(及时加载)。饿汉就是提前预加载,项目启动的时候就把数据加载到缓存。懒汉模式即是第一次查询到才会加载到缓存中。

对于极热数据,就比如秒杀那种的,可以预先加载到缓存中,能极大的提升请求处理的性能和系统的吞吐量。

读写穿透模式

读写穿透模式可以理解为在缓存旁路模式之上进行了一层封装,封装出一层独立的缓存程序Cache Provider。数据的读写都由这个Cache-Provider层处理,代码会更简洁。

整体过程如下:读取的过程:

  • 接收到数据查询请求
  • 应用程序通过Cache Provider查询数据
    • cache Provider进行判断数据从何处处理,如果缓存在Redis中,则获取缓存中的数据并返回
    • 如果数据不在Redis中,则从数据库获取,添加到缓存中并返回。

写入的过程:

  • 接收到用户的数据写入
  • 应用程序通过Cache-provider写入
    • Cache-Provider先写入缓存数据
    • Cache-provider写入数据库

读写穿透模式和旁路缓存模式分享相似,区别是

  • 在读写穿透模式中,应用程序不需要具体的数据读写细节,直接通过Cache- Provider提供服务。所以代码会更加简洁。
  • 读写穿透模式中,更新缓存的时候,是直接写入新的缓存数据,旁路缓存模式是删除掉缓存。

读写穿透模式的应用场景是数据一致性要求高,并且写操作多的场景。但是因为要保证写缓存和写数据库要同步进行,需要用到锁等,会降低性能。

异步回写模式

异步写入模式在写入的时候,只更新缓存,不同步更新数据库,通过异步批量写入数据库。可以看出这种模式的性能肯定是很高的。但是对数据一致性的保证比较弱。如果缓存丢失,并且没有写入到数据库,则会造成数据丢失。

异步回写模式适合数据变化频繁,又对数据一致性要求不高的场景。如浏览量,点赞量

缓存双写一致性

一般分为5种策略

  • 方案1:先更新数据库,再更新缓存。
  • 方案2:先删除缓存,再更新数据库。
  • 方案3:先更新数据库,再删除缓存。
  • 方案4:延迟双删。
  • 方案5:先更新数据库,再基于队列删除缓存。

这个相信不止一次在面试中被问到了。其实思考的方向是并发方向。

方案1:

假设有两个请求,A和B都是写入数据:

1、A更新数据库

2、B更新数据库

3、B更新缓存

4、A更新缓存

可以结合并发,可以很明显的看出来,B更新的缓存被A覆盖掉了。导致数据其实是不一致的。因为并发的数据不好控制,所以大概率会造成脏数据。

方案2:先删除缓存,再更新数据库

假设有两个请求,A写入数据,B读取数据:

1、A删除缓存

2、B查询数据,因为没有命中缓存,所以在数据库中查找,并将数据保存到缓存中。

3、A将数据写入数据库

这个也可以看出来,缓存中其实缓存的是历史旧数据。这也会造成数据的不一致。

方案3:先更新数据库再删除缓存

假设有两个请求,A写入数据,B读取数据:

1、A更新数据到数据库

2、B查询数据,此时旧的数据还在缓存中,所以可以直接读取缓存数据

3、A删除缓存

这个方案基本上可以是可以的。但是如果读请求发生在 更新数据和删除缓存之间,还是会造成数据不一致。所以这个方案存在一些问题:

  • 在写入数据和删除缓存之间,会存在短暂的数据不一致
  • 如果缓存删除失败,则会长时间导致数据不一致。

那么如何解决写入和删除缓存的这个时间差,数据不一致的问题呢?

其实是因为没有预先删除缓存,导致查询到了缓存中的数据。那么,如果删除掉缓存中的数据呢?这就引出了方案4,延迟双删

方案4:延迟双删

1、先删除缓存,2、再更新数据库,然后再延迟一段时间,删除缓存。

为什么要最后再删除一次?因为可能并发读发生在删除缓存和更新数据库这个时间。

延迟双删存在的问题:

  • 因为有两次删除,所以会对redis有两次写入,导致写入压力增大
  • 如果最后一次的删除失败,依然可能导致数据不一致。并且不一致时间较长。

方案5:更新数据库再基于队列删除缓存

先更新数据库,然后对应的删除操作放入队列,通过队列进行删除。

因为队列可以进行重试,所以,基本上可以保证缓存可以删除掉。

说到队列,又会分为内存型队列,消息队列中间件,binlog+消息队列。

内存队列可能造成内存占用过高,并且如果出现宕机,会导致没有删除的数据丢失。

在数据库和缓存的分布式架构中,加入分布式缓存是为了获得高性能,高吞吐,即为了获得分布式系统的AP特性(可用和分区容错)所以,如果需要数据库和缓存数据保持强一致(强CP特性),就不适合使用缓存。


缓存和缓存更新策略
http://yoursite.com/2023/01/27/缓存和缓存更新策略/
作者
yangchen
发布于
2023年1月27日
许可协议