浅析缓存一致性的解析方案

各位同学们平时开发的时候除了使用到数据库(这里以mysql为例)还会用到相关的缓存(这里以redis为例)操作。
举一个常用的场景当我们写的接口性能相对比较慢的时候(高并发场景需要响应速度很快)为了保证性能的达标,我们一般会结合用缓存来提高响应,比如redis号称每秒响应次数达到10W级。
那么问题来了,如何保证数据库和缓存的接口一致性呢,本文就来简要的分析一下目前市面上大家常用的6种不通的缓存一致性解决方式。
在这里插入图片描述

一、方案介绍

方案一、先写数据库,在写缓存

在这里插入图片描述

请求 A、B 都是先写数据库,然后再写缓存,在高并发情况下,如果请求 A 在写缓存时卡了一会,请求 B 已经依次完成数据的更新,就会出现图中的问题。
对于读请求,先去读缓存,如果没有,再去读 数据库,但是读请求不会再回写缓存。

方案二、先写缓存,再写数据库

在这里插入图片描述
方案二和方案一相反,先写缓存,在写数据库,

方案三、先删除缓存,再写数据库

在这里插入图片描述
这里的请求 A 是更新请求,但是请求 B 是读请求,且请求 B 的读请求会回写数据库。
请求 A 先删除缓存,可能因为卡顿,数据一直没有更新到数据库,导致两者数据不一致。这种情况出现的概率比较大,因为请求 A 更新数据库可能耗时会比较长,而请求 B 的前两步都是查询,会非常快。

方案四、先删除缓存,再写数据库,再删除缓存

对于“先删除缓存,再写 数据库”,如果要解决最后的不一致问题,其实再对 缓存重新删除即可,这个也是常说的“缓存双删”。
在这里插入图片描述
“删除缓存 5”必须在“回写缓存5”后面,怎么保证一定是在后面呢?
走异步串行化删除,在任务处理完成后把最后“删除缓存 5”放到异步队列中,进行延迟删除,确保最后的一致性。
在这里插入图片描述
异步删除对线上业务无影响,串行化处理保障并发情况下正确删除。
如果双删失败怎么办?建议走重试机制,方案一是借助消息队列的重试机制;方案二也可以自己整个表,记录重试次数。

方案五、先写数据库,再删除缓存

在这里插入图片描述
对于上面这种情况,对于第一次查询,请求 B 查询的数据是 5,但是 数据库中的数据是 6,只存在这一次不一致的情况,对于不是强一致性要求的业务,可以容忍。(那什么情况下不能容忍呢,比如秒杀业务、库存服务等。)

当请求 B 进行第二次查询时,因为没有命中缓存,会重新查一次数据库,然后再回写到缓存。
在这里插入图片描述
这里需要满足 2 个条件:

  • 缓存刚好自动失效;
  • 请求 B 从数据库查出 5,回写缓存的耗时,比请求 A 写数据库,并且删除缓存的还长。

对于第二个条件,我们都知道更新数据库肯定比查询耗时要长,所以出现这个情况的概率很小,同时满足上述条件的情况更小。

方案六、先写数据库,通过 Binlog,异步更新缓存

这种方案,主要是监听数据库(mysql)的 Binlog,然后通过异步的方式,将数据更新到缓存,这种方案有个前提,查询的请求,不会回写缓存。
在这里插入图片描述
这个方案,会保证数据库和缓存的最终一致性,但是如果中途请求 B 需要查询数据,如果缓存无数据,就直接查数据库;如果缓存有数据,查询的数据也会存在不一致的情况。

所以这个方案,是实现最终一致性的终极解决方案,但是不能保证实时性。

二、各方案的比较

方案一、先写数据库,在写缓存

  • 这种方案不建议用,万一数据库挂了,你把数据写到缓存,数据库无数据,影响肯定是灾难性的,如果真要使用,建议增加重试机制试试,如果操作失败的话;

方案二、先写缓存,再写数据库

  • 这种只适合那种并发量、一致性要求不高的项目,比如那种政企项目,之前做过的政企项目就是经常这么搞,但是不建议这么做;
  • 当缓存瞬间不可用的情况,需要及时告警,做后续处理。

方案三、先删除缓存,再写数据库

  • 这种方法……好像没使用过,一般都是用方案五。

方案四、先删除缓存,再写数据库,再删除缓存

  • 这种方式虽然可行,但是有点负责化了,还要搞个消息队列去异步删除 Redis。简单点的可以用java的异步线程去做延时删除。

方案五、先写数据库,再删除缓存

  • 比较推荐这种方式,删除 Redis 如果失败,可以再多重试几次,否则报警出来;
  • 这个方案,是实时性中最好的方案,在一些高并发场景中,推荐这种。

方案六、先写数据库,通过 Binlog,异步更新缓存

  • 对于异地容灾、数据汇总等,建议会用这种方式,比如 binlog + kafka,数据的一致性也可以达到秒级;
  • 纯粹的高并发场景,不建议用这种方案,比如抢购、秒杀等。

三、总结

  • 实时一致性方案:采用“先写数据库,再删除缓存”的策略,这种情况虽然也会存在两者不一致,但是需要满足的条件有点苛刻,所以是满足实时性条件下,能尽量满足一致性的最优解。
  • 最终一致性方案:采用“先写数据库,通过Binlog,异步更新缓存”,可以通过 Binlog,结合消息队列异步更新缓存,是最终一致性的最优解。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ThreadLocal 是 Java 中的一个类,它提供了一种线程局部变量的机制。线程局部变量是指每个线程都有自己的变量副本,每个线程对该变量的访问都是独立的,互不影响。 ThreadLocal 主要用于解决多线程并发访问共享变量时的线程安全问题。在多线程环境下,如果多个线程共同访问同一个变量,可能会出现竞争条件,导致数据不一致或者出现线程安全问题。通过使用 ThreadLocal,可以为每个线程提供独立的副本,从而避免了线程安全问题。 ThreadLocal 的工作原理是,每个 Thread 对象内部都维护了一个 ThreadLocalMap 对象,ThreadLocalMap 是一个 key-value 结构,其中 key 是 ThreadLocal 对象,value 是该线程对应的变量副本。当访问 ThreadLocal 的 get() 方法时,会根据当前线程获取到对应的 ThreadLocalMap 对象,并从中查找到与 ThreadLocal 对象对应的值。如果当前线程尚未设置该 ThreadLocal 对象的值,则会通过 initialValue() 方法初始化一个值,并将其存入 ThreadLocalMap 中。当访问 ThreadLocal 的 set() 方法时,会将指定的值存入当前线程对应的 ThreadLocalMap 中。 需要注意的是,ThreadLocal 并不能解决共享资源的并发访问问题,它只是提供了一种线程内部的隔离机制。在使用 ThreadLocal 时,需要注意合理地使用,避免出现内存泄漏或者数据不一致的情况。另外,由于 ThreadLocal 使用了线程的 ThreadLocalMap,因此在使用完 ThreadLocal 后,需要手动调用 remove() 方法清理对应的变量副本,以防止内存泄漏。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值