Redis缓存一致性问题及解决方案 |
您所在的位置:网站首页 › redis缓存分页数据更新 › Redis缓存一致性问题及解决方案 |
对于没有并发的用户请求
先更新缓存,后更新数据库先更新数据库,后更新缓存
两者第二步没成功,都有问题 如果更新缓存成功,更新数据库没成功,一旦缓存失效,读取的仍是旧值如果更新数据库成功,更新缓存没成功,则修改结果迟迟看不到。 然后就是两者都有并发的情况下:先更新缓存后更新数据库 用户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 |