如何保证数据库和缓存双写一致性
- 用户请求过来,先查缓存有没有数据,如果有直接返回,提高性能
- 如果缓存没有,再查询数据库
- 如果数据库有数据,则查询出来,放入缓存,供后续请求获取
- 如果数据库也没有,则返回空
这个方案很常见,不仔细思考,好像没有问题,但是我们忽略了一个非常重要的细节,如果查询完数据库,放入缓存之后,这个数据又被立马更新了,那我们该如何更新缓存呢?
如果不过更新,等缓存到过期时间,自动失效,那么这段时间,缓存和数据库就有数据不一致的问题。
所以必须更新缓存,如何更新,目前有以下4个方案:
- 先写缓存,再写数据库
- 先写数据库,再写缓存
- 先删除缓存,再写数据库
- 先写数据库,再删除缓存
接下来,讲一下这4种方案。
先写缓存,再写数据库
这个比较容易想到的方案,在更新请求后,直接更新缓存,我们先说下,先写缓存,再写数据库的情况。
一个更新请求过来,如果刚写完缓存,发生了网络异常,导致写数据库失败了,结果就是缓存是最新数据,但是数据库没有,这样缓存就是脏数据,一致性问题就出现了。为了提高性能使用了缓存,但是现在缓存查出来的是假数据或者旧数据,就没有意义了,因此这个方案在生产中很少使用。
先写数据库,再写缓存
该方案在并发不高的项目中,比较常用。
更新请求,先写数据库,在写缓存,可以避免上面方案中,出现假数据的问题。
如果写数据库,和写缓存在同一个事务了,当写缓存失败,我们会对数据回滚,这样会保证数据库缓存一致,这样在并发小的时候,对接口性能要求不高的系统,可以这么使用。
但是高并发业务场景中,写数据库和写缓存,都属于远程操作,为了防止出些大事务,造成死锁,通常情况下,建议写数据库和写缓存不要放在同一个事物中。
也就是说这个方案中,写数据库成功,但是写缓存失败,是不会回滚数据库的。
这样就出现缓存不一致的问题了。
如果觉得上面所说的情况太牵强,高并发情况下,还会出现以下问题 :
假设高并发场景,有两个写请求:a和b,他们同时请求到系统,其中a获取写入旧的数据,b是写入新的数据
1.a请求先到,刚写完数据库,由于网络问题,卡顿一下,还没来得及写缓存
2.b请求这个时候也到了,先写了数据库
3.接下来,b顺利写了缓存
4.此后,a卡顿结束,也写了缓存
很显然,数据库里面存的是b的新数据,而缓存却被a的旧数据覆盖了。
由此可见,在高并发的场景中,先写数据库,再写缓存,这套方案问题挺多的,也不太建议使用。
先删缓存,在写数据库
上面的方案,直接更新缓存问题比较多。
那么,我们换一种思路,不去更新缓存,而是直接删缓存。
先看看,先删缓存的情况:
这种虽然简单,但是也有会有一些问题:
高并发下的问题:
对同一个数据,同时有一个读请求c,还有一个更新请求d到系统,如图所示:
1.写请求d先到,把缓存删除,但由于网络原因,卡顿一下,还没来得及写数据库
2.这个时候查询请求c过来了,先查缓存发现没有数据,然后去查数据库,有值,但是是旧数据。
3.请求c将旧数据,更新到缓存中。
4.此时,写请求d卡顿结束,把新数据写入数据库。
在这个过程中,同样会导致数据库缓存不一致问题。(图中有问题,步骤7写入旧值,步骤9要删掉)
那么怎么解决呢?
缓存双删,如下图所示:
这个方案不同之处,就是在写入数据库之前删除一次,写完数据后,再删除一次。
还有一个关键的地方,就是第二次删除,并非马上删除,而是要在一定时间间隔(如:500ms)之后删除。
这是为了保证第二次删除操作在写请求d卡顿结束,把新的值写入数据库后,查询请求c查到旧值写入缓存后执行。
先写数据库,再删除缓存
从前面得知,先删缓存,再写数据库,在并发的情况下,也可能会出现缓存和数据库的数据不一致的情况。
在高并发场景中,过程如下:
1.写请求e先写数据库,由于网路卡顿,没来得及删除缓存。
2.读请求f查询缓存,发现有数据,直接返回
3.写请求e删除缓存
这个过程,只有读请求f读了一次旧数据,后来e请求及时删除了缓存,看起来可以接受。
如果是还有另外一种情况呢?
1.读请求f查询缓存,发现有数据,直接返回。
2.写请求e先写数据库,再删除缓存。
看起来也没问题?
但是如果是下图显示的情况:
1.刚好缓存过期,自动失效
2.请求f从数据库获取旧值,写入缓存前,发生卡断
3.写请求e先写数据库,再删除缓存
4.f请求结束卡顿,把旧值写入缓存
这时,数据库和缓存也发生不一致的问题。
但这种情况还是比较少的,需要同时满足以下条件才可以:
- 缓存刚好自动失效。
- 请求f从数据库查出旧值,更新缓存的耗时,比请求e写数据库,并且删除缓存的还长。
由此可见,系统同时满足上述两个条件的概率非常小。
推荐大家使用先写数据库,再删缓存的方案,虽说不能100%避免数据不一致问题,但出现该问题的概率,相对于其他方案来说是最小的。
删缓存失败怎么办?
那么就需要加重试机制
。这个后面再开一篇讨论。