字节面试官:高并发场景下,如何保证缓存与数据库一致性?

Posted 敲代码的程序狗

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了字节面试官:高并发场景下,如何保证缓存与数据库一致性?相关的知识,希望对你有一定的参考价值。

问题分析

我们日常开发中,对于缓存用的最多的场景就像下图一样,可能仅仅是对数据进行缓存,减轻数据库压力,缩短接口响应时间。

这种方案在不需要考虑高并发得去写缓存,高并发得读写缓存时,是不会有问题,但是如果是在高并发场景下,要保证缓存和数据库的一致性,至少需要解决以下问题:

高并发写时的数据不一致问题

高并发读写时,请求执行各步骤的顺序是不可控的。假设此时有一个请求 A,B 都在在执行写流程,请求 A 是需要将某个数据改成 1,请求 B 是需要将某个数据改为 2,执行操作如下时就会导致数据不一致的问题:

1.请求 A 执行操作 1.1 删除缓存。

2.请求 A 执行操作 1.2 更新数据库,将值改为 1。

3.请求 B 执行操作 1.1 删除缓存。

4.请求 B 执行操作 1.2 更新数据库,将值改为 2

5.假设说请求 B 所在服务器网络延迟比较低,请求 B 先更新缓存,此时缓存中的 key 对应的 value 是 2。

6.请求 A 更新缓存,将缓存中 B 更新的数据进行覆盖,将 key 对应的值改为 1。

此时数据库中是 B 修改后的数据,值为 2,而缓存中的数据是 1,这样在缓存过期钱,用户读到的都是脏数据,与数据库不一致。

高并发读写时的数据不一致的问题

高并发读写时,请求执行各步骤的顺序是不可控的。假设此时有一个请求 A 在执行写流程,将原值由 1 改成 2,请求 B 执行读流程,执行操作如下时就会导致数据不一致的问题:

1.写请求 A 执行 1.1 操作删除缓存 key,value 是原值 1。

2.读请求 B 执行 2.1 操作发现缓存中没有数据,就去执行 2.2 操作读数据库,读到旧数据,值为 1。

3.写请求 A 执行 1.2 操作更新数据库,将数据由 1 改为 2。

4.写请求 A 执行 1.3 操作更新缓存,此时缓存中的数据 key 对应的 value 是 2。

5.读请求 B 执行 2.3 操作更新缓存,将之前读到的旧数据 1 设置到缓存中,此时缓存中的数据 key 对应的 value 是 1。

所以如果说读请求 B 所在服务器网络延迟比较高,去执行 2.3 操作比写请求 A 晚,就会导致写请求 A 更新完缓存后,读请求 B 使用之前读到的旧数据去更新缓存,此时缓存中数据就与数据库中的不一致。

解决方案

保证数据一致性,网上有很多种方案,例如:

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

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

3.先删除缓存,再更新数据库,然后异步延迟一段时间再去删一次缓存。

但是这些方案都是存在各种各样的问题,这里篇幅有限,只给出目前相对正确的三套方案,目前的这些方案也有自己的局限性。

方案 1.写请求串行化

写请求

1.写请求更新之前先获取分布式锁,获得之后才能去数据库更新这个数据,获取不到就进行等待,超时后就返回更新失败。

2.更新完之后去刷新缓存,如果刷新失败,放到内存队列中进行重试(重试时取数据库最新数据更新缓存)。

读请求

读请求发现缓存中没有数据时,直接去读取数据库,读完更新缓存。

总结

这种技术方案通过对写请求的实现串行化来保证数据一致性,但是会导致吞吐量变低。比较适合银行相关的业务,因为对于银行项目来说,保证数据一致性比可用性更加重要,就像是去存款机存钱,取钱时,为了保证账户安全,都是会让用户执行操作后,等待一段时间才能获得反馈,这段时间其实取款机是不可用的。

方案 2.先更新数据库,异步删除缓存,删除失败后重试

1.先更新数据库

2.异步删除缓存(如果数据库是读写分离的,那么删除缓存时需要延迟删除,否则可能会在删除缓存时,从库还没有收到更新后的数据,其他读请求就去从库读到旧数据然后设置到缓存中。)

3.删除缓存失败时,将删除的 key 放到内存队列或者是消息队列中进行异步重试

发散思考

 在更新完数据库后,我们为什么不直接更新,而是采用删除缓存呢?

这是因为直接更新缓存的话,在高并发场景下,有多个更新请求时,难以保证后更新数据库的请求会后更新缓存,也就是上面的高并发写问题。如果采用删除缓存,可以让下次读时读取数据库,更新缓存,保证一致性。

方案 3.业务项目更新数据库,其他项目订阅 binlog 更新

1.业务项目直接更新数据库。

2.cannal 项目会读取数据库的 binlog,然后解析后发消息到 kafka。

3.然后缓存更新项目订阅 topic,从 kafka 接收到更新数据库操作的消息后,更新缓存,更新缓存失败时,新建异步线程去重试或者将操作发到消息队列,后续再进行处理

总结:

但是这种方案在更新数据库后,缓存中还是旧值,必须等缓存更新项目消费消息后,更新缓存,缓存中才是最新值。所以更新操作完成与更新生效之间会有一定的延迟。

最后

作为阅读福利,我也准备了一些高并发相关资料(包含脑图、手写pdf、面试真题等)现在免费分享给阅读到本篇文章的Java程序员朋友们,需要的可【点击此处】领取!下面是部分资料展示。

书籍资料

 面试资料

 

 

 核心知识点

 

 

以上是关于字节面试官:高并发场景下,如何保证缓存与数据库一致性?的主要内容,如果未能解决你的问题,请参考以下文章

大厂面试01期高并发场景下,如何保证缓存与数据库一致性?

大厂面试01期高并发场景下,如何保证缓存与数据库一致性?

亿级流量高并发场景下,如何保证缓存与数据库的双写一致性?

面试官:高并发场景下,你们是怎么保证数据的一致性的?

亿级流量高并发场景下,如何解决一致性问题?

面试官问:如何优化高并发相关的业务,你能回答的上来吗?