缓存双写一致性
查询流程
双写一致性概念
当修改了数据库的数据也要同时更新缓存的数据,缓存和数据库的数据要保持一致。
如果Redis中有数据,需要和数据库中的值相同
如果Redis中无数据,数据库中的值要是最新值,且回写Redis
缓存按照操作来分,可以分为两种:
只读缓存
读写缓存
大部分都是读写缓存,其有两种回写同步策略:
同步直写策略
写数据库后也同步写Redis缓存,缓存和数据库中的数据一致;
对于读写缓存来说,要想保证缓存和数据库中的数据一致,就要采用同步回写策略。
异步缓写策略
正常业务运行中,数据库变动了,但是可以在业务上容许出现一定时间后才作用于Redis;
当出现异常情况,不得不将失败的动作重新修补,可能需要借助消息中间件实现重试重写。
双检加锁策略
当高并发查询时,如果Redis没有数据,大量请求会落入数据库中,同时大量的回写操作造成数据覆盖,会耗费不必要的资源。
解决方案:采用双检加锁策略
多个线程同时去查询数据库的这条数据,我们可以在第一个查询数据的请求上使用一个互斥锁。其他线程等第一个线程查询完回写缓存释放锁后,直接加载缓存数据。
更新策略
目的:保证最终一致性
原则:以MySQL的数据为准
下述共4种更新策略,四种策略无最优解,不保证适配所有情况,视实际情况选择。
如果是停机更新,下面方法都可以使用,如果是无感更新,不同策略可能存在一些问题
先更新数据库,再更新缓存
问题:
如果更新数据库成功,但Redis更新失败了,两边数据不一致,读到Redis脏数据。
多线程环境下,线程有快有慢,可能存在如下情况:
线程A、B同时更新
1
2
3
4
5
6
7
8
9
10# 正常情况
A update mysql 100
A update redis 100
B update mysql 80
B update redis 80
# 异常情况
A update mysql 100
B update mysql 80
B update redis 80
A update redis 100导致数据库和Redis数据不一致
先更新缓存,再更新数据库
问题:
这种方式并不推荐,因为业务上一般把MySQL作为底单数据库,保证最后解释。
同第一种更新策略,多线程环境下,线程有快有慢,可能存在如下情况:
线程A、B同时更新
1
2
3
4
5
6
7
8
9
10# 正常情况
A update redis 100
A update mysql 100
B update redis 80
B update mysql 80
# 异常情况
A update redis 100
B update redis 80
B update mysql 80
A update mysql 100导致数据库和Redis数据不一致
先删除缓存,再更新数据库
问题:
缓存删除成功,但数据库更新失败,导致读取到旧值。
线程A已经删除缓存,数据库更新还在执行中(可能网络延迟,并没有真正到MySQL的update操作),另一个线程B此时进行查询操作。
此处就会有两个问题:
- 线程B从MySQL中获得了旧值
- 线程B将旧值回写给Redis
导致数据库和Redis数据不一致
解决方法:延时双删策略。在更新数据库前后都删除一次缓存。更新数据库后的删除需要一段延迟时间,这段时间是为了让线程B能够先从数据库读取数据,再把缺失的数据写入缓存,所以这个时间需要大于线程B读取数据再写入缓存的时间。
先更新数据库,再删除缓存
较为推荐的做法。但也有一些问题:
- 缓存删除失败或者来不及,会导致请求再次访问Redis时缓存命中,读取到的是缓存旧值。
强一致性解决方案
借助消息中间件
- 可以把要删除的缓存值或者是要更新的数据库值暂存到消息队列
- 当程序没有成功删除缓存或者更新数据库时,可以从消息队列中重新读取这些值,再次尝试
- 如果成功删除或更新,这些值就要从消息队列中去除,避免重复操作。此时,就可以保证数据库和缓存的数据一致性。否则还需要继续重试
- 如果超过一定次数还未成功,需要业务层发送错误信息,通知运维人员。
Canal
cancal主要用途是用于MySQL数据库增量日志数据的订阅、消费和解析,是阿里巴巴开发并开源的,采用Java语言开发。