技术专题-分布式缓存数据库-Redis系列

Redis缓存与数据库数据一致性

2020-04-06  本文已影响0人  925781609

Redis由于将数据保存在内存中,访问速度远远大于基于磁盘的数据库(如MySQL)。但是由于其容量有限,不开启持久化时断电数据容易丢失(开启持久化影响性能)等特点, 因此常常作为数据库的缓存来使用。Redis也主要适合于读多写少,且对一致性要求不是特别高的场景,这是使用Redis的前提。

缓存更新策略

由于引入缓存,数据就会分散在缓存和数据库两处不同的数据源,当数据更新时,事实上很难做到数据一致,除非采用强一致性方案,这里不在进行讨论。关于数据的更新,主要有以下4种模式, 其中Read Through和Write Through放在一起讨论:

这四种模式的主要区别在于最新数据在缓存中还是数据库中,由谁进行更新

模式 最新数据在哪里 由谁更新
Cache Aside 数据库 应用程序更新缓存
Read Through/Write Through 缓存服务更新数据库
Write Back 缓存 缓存服务更新数据库

下面分别对这四种模式进行阐述,为保证行文连贯,先假设更新数据库以及缓存都会事务成功,由于某一种更新导致的不一致性在后续章节讨论

1. Cache Aside

Cache Aside顾名思义,就是缓存“靠边站”,只是在访问数据库的主流程上帮个忙,最新的数据还是以数据库为主

Cache Aside主要有三点:

  1. 命中:应用程序从cache中取数据,取到后返回。
  2. 失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。
  3. 更新:先把数据存到数据库中,成功后,应用程序再让缓存失效。

至于为什么是先更新数据库,再让缓存失效, 而不是直接更新缓存,主要是为了保证在并发的情况下,尽可能降低数据不一致出现的概率,具体参考附录,在大概率的情况下先更新数据库再失效缓存能够保证数据一致,也是业界推荐的处理方式,包括Facebook的论文《Scaling Memcache at Facebook》也使用了这个策略。


图片来源于https://coolshell.cn/articles/17416.html

2. Read Through与Write Through

Cache Aside 对缓存以及数据库的更新逻辑是由应用程序去控制的,很显然这是一个很复杂的过程。Write/Read Through对调用方而言,缓存是作为整个的数据存储,而不用关心缓存后面的数据库,数据库的更新则是由缓存统一进行管理,对调用方而言只需要和缓存进行交互,整体过程是透明的。可以理解为,应用认为后端就是一个单一的存储,而存储自己维护自己的Cache。如下图,图片来源于Cache (computing)

3. Write Back

这种模式是当数据更新的时候直接更新缓存数据,然后建立异步任务去更新数据库。这种异步方式请求响应会很快,系统的吞吐量会明显提升。但是,因为是异步更新数据库,数据一致性的保障就会变弱,如果更新数据库失败则会永远的造成系统脏数据,需要很精细设计系统重试的策略,另外如果异步服务宕机的话,还要考虑更新的数据如何持久化,服务重启后能够迅速恢复。在更新数据库时,由于并发多任务的存在,还需要考虑并发写是否会造成脏数据的问题,就需要追溯每次更新数据的时序。使用这种模式需要考虑的细节会有很多,设计出一套好的方案是件很不容易的事情。

图片来源于https://en.wikipedia.org/wiki/Cache_(computing)

数据不一致的原因

1. 数据不一致的原因

  1. 逻辑失败造成的数据不一致
    因为异步读写请求在并发情况下的操作时序导致的数据不一致,称之为”逻辑失败“。解决这种因为并发时序导致的问题,核心的解决思想是将异步操作进行串行化。
  2. 物理失败造成的数据不一致
    在Cache Aside 模式中先更新数据库再删除缓存以及异步双删策略等等,如果删除缓存失败时都出现数据不一致的情况。出于性能的考虑,数据库及缓存的操作不会放在一个事务中,因为缓存操作失败,导致的数据不一致称之为“物理失败”。大多数情况物理失败的情况会重用重试的方式进行解决。

2. 数据最终一致性解决方案:

在绝大部分业务场景中,追求的是最终一致性,针对物理失败造成的数据不一致常用的方案有:消费消息异步删除缓存以及订阅Binlog的方式,针对逻辑失败造成的数据不一致常用的方案有:队列异步操作同步化。
消费消息异步删除缓存
流程如下图所示,主要包括:

  1. 应用程序更新数据库数据;
  2. 删除缓存
  3. 如果缓存删除失败,将删除失败的key 发送到消息队列
  4. 应用程序自己消费消息,获得需要删除的key
  5. 继续重试删除操作,直到成功

订阅binlog
消费消息异步删除缓存有一个缺点,对业务线代码有大量的侵入,所以引出了本方案, 主要流程如下:

  1. 应用程序更新数据库
  2. 通过canal 订阅数据库的binlog
  3. 数据更新服务解析binlog
  4. 根据解析的binlog更新缓存
  5. 对于更新失败的,将失败的key发送到消息队列
  6. 缓存服务订阅消息队列, 重试

总结:

无论是4种更新策略,还是缓存的最终一致性方案,Redis缓存与数据库都会有短时间或者小概率的不一致的风险, 这又回到了开篇Redis适合使用的场景,读多写少, 对一致性要求不高,如果是一致性要求特别高的情况,比如交易场景,则只能使用数据库了。

附录

1. 先更新数据库再失效缓存

先更新数据库再失效缓存出错情况(概率小)

2. 先失效缓存再更新数据库

先失效缓存再更新数据库数据不一致情况(概率大)

3. 先更新缓存再更新数据库与先更新数据库再更新缓存

先更新缓存再更新数据库数据不一致情况(概率大)

参考:

  1. 缓存更新的套路
  2. 响应速度不给力?解锁正确缓存姿势
上一篇 下一篇

猜你喜欢

热点阅读