Redis缓存一致性问题及解决方案

您所在的位置:网站首页 redis缓存分页数据更新 Redis缓存一致性问题及解决方案

Redis缓存一致性问题及解决方案

2023-06-03 00:41| 来源: 网络整理| 查看: 265

对于没有并发的用户请求 先更新缓存,后更新数据库先更新数据库,后更新缓存

两者第二步没成功,都有问题

如果更新缓存成功,更新数据库没成功,一旦缓存失效,读取的仍是旧值如果更新数据库成功,更新缓存没成功,则修改结果迟迟看不到。 然后就是两者都有并发的情况下:

先更新缓存后更新数据库

用户A更新缓存x=2(x=1)用户B更新缓存x=1用户B写入数据库x=1用户A写入数据库x=2

用户A把用户B的请求覆盖了

先更新数据库后更新缓存

线程 A 更新数据库(X = 1)线程 B 更新数据库(X = 2)线程 B 更新缓存(X = 2)线程 A 更新缓存(X = 1)

线程A的缓存把线程B的缓存覆盖了

显然有问题,以上是并发问题,除此之外从缓存利用率来讲,更新的缓存不一定会马上被读取,可能会导致缓存中有很多没有用的数据,浪费资源。

解决方案:删除缓存 先删除缓存,再更新数据库 线程 A 要更新 X = 2(原值 X = 1)线程 A 先删除缓存线程 B 读缓存,发现不存在,从数据库中读取到旧值(X = 1)线程 A 将新值写入数据库(X = 2)线程 B 将旧值写入缓存(X = 1)

最终 X 的值在缓存中是 1(旧值),在数据库中是 2(新值),发生不一致。

先更新数据库,再删除缓存 缓存中 X 不存在(数据库 X = 1)线程 A 读取数据库,得到旧值(X = 1)线程 B 更新数据库(X = 2)线程 B 删除缓存线程 A 将旧值写入缓存(X = 1)

最终 X 的值在缓存中是 1(旧值),在数据库中是 2(新值),也发生不一致。

然而上述条件很难满足,特别是条件三

缓存刚好已失效读请求 + 写请求并发更新数据库 + 删除缓存的时间(步骤 3-4),要比读数据库 + 写缓存时间短(步骤 2 和 5) 如何保证两步都执行成功

程序在执行过程中发生异常,最简单的解决办法是什么?

重试

直接重试方案不严谨:

异步重试

把重试请求写到「消息队列」中,然后由专门的消费者来重试,直到成功

或者更直接的做法,为了避免第二步执行失败,我们可以把操作缓存这一步,直接放到消息队列中,由消费者来操作缓存。

问题:

如果在执行失败的线程中一直重试,还没等执行成功,此时如果项目「重启」了,那这次重试请求也就「丢失」了,那这条数据就一直不一致了

解决方法:消息队列特性

消息队列保证可靠性:写到队列中的消息,成功消费之前不会丢失(重启项目也不担心)消息队列保证消息成功投递:下游从队列拉取消息,成功消费后才会删除消息,否则还会继续投递消息给消费者(符合我们重试的场景)

至于写队列失败和消息队列的维护成本问题:

写队列失败:操作缓存和写消息队列,「同时失败」的概率其实是很小的维护成本:我们项目中一般都会用到消息队列,维护成本并没有新增很多

在这里插入图片描述

更简单的方案

订阅数据库变更日志,再操作缓存

们的业务应用在修改数据时,「只需」修改数据库,无需操作缓存。

当一条数据发生修改时,MySQL 就会产生一条变更日志(Binlog),我们可以订阅这个日志,拿到具体操作的数据,然后再根据这条数据,去删除对应的缓存。

在这里插入图片描述

订阅变更日志,目前也有了比较成熟的开源中间件,例如阿里的 canal,使用这种方案的优点在于:

无需考虑写消息队列失败情况:只要写 MySQL 成功,Binlog 肯定会有自动投递到下游队列:canal 自动把数据库变更日志「投递」给下游的消息队列


【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3