一致性是指同一时刻的请求,在缓存中的数据是否与数据库中的数据相同的。
强一致性:数据库更新操作与缓存更新操作是原子性的,缓存与数据库的数据在任何时刻都是一致的,这是最难实现的一致性。
弱一致性:当数据更新后,缓存中的数据可能是更新前的值,也可能是更新后的值,因为这种更新是异步的。
最终一致性:一种特殊的弱一致性,在一定时间后,数据会达到一致的状态。最终一致性是弱一致性的理想状态,也是分布式系统的数据一致性解决方案上比较推崇的。
我们更新的策略对应的就是4种:
(1) 先更新缓存,再更新数据库。
(2) 先更新数据库,再更新缓存。
(3) 先删除缓存,再更新数据库。
(4) 先更新数据库,再删除缓存。
更新缓存
优点:缓存中的数据会一直有效,相对删除缓存,会提高缓存命中率。
缺点:频繁更新缓存开销较大,遇到写多读少的业务场景并不适用。
删除缓存
优点:逻辑简单,直接删除缓存中的数据就可以了。
缺点:删除缓存会导致下次请求缓存未命中。
考虑到更新缓存的时间成本和代码难度,删除缓存时一个更经济实惠的方式。
我们来把4种实现方式会遇到的场景都画一遍
①线程B更新缓存
②线程A更新缓存
③线程A更新数据库
④线程B更新数据库
在这种双写的场景下,如果出现一个线程两个操作之间覆盖了另一个线程的更新操作,必然会产生一致性问题。因为如果线程A想让字段+1,线程B也想让字段+1,经过两个线程的独立计算,两个线程各计算出+1的结果,然后线程A将缓存更新为+1的结果,造成数据库与缓存不一致。
①线程B查询缓存,但是缓存未命中
②线程B查询数据库
③线程A更新缓存
④线程A更新数据库
⑤线程B查到数据,更新缓存
这种情况也会产生一致性问题。这个一致性问题就很好理解了,线程B接收到查询请求,去缓存中查找结果,但是缓存未命中,再去查数据库。线程A先去更新缓存,再去更新数据库。反过来线程B才去更新缓存,这时缓存里是线程A更新之前的结果,所以数据不一致。但是由于操作缓存比操作数据库更快,所以这种情况出现的概率较低。
①线程B删除缓存
②线程A删除缓存
③线程A更新数据库
④线程B更新数据库
在与先更新缓存的双写场景下,面临相同的状况,但是先删除缓存显然不会受困于一致性问题,因为无论什么顺序缓存都是要被删除的。
①线程B查询缓存,未命中
②线程B查询数据库
③线程A删除缓存
④线程A更新数据库
⑤线程B更新缓存
这种情况也不能保证一致性。线程B接收到查询请求,去缓存中查找结果,但是缓存未命中,再去查数据库。线程A先去删除缓存,再去更新数据库。反过来线程B才去更新缓存,这时缓存里是线程A更新之前的结果,所以数据不一致。
①线程B更新数据库
②线程A更新数据库
③线程A更新缓存
④线程B更新缓存
这种情况不能保证一致性。线程B先更新数据库拿到结果,线程A紧随其后更新数据库并更新缓存,线程B再去更新缓存必然导致缓存不一致。
①线程B查询缓存,未命中
②线程B查询数据库
③线程A更新数据库
④线程A更新缓存
⑤线程B更新缓存
这种情况无法保证一致性。线程B接收到查询请求,去缓存中查找结果,但是缓存未命中,再去查数据库。线程A先去更新缓存,再去更新数据库。反过来线程B才去更新缓存,这时缓存里是线程A更新之前的结果,所以数据不一致。但是由于操作缓存比操作数据库更快,所以这种情况出现的概率较低。
①线程B更新数据库
②线程A更新数据库
③线程A删除缓存
④线程B删除缓存
这种情况可以保证一致性。因为删除缓存的双写场景并不在乎你是先删还是后删,因为没区别,所以必然一致。
①线程B查询缓存,未命中
②线程B查询数据库
③线程A更新数据库
④线程A删除缓存
⑤线程B更新缓存
这种情况无法保证一致性。线程B接收到查询请求,去缓存中查找结果,但是缓存未命中,再去查数据库。线程A先去更新数据库,再去删除缓存。反过来线程B才去更新缓存,这时缓存里是线程A更新之前的结果,所以数据不一致。但是由于操作缓存比操作数据库更快,所以这种情况出现的概率较低。
等等~秋豆麻袋,怎么四种策略都无法保证一致性。你在搞什么?
别急,我们来回顾一下这四种情况:
1.先更新缓存再更新数据库:在双写场景下,很容易出现一致性问题,在读写场景下,小概率出现一致性问题,所以Pass。
2.先删除缓存再更新数据库:在双写场景下,不会出现一致性问题,在读写场景下,很容易出现一致性问题,所以Pass。
3.先更新数据库再更新缓存:在双写场景下,很容易出现一致性问题,在读写场景下,小概率出现一致性问题,所以Pass。
4.先更新数据库再删除缓存:在双写场景下,不会出现一致性问题,在读写场景下,小概率出现一致性问题,所以暂时保留。
四种策略中的三种已经被Pass了,只剩下一个哥们,但是还是无法完全满足一致性,所以我们需要加点技术。
我们先知道了延迟双删的流程,再来解释一下流程。
为什么要先删一次缓存?
先解决更新数据库时,查询请求走到缓存,获取到过时数据问题。
为什么第二次删除需要延时?
为了规避线程A的第二次删除缓存操作,早于线程B更新缓存操作,从而依然存在的不一致问题。
这个延时怎么定义呢,定义多长时间比较合适?
这个只能根据系统的查询性能做一个具体的评估,没有一个普适的值。
另外,延迟双删只能保障最终一致性,没法保障强一致性。