Skip to content

Commit 8054a9e

Browse files
committed
change image
1 parent 5a405d8 commit 8054a9e

4 files changed

Lines changed: 104 additions & 2 deletions

File tree

README.md

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@ Github项目主页:https://github.com/NotFound9/interviewGuide
2121
## 目录
2222

2323
- [首页](README.md)
24+
- [大厂面试系列](docs/BATInterview.md)
25+
-[【大厂面试01期】高并发场景下,如何保证缓存与数据库一致性?](docs/BATInterview.md#【大厂面试01期】高并发场景下,如何保证缓存与数据库一致性?)
2426
* Java
2527
- [基础](docs/JavaBasic.md)
2628
- [1.Java中的多态是什么?](docs/JavaBasic.md#Java中的多态是什么?)
@@ -70,6 +72,7 @@ Github项目主页:https://github.com/NotFound9/interviewGuide
7072
- [7.阻塞与非阻塞的区别是什么?](docs/RedisBasic.md#阻塞与非阻塞的区别是什么?)
7173
- [8.如何解决Redis缓存穿透问题?](docs/RedisBasic.md#如何解决Redis缓存穿透问题?)
7274
- [9.如何解决Redis缓存雪崩问题?](docs/RedisBasic.md#如何解决Redis缓存雪崩问题?)
75+
- [10.如何解决缓存与数据库的数据一致性问题?](docs/RedisBasic.md#如何解决缓存与数据库的数据一致性问题?)
7376
- [数据结构](docs/RedisDataStruct.md)
7477
- [1.Redis常见的数据结构有哪些?](docs/RedisDataStruct.md#Redis常见的数据结构有哪些?)
7578
- [2.谈一谈你对Redis中简单动态字符串的理解?](docs/RedisDataStruct.md#谈一谈你对Redis中简单动态字符串的理解?)

_sidebar.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
- [首页](README.md)
2+
- [大厂面试系列](docs/BATInterview.md)
23
* Java
34
- [基础](docs/JavaBasic.md)
45
* 容器

docs/BATInterview.md

Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
(PS:扫描[首页里面的二维码](README.md)进群,分享我自己在看的技术资料给大家,希望和大家一起学习进步!)
2+
3+
#### [【大厂面试01期】高并发场景下,如何保证缓存与数据库一致性?](#【大厂面试01期】高并发场景下,如何保证缓存与数据库一致性?)
4+
5+
## 【大厂面试01期】高并发场景下,如何保证缓存与数据库一致性?
6+
7+
### 问题分析
8+
我们日常开发中,对于缓存用的最多的场景就像下图一样,可能仅仅是对数据进行缓存,减轻数据库压力,缩短接口响应时间。
9+
![](https://user-gold-cdn.xitu.io/2020/6/1/1726eba6a331db46?w=1475&h=576&f=png&s=240653)
10+
这种方案在不需要考虑高并发得去写缓存,高并发得读写缓存时,是不会有问题,但是如果是在高并发场景下,要保证缓存和数据库的一致性,至少需要解决以下问题:
11+
#### 高并发写时的数据不一致问题
12+
高并发读写时,请求执行各步骤的顺序是不可控的。假设此时有一个请求A,B都在在执行写流程,请求A是需要将某个数据改成1,请求B是需要将某个数据改为2,执行操作如下时就会导致数据不一致的问题:
13+
14+
1.请求A执行操作1.1删除缓存。
15+
16+
2.请求A执行操作1.2更新数据库,将值改为1。
17+
18+
3.请求B执行操作1.1删除缓存。
19+
20+
4.请求B执行操作1.2更新数据库,将值改为2
21+
22+
5.假设说请求B所在服务器网络延迟比较低,请求B先更新缓存,此时缓存中的key对应的value是2。
23+
24+
6.请求A更新缓存,将缓存中B更新的数据进行覆盖,将key对应的值改为1。
25+
26+
此时数据库中是B修改后的数据,值为2,而缓存中的数据是1,这样在缓存过期钱,用户读到的都是脏数据,与数据库不一致。
27+
28+
#### 高并发读写时的数据不一致的问题
29+
高并发读写时,请求执行各步骤的顺序是不可控的。假设此时有一个请求A在执行写流程,将原值由1改成2,请求B执行读流程,执行操作如下时就会导致数据不一致的问题:
30+
31+
1.写请求A执行1.1操作删除缓存key,value是原值1。
32+
33+
2.读请求B执行2.1操作发现缓存中没有数据,就去执行2.2操作读数据库,读到旧数据,值为1。
34+
35+
3.写请求A执行1.2操作更新数据库,将数据由1改为2。
36+
37+
4.写请求A执行1.3操作更新缓存,此时缓存中的数据key对应的value是2。
38+
39+
5.读请求B执行2.3操作更新缓存,将之前读到的旧数据1设置到缓存中,此时缓存中的数据key对应的value是1。
40+
41+
所以如果说读请求B所在服务器网络延迟比较高,去执行2.3操作比写请求A晚,就会导致写请求A更新完缓存后,读请求B使用之前读到的旧数据去更新缓存,此时缓存中数据就与数据库中的不一致。
42+
43+
### 解决方案
44+
保证数据一致性,网上有很多种方案,例如:
45+
46+
1.先删除缓存,再更新数据库。
47+
48+
2.先更新数据库,再删除缓存。
49+
50+
3.先删除缓存,再更新数据库,然后异步延迟一段时间再去删一次缓存。
51+
52+
但是这些方案都是存在各种各样的问题,这里篇幅有限,只给出目前相对正确的三套方案,目前的这些方案也有自己的局限性。
53+
54+
### 方案1.写请求串行化
55+
#### 写请求
56+
1.写请求更新之前先获取分布式锁,获得之后才能去数据库更新这个数据,获取不到就进行等待,超时后就返回更新失败。
57+
58+
2.更新完之后去刷新缓存,如果刷新失败,放到内存队列中进行重试(重试时取数据库最新数据更新缓存)。
59+
#### 读请求
60+
读请求发现缓存中没有数据时,直接去读取数据库,读完更新缓存。
61+
62+
#### 总结
63+
这种技术方案通过对写请求的实现串行化来保证数据一致性,但是会导致吞吐量变低。比较适合银行相关的业务,因为对于银行项目来说,保证数据一致性比可用性更加重要,就像是去存款机存钱,取钱时,为了保证账户安全,都是会让用户执行操作后,等待一段时间才能获得反馈,这段时间其实取款机是不可用的。
64+
65+
### 方案2.先更新数据库,异步删除缓存,删除失败后重试
66+
67+
![](https://user-gold-cdn.xitu.io/2020/6/1/1726ef4a894cf529?w=656&h=426&f=png&s=32146)
68+
69+
1.先更新数据库
70+
71+
2.异步删除缓存(如果数据库是读写分离的,那么删除缓存时需要延迟删除,否则可能会在删除缓存时,从库还没有收到更新后的数据,其他读请求就去从库读到旧数据然后设置到缓存中。)
72+
73+
3.删除缓存失败时,将删除的key放到内存队列或者是消息队列中进行异步重试
74+
#### 发散思考
75+
> 在更新完数据库后,我们为什么不直接更新,而是采用删除缓存呢?
76+
77+
这是因为直接更新缓存的话,在高并发场景下,有多个更新请求时,难以保证后更新数据库的请求会后更新缓存,也就是上面的高并发写问题。如果采用删除缓存,可以让下次读时读取数据库,更新缓存,保证一致性。
78+
79+
80+
### 方案3.业务项目更新数据库,其他项目订阅binlog更新
81+
![](https://user-gold-cdn.xitu.io/2020/6/1/1726ef4d6cc50204?w=743&h=505&f=png&s=52081)
82+
1.业务项目直接更新数据库。
83+
84+
2.cannal项目会读取数据库的binlog,然后解析后发消息到kafka。
85+
86+
3.然后缓存更新项目订阅topic,从kafka接收到更新数据库操作的消息后,更新缓存,更新缓存失败时,新建异步线程去重试或者将操作发到消息队列,后续再进行处理。
87+
88+
#### 总结:
89+
90+
但是这种方案在更新数据库后,缓存中还是旧值,必须等缓存更新项目消费消息后,更新缓存,缓存中才是最新值。所以更新操作完成与更新生效之间会有一定的延迟。
91+
92+
参考链接:
93+
94+
https://www.cnblogs.com/-wenli/p/11474164.html
95+
96+
https://www.cnblogs.com/rjzheng/p/9041659.html

docs/RedisBasic.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,8 @@
1414
#### [7.阻塞与非阻塞的区别是什么?](#阻塞与非阻塞的区别是什么?)
1515
#### [8.如何解决Redis缓存穿透问题?](#如何解决Redis缓存穿透问题?)
1616
#### [9.如何解决Redis缓存雪崩问题?](#如何解决Redis缓存雪崩问题?)
17-
#### [10.如何解缓存与数据库的数据一致性问题?](#如何解缓存与数据库的数据一致性问题)
17+
18+
#### [10.如何解决缓存与数据库的数据一致性问题?](#如何解决缓存与数据库的数据一致性问题?)
1819

1920

2021

@@ -254,7 +255,8 @@ Redis 缓存穿透指的是攻击者故意大量请求一些Redis缓存中不存
254255
2.双缓存机制,缓存A的失效时间为20分钟,缓存B没有失效时间,从缓存A读取数据,缓存A中没有时,去缓存B中读取数据,并且启动一个异
255256
3.步线程来更新缓存A。
256257

257-
#### 如何解缓存与数据库的数据一致性问题?
258+
### 如何解决缓存与数据库的数据一致性问题?
259+
258260
首先需要明白会导致缓存与数据库的数据不一致的几个诱因:
259261
多个写请求的执行顺序不同导致脏数据。
260262
更新时正好有读请求,读请求取到旧数据然后更新上。或者数据库是读写分离的,在主库更新完之后,需要一定的时间,从库才能更新。

0 commit comments

Comments
 (0)