什么是双写一致性?
双写一致指我们去更新数据库的数据之后,也要去更新缓存,使数据库与缓存的数据是一致的。
强一致性:不允许出现数据库和缓存不一致的情况,任何时间缓存和数据库的数据都要一致,强一致性通常需要付出很大的性能代价。
弱一致性:允许短时间内数据库与缓存的数据不一致,但是会通过一些方法使数据库和缓存最终是一致的,也就是最终一致性。
现在有数据需要更新,我们是先操作数据库还是先操作缓存呢?是删除缓存还是更新缓存呢?
删除缓存还是更新缓存?
答案是删除缓存。为什么?因为如果是更新缓存,更新了之后没有请求来查询,之后又需要更新数据,那么这次更新缓存的操作就是无效的操作,因为没有请求来查询缓存就被更新了。而删除缓存的话,只需要在有请求来查询数据库的时候去重构一下缓存就行了。
先操作数据库还是先操作缓存?
我们先来看看先删除缓存,再更新数据库会出现的问题:
线程1去删除缓存并更新数据库,更新数据库还未完成这期间,线程2来查询缓存未命中,于是去查询数据库的旧数据,并且把旧数据写入了缓存,最后线程1去更新了数据库,这样就导致了数据库和缓存数据的不一致。
再来看看先更新数据库,再删除缓存会出现的问题:
首先线程1来查询缓存未命中,可能是缓存过期了,然后去查询数据库并写入缓存,在还未写入缓存的这期间,线程2来更新数据库并删除缓存了,之后线程1把旧数据写入缓存成功,造成双写不一致。
虽然无论是先操作缓存还是先操作数据库都有可能会造成双写不一致,但是它们出现双写不一致的概率是不同的。
先删缓存再更新数据库出现问题的情况,是在删除缓存和更新数据库之间有其他的线程来查询缓存,是在写缓存和写数据库之前插入了查询缓存和数据库的操作,我们知道写操作一般是要比查询操作要慢的,所以是有概率出现这种情况的。
而先更新数据库再删缓存,首先要满足缓存失效,其次是要在查询数据库和写缓存之间插入更新数据库以及写缓存的操作,这种情况发生的概率明显就更低了。
无论是先操作数据库还是先操作缓存都有双写不一致的情况,那么我们如何保证它们的一致性?
保证强一致性:使用分布式锁
使用分布式锁,每次只允许一个线程去读或者写,那么这样就可以保证缓存和数据库的强一致性,但是这么性能太低。可以使用分布式读写锁优化,让读写互斥,读操作之间不互斥。
保证最终一致性
方案1:延迟双删 (缓存双删)
先删除缓存,再更新数据库,延迟一段时间后,又去删除缓存,这样可以将旧缓存删掉,保证最钟一致性。这里为了保证最后删缓存操作成功,可以删除失败后重试,加入最大重试次数。
这种方案可以保证最终一致性,但是延迟的时间不好控制,延迟是为了让主库的数据完全同步到从库,如果延迟时间过短,还未同步完成就删除缓存,那么还是会造成不一致的情况。
方案2:监听binlog日志
mysql的写操作都会记录到binlog日志中,可以使用canal中间件去听binlog日志,canal会伪装成mysql的一个从节点,去获取binlog日志中的信息,拿到了这些信息之后,可以通过MQ异步的去更新缓存或者删除缓存。