Redis高级系列-缓存双写一致性
文章目录
- Redis高级系列-缓存双写一致性
-
- [1. 什么叫做缓存双写一致性?](#1. 什么叫做缓存双写一致性?)
- [2. 缓存双写一致性有那些解决方案?](#2. 缓存双写一致性有那些解决方案?)
-
- [2.1 Cache Aside Pattern(旁路缓存模式)](#2.1 Cache Aside Pattern(旁路缓存模式))
- [2.2 Read Through/Write Through(读写穿透)](#2.2 Read Through/Write Through(读写穿透))
- [2.3 Write Behind(后写或异步写)](#2.3 Write Behind(后写或异步写))
- [3. 总结](#3. 总结)
1. 什么叫做缓存双写一致性?
Redis缓存双写一致性是指在更新数据库数据后,同时更新缓存数据以保持数据一致性的策略,总的来说,就是写入redis 和写入数据库的数据要保持一致
2. 缓存双写一致性有那些解决方案?
2.1 Cache Aside Pattern(旁路缓存模式)
旁路缓存模式,字面意思理解:缓存是旁路,缓存相对与应用程序和数据库是旁路,应用程序可以直接绕过缓存访问数据库
在Cache Aside模式中,应用程序首先从缓存中读取数据,如果缓存中不存在,则从数据库中读取数据,并将数据写入缓存中。在更新数据时,应用程序首先更新数据库中的数据,然后删除缓存中的数据。当下一次请求到来时,应用程序会从数据库中读取最新的数据,并将其写入缓存中
那为什么要先更新数据库在删除缓存了?我们再看看几种更新策略
策略 | 是否是多线程 | 场景 | 现象 |
---|---|---|---|
先删除缓存再更新数据库 | 否 | 删除缓存成功但数据库更新失败 | 应用程序从数据库中读到旧值 |
是 | 缓存删除成功但数据库再更新中...有并发读请求 | 并发读请求从数据库读到旧值,并回写到redis,导致后续都从redis读取到旧值 | |
先更新数据库再删除缓存 | 否 | 数据库更新成功,但缓存删除失败 | 应用程序从redis读取到旧值 |
是 | 数据库更新成功,但缓存再删除中...有并发读请求 | 并发读请求读取到旧值 | |
先更新缓存再更新数据库 | 否 | 更新缓存成功但更新数据库失败 | 缓存和数据库数据不一致(不推荐,一般数据库作为兜底方案) |
是 | 更新缓存成功但数据库再更新中 | 缓存和数据库数据不一致(不推荐,一般数据库作为兜底方案) | |
先更新数据库再更新缓存 | 否 | 更新数据库成功但缓存更新失败 | 应用程序读取到旧值 |
是 | 更新数据库成功但缓存再更新中...有并发读请求 | 并发读请求读取到旧值 |
延迟双删
经过我们对Cache Aside Pattern四种方案的八种场景进行仔细分析,我们大体上确定了先更数据库再删缓存的方案。但是这种方案也并不完美,假设我们更新数据库成功了,删除缓存失败了,那么同样会出现一致性问题。为了解决这种场景下的一致性问题,我们进一步引入了延迟双删方案来解决。
- 为什么要做两次缓存删除呢?
从上图"延迟双删"中我们可以很清晰地看到,数据库的更新是在首次的缓存删除成功后进行的,这样就有效避地免了"先更数据库再删缓存"方案中可能出现的数据库更新成功缓存删除失败导致的一致性问题。第二次的缓存删除是为了避免在执行更新数据库操作完成之前其它线程读取数据库并更新缓存而导致的一致性问题。
- 为什么第二次缓存删除又要延迟执行呢?
试想,如果我们不做第二次的延迟删除,而是更新数据库后立即删除会出什么问题?有没有可能其它读请求线程在更新数据库之前读取了数据,并在第二次立即删除缓存之后更新了缓存。很明显,延迟删除就是为了让可能存在的其它读请求线程尽可能地在更新完缓存后再执行缓存删除操作。这样一通操作后,数据库是最新的数据了,缓存里没有数据,后面的读请求线程又可以拿到数据库的最新数据写入缓存了。
- 延迟删除具体需要延迟多长时间?
对于这个具体需要延迟多长时间,其实没有绝对的标准,唯一的标准就是根据读请求的耗时来确定,读请求越耗时,延迟时间越长。一般情况下我们设置的延迟时间为1秒。
重试删除
在上面的延迟双删方案中,如果我们思考,其实还是有诸多问题的。比如:在延迟双删的读写场景中,如果第二次缓存删除失败了,同样会出现一致性问题。那么删除失败了,重试几次不就好了吗?基于这个思想我们进一步引入了重试删除方案来解决
重试删除确实很大程度上解决了一致性问题,并且逻辑非常简单。唯一的不足可能就是需要引入消息队列,并且业务代码也会有一定的侵入。那么有没有既能够保证最终一致性,又能够解耦的方案呢?我们继续往下面看
binlog订阅异步删除
binlog订阅异步删除确实也能解决一致性问题,对代码也无任何侵入,但是整个架构复杂,中小项目一般都用不上。
2.2 Read Through/Write Through(读写穿透)
该策略又被称为读穿/写穿策略,和CacheAside策略的缓存数据与数据库数据为准不同,该策略的核心是用户只与缓存层交互(应用程序只与缓存或者中间层、缓存抽象层交互),由缓存层与数据库通信,写入或读取数据。
在读取数据时,通过缓存层进行读取,若缓存存在则直接返回,若不存在则由缓存层拉取数据库数据到缓存中并返回。
在写数据时,通过缓存层进行写入,若缓存存在则直接写入缓存中并同步到数据库,若不存在则写入数据库中。
使用场景
适用于读多写多的场景,数据一致性要求较高的场景。
2.3 Write Behind(后写或异步写)
Write Behind Caching是一种将缓存和数据库异步写入的缓存模式。在Write Behind Caching模式中,应用程序首先将更新操作写入缓存中,然后异步地将更新操作写入数据库中。当下一次请求到来时,应用程序会从缓存中读取数据,并将其写入数据库中。
使用场景
适用于写多读少的场景,数据一致性要求不高的场景
3. 总结
在使用Redis缓存时,应根据具体的业务场景和需求选择合适的缓存模式。Cache Aside模式简单易用,适用于读多写少的场景;Read/Write Through模式完全解耦缓存和数据库,适用于读多写多的场景;Write Behind Caching模式可以提高写入性能,适用于写多读少的场景。在实际应用中,可以根据具体的业务需求和性能要求,选择合适的缓存模式,以提高系统的性能和稳定性。