Redis 做缓存常见的几个问题总结

image-20211231113023088

1. 数据一致性

我们知道,Redis 主要是用来做缓存使用,只要使用到缓存,无论是本地内存做缓存还是使用 Redis 做缓存,那么就会存在数据同步的问题。

一般情况下,我们都是先读缓存数据,缓存数据有,则立即返回结果;如果没有数据,则从数据库读数据,并且把读到的数据同步到缓存里,提供下次读请求返回数据

image-20220110113731468

这样能有效减轻数据库压力,但是如果修改删除数据库中的数据,而内存是无法感知到数据在数据库的修改。这样就会造成数据库中的数据与缓存中数据不一致的问题,那该如何解决呢?

通常的方案有以下几种:

  1. 先更新缓存,在更新数据库;
  2. 先更新数据库,在更新缓存;
  3. 先删除缓存,在更新数据库;
  4. 先更新数据库,在删除缓存。

1.1 先更新缓存,在更新数据库

这个方案我们一般不考虑。原因是更新缓存成功,更新数据库出现异常了,导致缓存数据与数据库数据完全不一致,而且很难察觉,因为缓存中的数据一直都存在。

1.2 先更新数据库,在更新缓存

这个方案也我们一般不考虑,原因跟第一个一样,数据库更新成功了,缓存更新失败,同样会出现数据不一致问题。

也就是说对于更新缓存来说,一般都不考虑,主要从下面 2 点考虑:

1、并发问题

如果同时有请求 A 和请求 B 进行更新操作,那么会出现:

  • 线程 A 更新数据库;
  • 线程 B 更新数据库;
  • 线程 B 更新缓存;
  • 线程 A 更新缓存。

这就出现请求 A 更新缓存应该比请求 B 更新缓存早才对,但是因为网络等原因,B 却比 A 更早更新了缓存。这就导致了脏数据,因此不考虑。

2、业务场景问题

如果你是一个写数据库场景比较多,而读数据场景比较少的业务需求,采用这种方案就会导致,数据压根还没读到,缓存就被频繁的更新,浪费性能。

其次很多时候,在复杂点的缓存场景,缓存不单单是数据库中直接取出来的值。比如可能更新了某个表的一个字段,然后其对应的缓存,是需要查询另外两个表的数据并进行运算,才能计算出缓存最新的值的。

而且是不是说,每次修改数据库的时候,都一定要将其对应的缓存更新一份,也许有的场景是这样,但是对于比较复杂的缓存数据计算的场景,就不是这样了。如果你频繁修改一个缓存涉及的多个表,缓存也频繁更新。但是问题在于,这个缓存到底会不会被频繁访问到?

举个例子:一个缓存涉及的表的字段,在 1 分钟内就修改了 20 次,或者是 100 次,那么缓存更新 20 次、100次,但是这个缓存在 1 分钟内只被读取了 1 次,有大量的冷数据。

实际上,如果你只是删除缓存的话,那么在 1 分钟内,这个缓存不过就重新计算一次而已,开销大幅度降低。用到缓存才去算缓存。

其实删除缓存,而不是更新缓存,就是一个 Lazy 计算的思想,不要每次都重新做复杂的计算,不管它会不会用到,而是让它到需要被使用的时候再重新计算。

说到底是选择更新缓存还是淘汰缓存呢,主要取决于更新缓存的复杂度,更新缓存的代价很小,此时我们应该更倾向于更新缓存,以保证更高的缓存命中率,更新缓存的代价很大,此时我们应该更倾向于淘汰缓存。但是淘汰缓存操作简单,并且带来的副作用只是增加了一次cache miss,所以一般作为通用的处理方式。

1.3 先删除缓存,在更新数据库

该方案也会出问题,具体出现的原因如下:

  1. 如果有两个请求,请求 A(更新操作) 和请求 B(查询操作);
  2. 请求 A 会先删除 Redis 中的数据,然后去数据库进行更新操作;
  3. 此时请求 B 看到 Redis 中的数据时空的,会去数据库中查询该值,补录到 Redis 中。

但是此时请求 A 并没有更新成功,或者事务还未提交,请求 B 去数据库查询得到旧值,那么这时候就会产生数据库和 Redis 数据不一致的问题。

那么如何解决呢?最简单的解决办法就是延时双删的策略,即:

  1. 先淘汰缓存;
  2. 再写数据库;
  3. 休眠 1 秒,再次淘汰缓存。

这种做法可以将 1s 内造成的造数据删除。

那么,这个 1 秒怎么确定的,具体该休眠多久呢?

针对上面的情形,自行评估自己的项目的读数据业务逻辑的耗时。然后写数据的休眠时间则在读数据业务逻辑的耗时基础上,加几百 ms 即可。这么做的目的,就是确保读请求结束,写请求可以删除读请求造成的缓存脏数据。

1.4 先更新数据库,在删除缓存

这种方式,被称为 Cache Aside Pattern

对于读请求

  1. 先读缓存,在读数据库;
  2. 若存在,则返回;
  3. 若不存在,读取数据库,然后取出数据后放入缓存,同时返回响应。

对于写请求

  1. 先更新数据库;
  2. 再删除缓存。

这种情况依然存在并发问题?

假设有两个请求,一个请求 A 做查询操作,一个请求 B 做更新操作,那么会有如下情形产生:

  1. 缓存刚好失效;
  2. 请求 A 查询数据库,得一个旧值;
  3. 请求 B 将新值写入数据库;
  4. 请求 B 删除缓存;
  5. 请求 A
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

LBXX_1

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值