redis之缓存一致性

目录

  • 前言
  • 一 缓存更新策略
    • 一) 内存淘汰
    • 二) 过期删除
    • 三) 主动更新
      • 1 Cache Aside Pattern
      • 2 Read/Write Through Pattern
        • 1) Read Through
        • 2) Write Through
      • 3 Write Behind Caching Pattern
      • 4 三种主动更新策略的对比
    • 四) 三种缓存更新策略的对比
  • 二 更新缓存的两种方式
    • 1 删除缓存
    • 2 更新缓存
  • 三 缓存更新策略的实现方式
    • 一) 先更新缓存,再更新DB
      • 1 并发写场景
      • 2 并发读写场景
    • 二) 先更新DB,再更新缓存
      • 1 并发写场景
      • 2 并发读写场景
    • 三) 先删除缓存,再更新DB
      • 1 并发写场景
      • 2 并发读写场景
    • 四) 先更新DB,再删除缓存
      • 1 并发写场景
      • 2 并发读写场景
    • 五) 延迟双删
      • 1 并发写场景
      • 2 并发读写场景
      • 3 延时双删的缺点
    • 六) 异步删除缓存
      • 1 基于消息队列的异步删除缓存
        • 1) 并发写场景
        • 2) 并发读写场景
      • 2 基于binlog+消息队列删除缓存
        • 1) 并发写场景
        • 2) 并发读写场景
      • 3 异步删除缓存的优缺点
    • 七) 几种实现方式的对比

前言

使用redis作为缓存,必然存在redis缓存和DB数据一致性的问题:某一时刻,redis缓存数据和DB数据不一致

一 缓存更新策略

按照缓存更新的方式大致分为: 内存淘汰、过期删除、主动更新

一) 内存淘汰

利用Redis的内存淘汰策略,当内存不足时自动进行淘汰部分数据,下次查询时更新缓存,一致性差,无维护成本

内存淘汰策略详情请参考:redis内存淘汰策略和过期删除策略

二) 过期删除

缓存添加过期时间,到期后根据过期删除策略自动进行删除缓存,下次查询时更新缓存,一致性一般,维护成本低

过期删除策略详情请参考:redis内存淘汰策略和过期删除策略

三) 主动更新

应用程序中修改DB,修改缓存,一致性好,维护成本高

主动更新大致分为: Cache Aside Pattern、Read/Write Through Pattern、Write Behind Caching Pattern

1 Cache Aside Pattern

即旁路缓存模式,旁路路由策略,最经典常用的缓存策略

应用程序负责缓存和DB的读写

读写操作步骤:
读操作时,先读缓存,缓存存在直接返回;缓存不存在则读DB,然后把读的DB数据存入缓存,返回

写操作时,先更新DB,再删除缓存

读操作流程图:
redis之缓存一致性_第1张图片

写操作流程图:
redis之缓存一致性_第2张图片

2 Read/Write Through Pattern

即读写穿透,该模式下应用程序只与缓存管理组件交互,缓存管理组件负责缓存和DB的读写

1) Read Through

读操作时,缓存管理组件先读缓存,缓存存在直接返回;缓存不存在则读DB,然后把读的DB数据存入缓存,返回

流程图:
redis之缓存一致性_第3张图片

2) Write Through

写操作时,缓存管理组件同步更数DB和缓存

流程图:
redis之缓存一致性_第4张图片

3 Write Behind Caching Pattern

即异步缓存写入,该模式下应用程序只与缓存管理组件交互操作,缓存管理组件负责缓存和DB的读写,通过定时或阈值的异步方式将数据同步到DB,保证最终一致

该模式和Read/Write模式相似,不同点在于Read/Write模式更新DB和更新缓存是同步的,而Write Behind Caching Pattern模式更新DB和更新缓存是异步的

读流程图:
redis之缓存一致性_第5张图片

写流程图:
redis之缓存一致性_第6张图片

优点:
减少了更新DB的频率,读写响应非常快,吞吐量也会有明显的提升

缺点:
不能时时同步,数据同步DB过程服务不可用,导致数据丢失

4 三种主动更新策略的对比

策略 说明 优点 缺点
Cache Aside Pattern 应用程序负责缓存和DB的读写 使用简单,直接操作缓存和DB 需要编写对缓存和DB读写的代码
Read/Write Through Pattern 应用程序只与缓存管理组件交互,缓存管理组件负责缓存和DB的读写 使代码更简洁 缓存管理组件需要提供对DB和缓存读写的方法
Write Behind Caching Pattern 应用程序只与缓存管理组件交互,缓存管理组件负责缓存和DB的读写 性能最好,在高并发场景下可以降低数据库的压力 缓存管理组件 需要提供对DB和缓存读写的方法;

不能时时同步,数据同步DB过程DB不可用,导致数据丢失;

一致性不强,对一致性要求高的系统不适用

四) 三种缓存更新策略的对比

策略 说明 一致性 维护成本
内存淘汰 使用Redis的内存淘汰策略,当内存不足时自动进行淘汰部分数据,下次查询时更新缓存
过期删除 缓存添加过期时间,到期后根据过期删除策略自动进行删除缓存,下次查询时进行更新缓存
主动更新 修改数据库时也修改缓存,使用硬编码方式或者硬编码+中间件方式在修改数据库时同步或异步的修改缓存

二 更新缓存的两种方式

1 删除缓存

更新DB时删除缓存,查询时再从DB中读取数据并更新到缓存

2 更新缓存

更新DB时更新缓存,频繁更新缓存开销大,且并发时可能导致请求读取的缓存数据是旧数据

三 缓存更新策略的实现方式

一) 先更新缓存,再更新DB

1 并发写场景

所有线程都是先更新缓存再更新DB,在某个写线程更新缓存和更新DB之间,其他写线程也更新了缓存和DB,导致缓存和DB数据不一致

流程图:
redis之缓存一致性_第7张图片

具体步骤:
1) 线程1更新缓存
2) 线程2更新缓存
3) 线程2更新DB
4) 线程1更新DB

缓存和DB数据不一致的原因:
理论上先更新缓存的线程也会先更新DB,但是并发场景下线程的执行顺序无法保证:
a) 若更新DB的顺序是: 线程1再线程2,则不会出现数据不一致问题
b) 若更新DB的顺序是: 线程2再线程1,此时缓存是线程2的数据,DB是线程1的数据,导致缓存和DB数据不一致

2 并发读写场景

在写线程更新缓存和更新DB之间,读线程也可以获取到最新的缓存,不会导致缓存和DB数据不一致

流程图:
redis之缓存一致性_第8张图片

具体步骤:
1) 线程1更新缓存
2) 线程2查询,命中缓存返回
3) 线程1更新DB

缓存和DB数据不一致的原因:
可以保证缓存和DB数据一致,虽然线程1更新DB的操作还没有完成,但是更新缓存的操作已经完成了,读请求可以获取到最新的缓存

二) 先更新DB,再更新缓存

1 并发写场景

所有线程都是先更新DB再更新缓存,在某个写线程更新DB和更新缓存之间,其他写线程也更新了DB和缓存,导致缓存和DB数据不一致

流程图:
redis之缓存一致性_第9张图片

具体步骤:
1) 线程1更新DB
2) 线程2更新DB
3) 线程2更新缓存
4) 线程1更新缓存

缓存和DB数据不一致的原因:
理论上先更新DB的线程也会先更新缓存,但是并发场景下线程的执行顺序无法保证:
a) 若更新缓存的顺序是: 先线程1再线程2,则不会出现数据不一致问题
b) 若更新缓存的顺序是: 先线程2再线程1,此时缓存是线程1的数据,DB是线程2的数据,导致缓存和DB数据不一致

2 并发读写场景

在写线程更新DB和更新缓存之间,读线程可以获取到旧数据,但最终会一致

流程图:
redis之缓存一致性_第10张图片

具体步骤:
1) 线程1更新DB
2) 线程2查询,命中缓存返回
3) 线程1更新缓存

缓存和DB数据不一致的原因:
线程2获取的缓存是旧数据,但最终都会一致

三) 先删除缓存,再更新DB

1 并发写场景

所有线程都是先删除缓存再更新DB,无论哪个线程先删除缓存再更新DB,缓存都会被删除,不会导致缓存和DB数据不一致

流程图:redis之缓存一致性_第11张图片

具体步骤:
1) 线程1删除缓存
2) 线程2删除缓存
3) 线程2更新DB
4) 线程1更新DB

缓存不一致原因:
无论哪个线程先删除缓存再更新DB,缓存都会被删除,不会导致缓存和DB数据不一致

2 并发读写场景

在写线程删除缓存和更新DB之间,读线程根据查询的DB结果更新了缓存,导致缓存和DB数据不一致

流程图:
redis之缓存一致性_第12张图片

具体步骤:
1) 线程1删除缓存
2) 线程2查询,未命中
3) 线程2查询DB
4) 线程2根据查询的DB结果更新缓存
5) 线程1更新DB

缓存和DB数据不一致的原因:
线程1删除缓存和更新DB之间,线程2根据查询的DB结果更新了缓存,导致缓存和DB数据不一致

四) 先更新DB,再删除缓存

1 并发写场景

所有线程都是先更新DB再删除缓存,无论哪个线程先更新DB再删除缓存,缓存都会被删除,不会导致缓存和DB数据不一致

流程图:
redis之缓存一致性_第13张图片

具体步骤:
1) 线程1更新DB
2) 线程2更新DB
3) 线程2删除缓存
4) 线程1删除缓存

缓存不一致原因:
无论哪个线程先更新DB再删除缓存,缓存都会被删除,不会导致缓存和DB数据不一致

2 并发读写场景

在写线程更新DB再删除缓存之间,读线程可以获取到旧数据,但最终会一致

流程图:redis之缓存一致性_第14张图片

具体步骤:
1) 线程1更新DB
2) 线程2查询命中缓存返回
3) 线程1删除缓存

缓存不一致原因:
线程2获取的缓存是旧数据,但最终都会一致

五) 延迟双删

先删除缓存再更新DB在并发写场景不会导致数据不一致,但是在并发读写场景会导致数据不一致
延迟双删是基于先删除缓存再更新DB的基础上的改进:在更新DB后延迟一定时间,再次删除缓存

延时是为了保证第二次删除缓存前能完成更新DB操作,延时时间根据系统的查询性能而定
第二次删除缓存是为了保证后续请求查询DB(此时数据库中的数据已是更新后的数据),重新写入缓存,保证数据一致性

1 并发写场景

无论哪个线程都会删除缓存,不会导致缓存和DB数据不一致

流程图:
redis之缓存一致性_第15张图片

具体步骤:
1) 线程1删除缓存
2) 线程2删除缓存
3) 线程2更新DB
4) 线程1更新DB
5) 线程1延时删除缓存
6) 线程2延时删除缓存

缓存不一致原因:
无论哪个线程都会删除缓存,不会导致缓存和DB数据不一致

2 并发读写场景

流程图:
redis之缓存一致性_第16张图片

具体步骤:
1) 线程1删除缓存
2) 线程2查询,未命中
3) 线程2查询DB
4) 线程2根据查询的DB结果更新缓存
5) 线程1更新DB
6) 线程1延时删除缓存

缓存不一致原因:
线程1第一次删除缓存之后,线程2根据查询的DB结果更新缓存,此时查询得到的结果是旧数据,线程1延迟第二次删除缓存之后,后续查询DB(此时数据库中的数据已是更新后的数据),重新写入缓存,不会导致缓存和DB数据不一致

3 延时双删的缺点

1) 需要延时,低延时场景不合适,如秒杀等需要低延时,需要强一致,高频繁修改数据场景
2) 不能保证强一致性,在更新DB之前,查询线程查询得到的结果是旧数据,可但可以减轻缓存和DB数据不一致的问题
3) 延时的时间是一个不可评估的值,延时越久,能规避一致性的概率越大

六) 异步删除缓存

先更新DB再删除缓存在并发写场景不会导致数据不一致,但是在并发读写场景会短暂的导致数据不一致,但是由于删除缓存失败不会重试,并发写场景、并发读写场景都可能长时间导致数据不一致

异步更新缓存是基于先更新DB再删除缓存的基础上的改进:更新DB之后,基于消费队列异步删除缓存

根据消费队列不同大致分为:消息队列、binlog+消息队列

1 基于消息队列的异步删除缓存

1) 并发写场景

无论哪个线程先更新DB再删除缓存,缓存都会被删除,不会导致缓存和DB数据不一致

流程图:
redis之缓存一致性_第17张图片

具体步骤:
1) 线程1更新DB
2) 线程2更新DB
3) 线程2把删除缓存放入消息队列
4) 线程1把删除缓存放入消息队列
5) 异步:消息队列消费删除缓存

缓存不一致原因:
无论哪个线程先更新DB再删除缓存,缓存都会被删除,不会导致缓存和DB数据不一致

2) 并发读写场景

异步删除缓存期间,读线程获取的缓存是旧数据,短暂出现数据不一致,异步删除缓存后最终会一致

流程图:
redis之缓存一致性_第18张图片

具体步骤:
1) 线程1更新DB
2) 线程2查询缓存,命中返回
3) 线程1把删除缓存放入消息队列
4) 异步:消息队列消费删除缓存

缓存不一致原因:
异步删除缓存期间,读线程获取的缓存是旧数据,短暂出现数据不一致,异步删除缓存后最终会一致

2 基于binlog+消息队列删除缓存

1) 并发写场景

流程图:
redis之缓存一致性_第19张图片

具体步骤:
1) 线程1更新DB
2) 线程2更新DB
3) 异步:binlog日志收集中间件定时收集DB的binglog日志
4) 异步:binlog日志收集中间件发送日志消息到消息队列
5) 异步:消息队列消费删除缓存

缓存不一致原因:
无论哪个线程先更新DB再删除缓存,缓存都会被删除,不会导致缓存和DB数据不一致

2) 并发读写场景

流程图:redis之缓存一致性_第20张图片

具体步骤:
1) 线程1更新DB
2) 线程2查询缓存,命中返回
3) 异步:binlog日志收集中间件定时收集DB的binglog日志
4) 异步:binlog日志收集中间件发送日志消息到消息队列
5) 异步:消息队列消费删除缓存

缓存不一致原因:
异步删除缓存期间,读线程获取的缓存是旧数据,短暂出现数据不一致,异步删除缓存后最终会一致

3 异步删除缓存的优缺点

优点
1) 删除缓存的操作与主流程代码解耦
2) 中间件自带重试机制,增加了操作缓存的成功率

缺点
引入中间件,提升了系统的复杂度,在高并发场景可能会产生性能问题

七) 几种实现方式的对比

策略 并发场景 并发问题 数据不一致概率 说明
先更新缓存,再更新DB 并发写 所有线程都是先更新缓存再更新DB,在某个写线程更新缓存和更新DB之间,其他写线程也更新了缓存和DB,导致缓存和DB数据不一致
并发读写 在写线程更新缓存和更新DB之间,读线程也可以获取到最新的缓存,不会导致缓存和DB数据不一致 不会出现
先更新DB,再更新缓存 并发写 所有线程都是先更新DB再更新缓存,在某个写线程更新DB和更新缓存之间 其他写线程也更新了DB和缓存,此时缓存和DB数据不一致
并发读写 在写线程更新DB和更新缓存之间,读线程获取的缓存是旧数据,短暂出现数据不一致,但最终会一致 短暂不一致,最终会一致
先删除缓存,再更新DB 并发写 无论哪个线程先删除缓存再更新DB,缓存都会被删除,不会导致缓存和DB数据不一致 不会出现
并发读写 在写线程删除缓存和更新DB之间,读线程根据查询的DB结果更新了缓存,导致缓存和DB数据不一致
先更新DB,再删除缓存 并发写 无论哪个线程先更新DB再删除缓存,缓存都会被删除,不会导致缓存和DB数据不一致 不会出现
并发读写 在写线程更新DB和删除缓存之间,读线程获取的缓存是旧数据,短暂出现数据不一致,但最终会一致 短暂不一致,最终会一致
延迟双删 并发写 无论哪个线程都会删除缓存,不会导致缓存和DB数据不一致 不会出现 延迟双删基于先删除缓存再更新DB的基础上的改进:在更新DB后延迟一定时间,再次删除缓存
并发读写 在写线程删除缓存和更新DB之间,读线程根据查询的DB结果更新了缓存,短暂出现数据不一致,但延时再次删除缓存后数据会一致 短暂不一致,最终会一致
异步删除缓存 并发写 无论哪个线程先更新DB再删除缓存,缓存都会被删除,不会导致缓存和DB数据不一致 不会出现 异步更新缓存是基于先更新DB再删除缓存的基础上的改进:更新DB之后,基于消费队列异步删除缓存;引入了中间件,删除缓存失败时还可以重试
并发读写 异步删除缓存期间,读线程获取的缓存是旧数据,短暂出现数据不一致,异步删除缓存后最终会一致 短暂不一致,最终会一致

你可能感兴趣的:(redis,缓存,redis,数据库)