导读

提起缓存,大家应该都不陌生,开始时,如果你的业务处于起步阶段,流量非常小,那无论是读请求还是写请求,直接操作数据库即可,这时你的架构模型是这样的:

redission创建读写锁 redis读写策略_Redis


但随着业务量的增长,你的项目请求量越来越大,这时如果每次都从数据库中读数据,那肯定会有性能问题。这个阶段通常的做法是,引入「缓存」来提高读性能,架构模型就变成了这样:

redission创建读写锁 redis读写策略_redission创建读写锁_02


这时候就会处出现一个问题:缓存读写策略,即缓存的读写问题,之前只需要读写数据库即可,现在多了缓存,又该如何读写呢,怎么保证缓存一致性呢?下面主要讲解Redis常用的三种缓存策略。

Cache Aside Pattern(旁路缓存模式)

Cache Aside Pattern 是我们平时使用比较多的一个缓存读写模式,比较适合读请求比较多的场景。

Cache Aside Pattern 中服务端需要同时维系 DB 和 cache,并且是以 DB 的结果为准。

下面我们来看一下这个策略模式下的缓存读写步骤。

  • 写 :先更新 DB,然后直接删除 cache
  • 读:从 cache 中读取数据,读取到就直接返回;cache中读取不到的话,就从 DB 中读取数据返回,再把数据放到 cache 中

那么在写数据的过程中,可以先删除 cache ,后更新 DB 么? 答案是不可以。

如果有 2 个线程要并发「读写」数据,可能会发生以下场景:

线程 A 要更新 X = 2(原值 X = 1)
线程 A 先删除缓存
线程 B 读缓存,发现不存在,从数据库中读取到旧值(X = 1)
线程 A 将新值写入数据库(X = 2)
线程 B 将旧值写入缓存(X = 1)
最终 X 的值在缓存中是 1(旧值),在数据库中是 2(新值),发生不一致。

可见,先删除缓存,后更新数据库,当发生「读+写」并发时,还是存在数据不一致的情况

那么先更新数据库,后删除缓存就不会又数据一致性问题了吗?让我们来看下面这个场景:

依旧是 2 个线程并发「读写」数据:

缓存中 X 不存在(数据库 X = 1)
线程 A 读取数据库,得到旧值(X = 1)
线程 B 更新数据库(X = 2)
线程 B 删除缓存
线程 A 将旧值写入缓存(X = 1)
最终 X 的值在缓存中是 1(旧值),在数据库中是 2(新值),也发生不一致。

这种情况「理论」来说是可能发生的,但实际真的有可能发生吗?

其实概率「很低」,这是因为它必须满足 3 个条件:

  • 缓存刚好已失效
  • 读请求 + 写请求并发
  • 更新数据库 + 删除缓存的时间(步骤 3-4),要比读数据库 + 写缓存时间短(步骤 2 和 5)

仔细想一下,条件 3 发生的概率其实是非常低的。

因为写数据库一般会先「加锁」,所以写数据库,通常是要比读数据库的时间更长的

这么来看,「先更新数据库 + 再删除缓存」的方案,是可以保证数据一致性的。

所以,我们应该采用这种方案,来操作数据库和缓存。

Read/Write Through Pattern(读写穿透)

Read/Write Through Pattern 中服务端把 cache 视为主要数据存储,从中读取数据并将数据写入其中。cache 服务负责将此数据读取和写入 DB,从而减轻了应用程序的职责。一般在平时开发过程中很少使用。

写(Write Through):

  • 先查 cache,cache 中不存在,直接更新 DB。
  • cache 中存在,则先更新 cache,然后 cache 服务自己更新 DB(同步更新 cache 和 DB)

读(Read Through):

  • 从 cache 中读取数据,读取到就直接返回 。
  • 读取不到的话,先从 DB 加载,写入到 cache 后返回响应。

在 Cache-Aside Pattern 下,发生读请求的时候,如果 cache 中不存在对应的数据,是由客户端自己负责把数据写入 cache,而 Read-Through Pattern 则是 cache 服务自己来写入缓存的,这对客户端是透明的。

Write Behind Pattern(异步缓存写入)

Write Behind Pattern 和 Read/Write Through Pattern 很相似,两者都是由 cache 服务来负责 cache 和 DB 的读写

但是,两个又有很大的不同:Read/Write Through 是同步更新 cache 和 DB,而 Write Behind Caching 则是只更新缓存,不直接更新 DB,而是改为异步批量的方式来更新 DB。

Write Behind Pattern 下 DB 的写性能非常高,非常适合一些数据经常变化又对数据一致性要求没那么高的场景,比如浏览量、点赞量。

总结

这3 种缓存读写策略各有优劣,不存在最佳,需要我们根据具体的业务场景选择更适合的。如果读多写少,且对于数据一致性要求高,则宜采用旁路缓存模式;如果写多读少,且对于数据一致性要求不高,可以采用异步缓存写入模式。