文章插图
和之前一样,如果两段代码都执行成功,在并发情况下会是什么样呢?
文章插图
还是会造成数据的不一致性 。
但是此处达成这个数据不一致性的条件明显会比起其他的方式更为困难 :
- 时刻1:读请求的时候,缓存正好过期
- 时刻2:读请求在写请求更新数据库之前查询数据库,
- 时刻3:写请求,在更新数据库之后,要在读请求成功写入缓存前,先执行删除缓存操作 。
一定程度上来讲,这种方式还是解决了一定程度上的数据不一致性问题的 。
4.3、总结以上四种方式无论选择那种方式,如果实在多服务或时并发的情况下,其实都是有可能产生数据不一致性的 。
为了解决这个存在的问题有以下方式:
4.3.1、延迟双删先进行缓存清除,再执行update,最后(延迟N秒)再执行缓存清除 。进行两次删除,且中间需要延迟一段时间
文章插图
public void write(String key,Object data){// 延迟双删伪代码deleteRedisCache(key);// 删除redis缓存updateMySQLSql(obj);// 更新mysqlThread.sleep(100);// 延迟一段时间deleteRedisCache(key);// 再次删除该key的缓存}
- 1.
- 2.
- 3.
- 4.
- 5.
- 6.
- 7.
文章插图
解决这样的问题,其实最好的方式就是在执行完更新数据库的操作后,先休眠一会儿,再进行一次缓存的删除,以确保数据一致性
首先延迟删除的时间需要大于 1号用户执行流程的总时间
就是1号用户从数据库读取数据 写入缓存时间
4.3.2、通过发送MQ,在消费者线程去同步Redis
文章插图
无论是更新缓存还是删除缓存,在同时操作缓存和数据库时,都无法保证两者都能一次性操作成功,所以我们最好的办法就是重试,这个重试并不是立即重试,因为缓存和数据库可能因为网络或者其它原因停止服务了,立即重试成功率极低,而且重试会占用线程资源,显然不合理,所以我们需要采用异步重试机制 。
异步重试我们可以使用消息队列来完成,因为消息队列可以保证消息的可靠性,消息不会丢失,也可以保证正确消费,当且仅当消息消费成功后才会将消息从消息队列中删除 。
优点1:可以大幅减少接口的延迟返回的问题
优点2:MQ本身有重试机制,无需人工去写重试代码
优点3:解耦,把查询Mysql和同步Redis完全分离,互不干扰
4.3.3、Canal 订阅日志实现当我们业务修改数据时,我们只需要更新数据库,无需修改缓存,那什么时候修改缓存呢?
以mysql为例,在数据库一条记录发生变更时就会生成一条binlog日志,我们可以订阅这种消息,拿到具体的数据,然后根据日志消息更新缓存,订阅日志目前比较流行的就是阿里开源的canal,那么我们的架构就变为如下形式 。
文章插图
订阅数据库变更日志,当数据库发生变更时,我们可以拿到具体操作的数据,然后再去根据具体的数据,去删除对应的缓存 。
当然Canal 也是要配合消息队列一起来使用的,因为其Canal本身是没有数据处理能力的 。
文章插图
这个方式算的上彻底解耦了,应用程序代码无需再管消息队列方面发送失败问题,全交由 Canal来发送 。
【Redis数据一致性问题的三种解决方案】
推荐阅读
- 关系数据库和图数据库的基础解读
- 数据库的进化路径是怎么样的呢
- 使用矢量数据库打造全新的搜索引擎
- 一文了解Redis哨兵模式
- 在 SQL Server 中获取数据库备份历史记录
- 企业服务器数据库中了_locked勒索病毒怎么解密,_locked勒索病毒简介与防护
- 百模征战,如何解决数据卡脖子问题?
- 国内赏桃花去哪里好 中控智慧考勤机怎么上传数据
- 数据库是要拿来用的,不是用来PK先进性的
- 简化Java单元测试数据