目录
一、什么是缓存双写一致性呢?
1.1 双检加锁机制
二、数据库和缓存一致性的更新策略
2.1、先更新数据库,后更新缓存
2.2 、先更新缓存,后更新数据库
2.3、先删除缓存,在更新数据库
延时双删的策略:
2.4.先更新数据库,在删除缓存(常用)
2.5、实际中是不可能做到强一致性的,那么怎么做到最终一致性呢?
三、canal中间件
3.1 canal工作原理
3.2 MySQL的主从复制
如果redis中有数据
如果redis中无数据
缓存按照操作分
加锁前从redis中查一次,加锁后再查一次。
多个线程同时去查询数据库的这条数据,那么我们可以在第一个查询数据的请求上使用一个 互斥锁来锁住它。
其他的线程走到这一步拿不到锁就等着,等第一个线程查询到了数据,然后做缓存。
后面的线程进来发现已经有缓存了,就直接走缓存。
一般都是以MySQL为准。
给缓存设置过期时间,定期清理缓存并回写,是保证最终一致性的解决方案。
我们可以对存入缓存的数据设置过期时间,所有的写操作以数据库为准,对缓存操作只是尽最大努力即可。也就是说如果数据库写成功,缓存更新失败,那么只要到达过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存,达到一致性,切记,要以mysql的数据库写入库为准
异常一: 回写失败会出现脏数据
异常二: 高并发下会出现数据覆盖
我们一般是不用这种的,因为我们一般都把MySQL作为根基
异常: 高并发下会出现数据覆盖
当有两个线程:一个线程负责删Redis,修改MySQL, 另一个来查找redis
如果数据库更新失败或者不及时就会发生异常:
(1)请求A进行写操作,删除redis缓存后,工作正在进行中,更新mysql......A还没有彻底更新完mysql,还没commit
(2)请求B开工查询,查询redis发现缓存不存在(被A从redis中删除了)
(3)请求B继续,去数据库查询得到了mysql中的旧值(A还没有更新完)
(4)请求B将旧值回写redis缓存
(5)请求A将新值写入mysql数据库
上述情况就会导致不一致的情形出现。
时间
|
线程A
|
线程B
|
出现的问题
|
t1
|
请求A进行写操作,删除缓存成功后,工作正在mysql进行中......
|
||
t2
|
1 缓存中读取不到,立刻读mysql,由于A还没有对mysql更新完,读到的是旧值
2 还把从mysql读取的旧值,写回了redis
|
1 A还没有更新完mysql,导致B读到了旧值
2 线程B遵守回写机制,把旧值写回redis,导致其它请求读取的还是旧值,A白干了。
|
|
t3
|
A更新完mysql数据库的值,over
|
redis是被B写回的旧值,
mysql是被A更新的新值。
出现了,数据不一致问题。
|
总结一下:
先删除缓存,再更新数据库
|
如果数据库更新失败或超时或返回不及时,导致B线程请求访问缓存时发现redis里面没数据,缓存缺失,B再去读取mysql时, 从数据库中读取到旧值,还写回redis,导致A白干了,o(╥﹏╥)o
|
改怎么解决呢?
这个删除该休眠多久呢?
因为这种同步淘汰机制加上了sleep,导致MySQL吞吐量降低怎么办?
这一种方法的弊端相对比较少
时间
|
线程A
|
线程B
|
出现的问题
|
t1
|
更新数据库中的值......
|
||
t2
|
缓存中立刻命中,此时B读取的是缓存旧值。
|
A还没有来得及删除缓存的值,导致B缓存命中读到旧值。 | |
t3
|
更新缓存的数据,over
|
先更新数据库,再删除缓存
|
假如缓存删除失败或者来不及,导致请求再次访问redis时缓存命中, 读取到的是缓存旧值。
|
需要用到消息队列:kafka或者RabbitMQ
但是还是都需要是先更新数据库,再删除缓存,这样最多也就是数据暂时不一致,不会导致雪崩、击穿啥的出现。
1 可以把要删除的缓存值或者是要更新的数据库值暂存到消息队列中(例如使用Kafka/RabbitMQ等)。
2 当程序没有能够成功地删除缓存值或者是更新数据库值时,可以从消息队列中重新读取这些值,然后再次进行删除或更新。
3 如果能够成功地删除或更新,我们就要把这些值从消息队列中去除,以免重复操作,此时,我们也可以保证数据库和缓存的数据一致了,否则还需要再次进行重试
4 如果重试超过的一定次数后还是没有成功,我们就需要向业务层发送报错信息了,通知运维人员。
|
能够立刻感知到MySQL改变的有一个MySQL的binlog文件
我们需要一种技术来充当两者之前的吹哨人
这里有阿里研发的一种中间件canal
1. canal 模仿MySQL的dump协议,假装自己是MySQL的slave,向MySQL发送dump协议
2. MySQLmaster收到dump请求之后,便会给canal推送自身bin log 变化给canal
3. cannal收到bin log 消息并解析。
MySQL的主从复制将经过如下步骤:
1、当 master 主服务器上的数据发生改变时,则将其改变写入二进制事件日志文件中;
2、salve 从服务器会在一定时间间隔内对 master 主服务器上的二进制日志进行探测,探测其是否发生过改变,
如果探测到 master 主服务器的二进制事件日志发生了改变,则开始一个 I/O Thread 请求 master 二进制事件日志;
3、同时 master 主服务器为每个 I/O Thread 启动一个dump Thread,用于向其发送二进制事件日志;
4、slave 从服务器将接收到的二进制事件日志保存至自己本地的中继日志文件中;
5、salve 从服务器将启动 SQL Thread 从中继日志中读取二进制日志,在本地重放,使得其数据和主服务器保持一致;
6、最后 I/O Thread 和 SQL Thread 将进入睡眠状态,等待下一次被唤醒;