Redis-6-三种缓存读写策略

缓存策略 描述 优点 缺点 适用场景
旁路缓存 (Cache Aside Pattern) 服务端需要同时维护数据库和缓存,以数据库的结果为准。
读请求先查缓存,没有命中则查数据库并更新缓存。写请求先更新数据库,然后删除缓存。
适合读多写少的场景,缓存命中率高 写操作复杂,存在短暂的数据不一致风险 数据读写比例悬殊、读操作频繁的场景。
读写穿透 (Read/Write Through Pattern) 服务端将缓存视为主要数据存储,所有数据的读写都通过缓存,缓存服务负责将数据同步到数据库 简化了应用程序的职责,数据一致性高。 实现复杂,增加了缓存服务的压力。 数据一致性要求高,且读写操作频繁的场景。
异步缓存写入 (Write Behind Pattern) 与读写穿透类似,但写操作不立即同步到数据库,而是异步批量更新数据库 写操作效率高,减轻了数据库压力。 存在数据丢失风险,数据一致性较低。 写操作频繁且对实时一致性要求不高的场景

1 旁路缓存

Cache Aside Pattern(旁路缓存)适合读请求比较多的场景

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

1.1.1 写

  • 先更新db
  • 直接删除缓存

1.1.2 读

  • 先读缓存
    • 有,则从缓存返回。
    • 没有,从db中读取返回。
  • 再将读取的数据写入缓存

1.1.3 常见问题

1.写,写DB后咋是删缓存不是更新缓存?

写N次,只有最后一次数据的有效,前面写的都会被覆盖。

读不到的时候自然会重建缓存,我们直接删除了,等读的时候重建即可。

2.写,可不可以先删除缓存,再更新DB?

不可以。

前面已经讲了,我们更新的时候,db更新没问题,但是缓存呢不做更新,直接删除。

现在的问题就是,更新db和删除缓存这两个操作的先后问题。

假设我们数据库中有个值name=yang,我们准备更新成yang37

2.1 (×)先删除缓存,再更新db。

这个情况,主要是考虑并发场景,在删除缓存 -> 更新db期间,db中的始终是旧数据。

如果线程2发生了读取请求,会导致数据不一致。

  • 线程1:尝试更新name从yang到yang37
  • 线程2:尝试读取name的值

image-20240609123026058

最终我们可以看到,缓存中的数据还是旧数据yang,而db中是正确的yang37,数据不一致

问题的根源在哪里,在于你线程2读取缓存是很快的,缓存中没值,必然触发缓存的重建。

  • 线程1的db更新完了,那就皆大欢喜,即左边线程1整个期间没有并发问题。
  • 线程1的db没更新完,db中的必定是旧数据,重建的缓存值也必定成旧数据,导致出问题。

注意图最下面的两个框框。最重要的是,在缓存有效期内,你缓存的一直是脏数据,如果这个缓存没过期时间,那么缓存中的数据始终有问题。

这个问题出现的概率大吗?即左边删除缓存 -> 更新db的期间能不能包裹住右边边。

image-20240609133539141

实际上,db查询数据比更新数据快,这个情况很容易出现。

2.2 (√) 先更新db,再删除缓存。

接着上面的,来看下先更新db的。

image-20240609125453219

嗯,这里,如果更新db -> 删除缓存期间有读取请求,我们的线程2还是会读取到脏数据。

但是,咱们最终缓存中的数据不存在,下次查询会触发重建。

先删除缓存再更新db的方式,逻辑上就存在问题了,咱们这个影响程度小的多。

咱们只是在这期间有短暂的不一致问题,不会导致最终状态下的数据不一致。

但是,还是可能有缓存最终不一致的情况,就是刚好缓存失效了,例如下图。

image-20240609131314236

这里的问题点是什么,线程2的更新缓存操作覆盖了线程1的更新db+删除缓存的操作。

它必须要完整的包裹住线程1的更新操作和删除操作。

  • 如果线程1更新操作在线程2查询db的操作之前,那么此时查询到的数据已经被更新了,重建的缓存值刚好是正确的。
  • 如果线程1的删除操作发生在线程2的更新缓存之后,那么此时缓存中的数据已经被删除了,下次重建会成为正确的值,也符合我们的预期。

我们注意下哈,先决条件是线程2从db查到旧数据之后,线程1开始更新db了。

然后,必须完完整整的包裹住这整个操作,必须得下面这样。

image-20240609132053259

即,哪怕我像下面这样两种情况,它也不会有问题,必须满足上面包裹住的场景,才可能有问题。

image-20240609132534799 image-20240609132659946

实际上,db查询数据比更新数据快,所以这个更新db+删除缓存的时间很难比查询db+更新缓存的时间长

别忘了我们还有并发+缓存失效的背景,这所有因素组合在一起才可能有这个数据不一致的情况,所以出现问题的概率是很低的。

你说,咱们方案1中过期时间的场景都还没讨论呢。哈哈,其实不用讨论了,你想啊,方案1还没说过期就有很大隐患了,还不要说咱们现在这里的讨论的方案2+过期的场景。

综上呢,咱们的正确方案是:先更新db,再删除缓存。

1.2 读写穿透

Read/Write Through Pattern 中服务端把 cache 视为主要数据存储,从中读取数据并将数据写入其中。

cache 服务负责将此数据读取和写入 db,从而减轻了应用程序的职责。

1.3 异步缓存写入

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

读写穿透是同步更新 cache 和 db,而异步缓存写入则是只更新缓存,不直接更新 db,而是改为异步批量的方式来更新 db。

posted @ 2024-06-09 13:49  羊37  阅读(66)  评论(0编辑  收藏  举报