Redis - 缓存模式详解(Cache Aside、Read Through、Write Through、Write Back、Singleflight)
作者:hangge | 2024-12-06 08:51
一、Cache Aside
1,基本介绍
(1)Cache Aside 这个模式就是把缓存看作一个独立的数据源。当写入的时候,业务方来控制写入顺序。
(2)当读取的时候,也是由业务方来控制。
(3)一般来说,都是优先写入数据库的。
提示:先写数据库是因为大多数业务场景下数据都是以数据库为准的,也就是说如果写入数据库成功了,就可以认为这个操作成功了。即便写入缓存失败,但是缓存本身会有过期时间,那么它过期之后重新加载,数据就会恢复一致。
2,数据一致性问题
不管是先写数据库还是先写缓存,Cache Aside 都不能解决数据一致性问题。
二、Read Through
1,基本介绍
(1)Read Through 是指在读缓存的时候,如果缓存未命中,那么缓存会代替业务代码去数据库中加载数据。
(2)这种模式有两个异步变种,一种是异步写回缓存,一种是完全异步加载数据,然后写回缓存。
2,异步方案
(1)第一个变种缓存可以在从数据库加载了数据之后,立刻把数据返回给业务代码,然后开启一个线程异步更新缓存。
(2)第二个变种是直接让整个加载和回写缓存的过程都异步执行。也就是说,如果缓存未命中,那么就直接返回一个错误或者默认值,然后缓存异步地去数据库中加载,并且回写缓存。和第一个变种比起来,这种变种的缺陷是业务方在当次调用中只能拿到错误或者默认值。
(3)两种变种的选择建议:
- 如果业务方对响应时间的要求非常苛刻,那么就可以考虑使用变种二。代价就是业务方会收到错误响应或者默认值。
- 而变种一其实收益很小,只有在缓存操作很慢的时候才会考虑。比如说缓存大对象,又或者要把一个大对象序列化之后再存储到缓存里面。
3,数据一致性问题
Read Through 只管了读的部分,而写的部分是完全没有管的,所以它的写过程和 Cache Aside 是一样的。因此,它一样有缓存一致性的问题。
三、Write Through
1,基本介绍
(1)Write Through 就是在写入数据的时候,只写入缓存,然后缓存会代替我们的去更新数据库。但是,Write Through 没有要求先写数据库还是先写缓存,不过一般也是先写数据库。
(2)其次,Write Through 也没有讨论如果缓存中原本没有数据,那么写入数据的时候,要不要更新缓存。一般来说,如果预计写入的数据很快就会读到,那么就要刷新缓存中的数据。
(3)Write Through 也有对应的异步变种方案。
2,异步方案
(1)第一种方案就是在写入缓存之后,缓存立刻返回结果。但这种模式是有可能丢数据的,也就是当业务代码收到成功响应之后,缓存崩溃了,那么数据其实并没有写入到数据库中。
(2)另外一个比较可行的变种是只写入数据库,然后返回成功响应,后面可以异步刷新缓存。这种方案合用于缓存写入操作且代价高昂的场景。比如说前面提到的,写入大对象或者需要序列化大对象再写入缓存。
3,数据一致性问题
无论是哪种异步变种方案,都没有解决缓存一致性的问题。
四、Write Back
1,基本介绍
(1)Write Back 模式是指我们在更新数据的时候,只把数据更新到缓存中就返回。后续会有一个组件监听缓存中过期的 key,在过期的时候将数据刷新到数据库中。显然,只是监听过期 key 的话还是会有问题,比如说关闭缓存的时候还是需要把缓存中的数据全部刷新到数据库里。
(2)Write Back 有一个硬伤,就是如果缓存突然宕机,那么还没有刷新到数据库的数据就彻底丢失了。这也限制了 Write Back 模式在现实中的应用。不过要是缓存能够做到高可用,也就不容易崩溃,也可以考虑使用。
2,数据一致性问题
(1)Write Back 最大的优点是排除数据丢失这一点,它能解决数据一致性的问题。
(2)首先,在使用 Redis 更新数据的时候业务代码只更新缓存,所以对于业务方来说必然是一致的。也就是说,虽然数据库的数据和缓存的数据不一致,但是对于业务方来说,它只能读写到缓存的数据,对业务方来说,数据是一致的。
(3)当业务方读数据的时候,如果缓存没有数据,就要去数据库里面加载。这个时候,就有可能产生不一致的问题。比如说,数据库中 a = 3,读出来之后还没写到缓存里面。这个时候来了一个写请求,在缓存中写入了 a = 4。如果这时候读请求回写缓存,就会用数据库里的老数据覆盖缓存中的新数据。
(4)解决这个问题的思路也很简单,当读请求回写的时候,使用 SETNX 命令。也就是说,只有当缓存中没有数据的时候,才会回写数据。而如果回写失败了,那么读请求会再从缓存中读取到数据。
(5)因此 Write Back 除了有数据丢失的问题,在缓存一致性的表现上,比其他模式要好。
五、Refresh Ahead
1,基本介绍
(1)Refresh Ahead 是指利用 CDC(Capture Data Change)接口异步刷新缓存的模式。
(2)这种模式在实践中也很常见,比如说利用 Canal 来监听数据库的 binlog,然后 Canal 刷新 Redis。
2,数据一致性问题
(1)这种模式也有缓存一致性的问题,也是出在缓存未命中的读请求和写请求上。
(2)实际上,这个缓存一致性问题是可以解决的,也就是参考 Write Back 里面的策略。
- 如果读请求在回写缓存的时候,使用了 SETNX 命令,那么就没有什么大的不一致问题了。唯一的不一致就是数据写入到了数据库,但是还没刷新到缓存的那段时间。
六、Singleflight
1,基本介绍
(1)Singleflight 模式是指当缓存未命中的时候,访问同一个 key 的线程或者协程中只有一个会去真的加载数据,其他都在原地等待。
(2)这个模式昀大的优点就是可以减轻访问数据库的并发量。比如说如果同一时刻有 100 个线程要访问 key1,那么最终也只会有 1 个线程去数据库中加载数据。
(3)这个模式的缺点是如果并发量不高,那么基本没有效果。所以热点之类的数据就很适合用这个模式。
附一:删除缓存和延迟双删
1,删除缓存
(1)删除缓存这算是在业务中比较常见的用法,也就是在更新数据的时候先更新数据库,然后将缓存删除。
(2)这种做法可以是业务代码来控制删除操作,也可以结合 Write Through 使用。让缓存去更新数据库,然后缓存自己删除自己的数据。
(3)而且删除缓存之后会使缓存命中率下降,也算是一个隐患。如果偶尔出现写频繁的场景,导致缓存一直被删除,那么就会使性能显著下降。缓存未命中回查数据库叠加写操作,数据库压力会很大。
(4)这个模式依旧没有解决数据一致性的问题,但是它的一致性问题不是源自两个线程同时更新数据,而是源自一个线程更新数据,一个线程缓存未命中回查数据库。为了避免这种缓存不一致的问题,又有了下面的延迟双删模式。
2,延迟双删
(1)延迟双删类似于删除缓存的做法,它在第一次删除操作之后设定一个定时器,在一段时间之后再次执行删除。
(2)第二次删除就是为了避开删除缓存中的读写导致数据不一致的场景。
(3)还有一种不一致的可能如下图。但是这种可能性只是存在理论中,因为两次删除的时间间隔很长,不至于出现图片里的这种情况。
附二:模式选择建议
任何一种缓存模式都有各自的优势和缺陷,相对来说延迟双删比较适用大多数项目。因为它的一致性问题不是很严重。虽然会降低缓存的命中率,如果业务并发没有特别高,写请求少的的化。命中率降低一点点是完全可以接受的。
全部评论(0)