赞
踩
在redis和mysql的使用中,一般redis是当做“缓存”来使用的,目的是提高对数据读取的速度。而mysql是当做最终存储数据的“底单”来使用的,所以mysql对数据的一致性要求很高。而mysql的存取速度不如redis,一旦有大量的请求涌入mysql,可能会造成mysql的崩溃从而带来损失。因此通用的做法是在查询数据时,先访问redis,若redis中存在该数据,则直接返回,提高数据读取的效率;若redis中不存在,则再到mysql中读取并写入到缓存中。所以在这个过程中,redis和mysql的数据一致性就十分重要了。
该操作可能会在redis中读到脏数据,例如A,B两个线程对redis发起操作
1 A update mysql 100
2 A update redis 100
3 B update mysql 80
4 B update redis 80
从逻辑上看数据不会出现问题,但是线程的执行顺序常常和代码的顺序并不相同,如果语句的执行顺序为1→3→4→2,那么mysql的值变为80,而redis中的值则为100,会导致后续进入redis中的线程对该数据进行相关的操作时出错。
同样是上面的代码,这次线程的执行顺序变为:
A update redis 100
B update redis 80
B update mysql 80
A update mysql 100
那么mysql的值变为100,redis中的值变为80。但是实际上我们想要数据变为80,mysql中的值和实际的值不相符了,当mysql是作为最后数据的凭证来使用时,这种错误是不允许的。
例子:A线程先将缓存删除,然后更新mysql,如果是在数据量较多或网络抖动等情况下,更新需要一段时间,但如果在更新的过程中,B线程访问了缓存中的数据,就会出现以下结果
整体的流程为:
(1)请求A进行写操作,删除redis缓存后,工作正在进行中,更新mysql。但A还没有彻底更新完mysql,还没commit
(2)请求B开始查询,查询redis发现缓存不存在(被A从redis中删除了)
(3)请求B继续,去数据库查询得到了mysql中的旧值(A还没有更新完)
(4)请求B将旧值写回redis缓存
(5)请求A将新值写入mysql数据库
但是这种问题其实是有解决方案的,也就是“延时双删”
主要的思想就是,先将reds的缓存删除,然后再去更新mysql,更新完等待一段时间后,再将redis中的缓存删除
为什么要等待一段时间呢?
是为了让可能出现的B线程将数据写回到redis中,所以等待的时间需要大于线程B读取数据并写回数据的时间。再次删掉缓存后,后面访问的线程就会发现缓存中数据缺失,就可以读到mysql中更新的新值。
但是实际情况中,等待的时间并不是那么好估计的,所以有了第四种方案。
这个方案带来的问题是:如果缓存删除或来不及删除,导致请求访问redis时命中,这时读取到的是缓存旧值。
解决方案如下:
1 可以把要删除的缓存值或者是要更新的数据库值暂存到消息队列中(例如使用Kafka/RabbitMQ等)。
2 当程序没有能够成功地删除缓存值或者是更新数据库值时,可以从消息队列中重新读取这些值,然后再次进行删除或更新。
3 如果能够成功地删除或更新,我们就要把这些值从消息队列中去除,以免重复操作,此时,我们也可以保证数据库和缓存的数据一致了,否则还需要再次进行重试
4 如果重试超过的一定次数后还是没有成功,我们就需要向业务层发送报错信息了,通知运维人员。
而在这个过程中,redis如何察觉到mysql更新的消息,并进行相应的操作呢?
可以使用canal,词语本身的意思是水道/管道/沟渠,主要的用途是基于MySQL增量日志解析,提供增量数据订阅和消费
具体如何使用canal,请参考
https://blog.csdn.net/Taurus22/article/details/136930215
文章部分内容参考https://www.bilibili.com/video/BV13R4y1v7sP/?p=119&spm_id_from=333.880.my_history.page.click&vd_source=5da9cd35522fab08cd689a08ec2f8d75
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。