虾皮一面:如何保证数据双写一致?

年关将至,又到了准备面试跳槽的季节了。据不完全统计,跳槽是涨薪最快的方式,没有之一。而跳槽成功与否的关键是"面试",所以认真准备面试 = 快速涨薪。

准备面试,自然就少不了刷面试真题了,而今天这份刚出炉的虾皮 Java 后端面试题就非常典型,它的难度适中,面试结构分为:半小时八股 + 半小时手撕代码,是目前典型的大公司的面试方式,真题如下: 面试问题很多,一篇文章肯定是写不完的(大部分面试题可以在我的网站上找到答案:www.javacn.site,咱们今天就拿里面最经典,最常见的面试题来聊一聊:,咱们今天就拿里面最经典,最常见的面试题来聊一聊:如何保证缓存和数据库的双写一致性?

1.什么是双写一致性?

在分布式系统中,数据库和缓存会搭配一起使用,以此来保证程序的整体查询性能。

也就说,分布式系统为了缓解数据库查询的压力,会将查出来的数据保存在缓存中,下次再查询时,直接走缓存系统,而不再查询数据库,这样就极大的提高了整体的查询性能。

1.1 为什么缓存比数据库快?

缓存之所以比数据库快的主要原因有以下 3 点:

  1. 内存访问速度快:缓存通常将数据存储在内存中,而数据库将数据存储在磁盘上。相比于磁盘访问,内存访问速度更快,可以达到纳秒级别的读取速度,远远快于数据库的毫秒级别的读取速度。
  2. IO 操作次数少:数据库通常需要进行磁盘 IO 操作,包括读取和写入磁盘数据。而缓存将数据存储在内存中,避免了磁盘 IO 的开销。内存访问不需要进行磁盘寻址和机械运动,相对来说速度更快。
  3. 特殊的数据结构:缓存的数据结构通常为 key-value 形式的,也就是说缓存可以做到任何数据量级下的查询数据复杂度为 O(1),所以它的查询效率是非常高的;而数据库采用的是传统数据结构设计,可能需要查询二叉树、或全文搜索、或回表查询等操作,所以其查询性能是远低于缓存系统的。

1.2 缓存一致性问题

虽然缓存可以极大的提高查询性能,但同时也带来的新的问题:数据库和缓存一致性的问题。

具体来说,在一个常见的应用场景中,当更新数据库的操作完成后,需要同步更新缓存,以保证缓存中的数据与数据库中的数据保持一致。然而,由于数据库和缓存是两个不同的组件,它们的数据更新操作是异步的,可能存在以下问题:

  1. 数据延迟:数据库更新和缓存更新之间存在时间延迟,导致缓存中的数据不是最新的。这可能会引起数据的不一致,当其他请求读取数据时,可能会读取到旧的数据。
  2. 更新失败:在尝试更新缓存时,可能出现更新失败的情况。例如,缓存节点暂时不可用,网络故障等。如果更新缓存失败而未进行适当的处理,也会导致数据库和缓存之间的数据不一致。

也就说,因为以上原因,可能会导致 A 用户和 B 用户执行了同一个查询操作,但是得到了完全不同的结果,这就是数据库和缓存的一致性问题。

2.如何解决一致性问题?

解决缓存和数据库一致问题的常见解决方案有以下 4 种:

  1. 先修改数据库,后更新缓存。
  2. 先更新缓存,后修改数据库。
  3. 先修改数据库,后删除缓存。
  4. 先删除缓存,后修改数据库。

然而,前 3 种解决方案,有同一个问题,也就是当第一步操作执行完之后,第二步未执行的情况下,就会导致数据库和缓存的一致性问题,例如第一步执行完之后,系统掉电了,那么一致性问题就会一直存在。

相比之下,第 4 种解决方案(先删除缓存,后修改数据库)相比于前三种解决方案更有优势,起码它保证了双方都未执行成功,那么从数据一致性层面来讲,第 4 种方案起码保证了一定的数据一致性,然而第 4 种执行方案依然存在其他问题,例如以下这几个:

  1. 业务完整性问题:程序只执行了一半,第一步执行完了但第二步未执行的情况。
  2. 并发保存旧值的问题 :在并发环境下,第四种方案可能会导致缓存保存旧值的情况,例如以下执行情况:

3.最终解决方案

所以,想要最大程度的双写一致性问题的最终解决方案是:消息队列 + 延迟双删策略

3.1 为什么要使用消息队列?

因为消息队列里面有消息确认机制,它可以保证我们执行完第一步之后,即时掉电重启的情况,依然可以执行后续的流程,因为之前的消息,未进行消息确认,所以程序重启之后,会继续执行后续的流程,这样就保证了业务执行的完整性。

3.2 什么是延迟双删?

延迟双删指的是删除两次缓存(并且最后一次是延迟删除),具体执行流程如下:

  1. 删除缓存
  2. 更新数据库
  3. 延迟一会再删除缓存

最后一次延迟删除缓存的原因是,为了避免上面因为并发问题导致保存旧值的情况发生,所以会延迟一段时间之后再进行删除操作。这样即使有并发问题,也能最大限度的解决保存旧值的情况,因为是延迟之后删除的,所以即使因为并发问题保存了旧值,但延迟一段时间之后旧值就会被删除,那么这样就自然而然的保证了数据库和缓存的最终一致性。

小结

数据库和缓存双写一致性问题是一道经典的面试题,最初解决方案是先更新数据库、再删除缓存,然而如果发生掉电情况,只执行了前一步操作,那么缓存和数据库就出现了不一致性的问题。为了解决这个问题,所以通常会采用延迟双删 + 消息队列来保证业务的完整执行和数据一致性问题。

本文已收录到我的面试小站 www.javacn.site,其中包含的内容有:Redis、JVM、并发、并发、MySQL、Spring、Spring MVC、Spring Boot、Spring Cloud、MyBatis、设计模式、消息队列等模块。

相关推荐
魔道不误砍柴功2 小时前
Java 中如何巧妙应用 Function 让方法复用性更强
java·开发语言·python
NiNg_1_2342 小时前
SpringBoot整合SpringSecurity实现密码加密解密、登录认证退出功能
java·spring boot·后端
闲晨2 小时前
C++ 继承:代码传承的魔法棒,开启奇幻编程之旅
java·c语言·开发语言·c++·经验分享
Chrikk3 小时前
Go-性能调优实战案例
开发语言·后端·golang
幼儿园老大*3 小时前
Go的环境搭建以及GoLand安装教程
开发语言·经验分享·后端·golang·go
canyuemanyue3 小时前
go语言连续监控事件并回调处理
开发语言·后端·golang
杜杜的man3 小时前
【go从零单排】go语言中的指针
开发语言·后端·golang
测开小菜鸟3 小时前
使用python向钉钉群聊发送消息
java·python·钉钉
P.H. Infinity4 小时前
【RabbitMQ】04-发送者可靠性
java·rabbitmq·java-rabbitmq
生命几十年3万天4 小时前
java的threadlocal为何内存泄漏
java