在项目中我们经常这样用缓存来缓解数据库的压力:
但是对于写缓存,你知道怎么设计方案,保证缓存与数据库的数据一致性吗?
请求B是最后请求的,那么应该是他最后更新缓存为正确的数据,但是有可能请求A处理的更慢,所以请求A更新了最后的缓存。
先删除缓存,数据库还没有更新成功,此时如果读取缓存,缓存不存在,去数据库中读取到的是旧值,然后更新缓存,缓存不一致发生。如图:
上面的问题可以用延时双删的方案来解决,思路是,更新完数据库之后,再sleep一段时间,然后再次删除缓存。
sleep的时间要对业务读写缓存的时间做出评估,sleep时间大于读写缓存的时间即可。
流程如下:
线程1删除缓存,然后去更新数据库
线程2来读缓存,发现缓存已经被删除,所以直接从数据库中读取,这时候由于线程1还没有更新完成,所以读到的是旧值,然后把旧值写入缓存
线程1,根据估算的时间,sleep,由于sleep的时间大于线程2读数据+写缓存的时间,所以缓存被再次删除
如果还有其他线程来读取缓存的话,就会再次从数据库中读取到最新值
当然,这样也会有并发问题。
比如:
但是数据库读操作速度远快于写操作,所以存在脏数据的可能性为0。
当然如果您问,如果真的存在怎么办?
简单,双删就行了,即第一次删除缓存之后,等待一段时间重新再删一次。
当然您如果还问,删除缓存失败了怎么办,解决方法如下:
即引入消息队列,删除缓存失败的记录下来重复删除,直到成功为止。如此一来,万无一失。
每次放入缓存的时候,设置一个过期时间,比如5分钟,以后的操作只修改数据库,不操作缓存,等待缓存超时后从数据库重新读取。
如果对于一致性要求不是很高的情况,可以采用这种方案。
这个方案还会有另外一个问题,就是如果数据更新的特别频繁,不一致性的问题就很大了。
在实际生产中,我们有一些活动的缓存数据是使用这种方式处理的。
因为活动并不频繁发生改变,而且对于活动来说,短暂的不一致性并不会有什么大的问题。
首先,我们要明确一点,缓存不是更新,而应该是删除。
为什么呢?
我们用先更新数据库,再删除缓存来举例。
如果是更新的话,那就是先更新数据库,再更新缓存。
举个例子:如果数据库1小时内更新了1000次,那么缓存也要更新1000次,但是这个缓存可能在1小时内只被读取了1次,那么这1000次的更新有必要吗?
反过来,如果是删除的话,就算数据库更新了1000次,那么也只是做了1次缓存删除,只有当缓存真正被读取的时候才去数据库读取。
删除缓存有以下两种方式:
1.先删除缓存,再更新数据库。解决方案是使用延迟双删。
2.先更新数据库,再删除缓存。解决方案是消息队列或者其他binlog同步,引入消息队列会带来更多的问题,并不推荐直接使用。
针对缓存一致性要求不是很高的场景,那么只通过设置超时时间就行了。