redis性能、并发、数据库双写一致性问题

一、程序运行读取缓存流程

获取缓存流程及访问数据库流程。

在这里插入图片描述

对于先更新数据库、还是先更新缓存、后删除缓存之间的顺序存在不同,不同的顺序会出现不同的情况。

这些问题考虑的出发点的来源于redis、数据库双写一致性,和读取的数据是否准确。

二、redis、数据库双写一致性

一致性又分为强一致性和最终一致性。

强一致性:这个主要使用场景是高并发是需要注意,例如:双十一商品数量问题,这个就需要做到强一致性。就是做到实时更新数据。

最终一致性:这个对数据的实时准确性不做过高的要求,可以一段时间之后同步数据,只要保证最后的数据是准确的就行。

1、那么如何保持redis、数据库双写一致?

理论上来说,我们给缓存设置过期时间,只要数据库更新成功,不管缓存是否更新成功,或者删除失败。是可以保证数据最终一致性的。接下来的策略介绍是基于不设置缓存过期时间探讨。

那我们先来介绍一下缓存的更新策略。

1、先更新数据库、在更新缓存

2、先删除缓存、在更新数据库

3、先更新数据库、在删除缓存

1、先更新数据库、在更新缓存

在这里插入图片描述

这种策略不推荐,会出现脏读数据。

原因:

事务A在更新数据库未完成时(可能由于网络原因)

事务B更新数据,更新缓存

导致本来是事务A

脏读: 所谓的脏读,其实就是读到了别的事务回滚前的脏数据。比如事务A执行过程中修改了数据X,在未提交前,事务B读取了X,而事务A却回滚了,这样事务B就形成了脏读。

2、先删除缓存、在更新数据库

这种策略也有问题,先删除缓存,后删除数据库

如果缓存删除失败,更新数据库成功,那么事务B读取的就是旧数据,造成读取的数据不一致。

在这里插入图片描述

3、先更新数据库、在删除缓存

首先,先说一下。老外提出了一个缓存更新套路,名为《Cache-Aside pattern》。其中就指出

  • 失效:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。
  • 命中:应用程序从cache中取数据,取到后返回。
  • 更新:先把数据存到数据库中,成功后,再让缓存失效。

另外,知名社交网站facebook也在论文《Scaling Memcache at Facebook》中提出,他们用的也是先更新数据库,再删缓存的策略。

在这里插入图片描述

这种策略依旧无法解决并发问题。

原因:我们可以根据时间线判断,事务A中的缓存依然是旧值,导致别的事务获取的数据不准确。

不过这种情况很少出现,原因就是事务B中的更新数据库耗时比事务A中更新数据所耗费的时间还要短。那我们从数据库方面分析,如果你做的是读写分离。首先要明白为啥要做读写分析,是因为你读的操作耗时短,消耗的资源少。

那么我如何解决以上出现的问题?网络上有很多解决方法

大部分使用的延时双删的策略,

4、什么是延时双删除?

延时双删,就是我们先删除缓存,然后在更新数据库,更新完数据库后,在延时一会,在删除数据库。

在这里插入图片描述

那我们来解释一下为什么要采用延时双删,解决了什么问题?

1、事务A删除缓存、假如删除失败

2、事务A更新数据库

3、这是事务B进来了,它已更新数据库

4、这是事务A完成了更新数据,删除了缓存

5、事务B在事务A删除缓存后,又更新了缓存

那以上操作会带了什么结果,导致缓存中存在存储的是事务B中更新的数据。

我们最终目的是下次访问的时候读到的是事务A中更新的数据,那么现在我们读到的事务B中的数据,数据不准确。

**延时:**我们可以在2、4之间做一步操作,睡眠,让4操作比5操作后执行,这样就能保证在事务B执行完成后,事务A删除了事务B中的缓存,下一次请求的获取数据的时候就需要走“失效”。上已经解释了缓存更新套路(Cache-Aside pattern)

三、最终解决数据一致性问题

就像第二大点我们介绍的三种策略,想第二种,第三种,如果换成删除失败如何解决,第三种策略也给出了延时双删的策略,那么如果第二次删除换成失败,我们改如何解决?

1、先更新数据库、在更新缓存

2、先删除缓存、在更新数据库

3、先更新数据库、在删除缓存

1、在业务代码中消息队列

在这里插入图片描述

1、更新数据库

2、删除缓存失败

3、将删除失败的key发送至消息队列

4、消费消息队列中的信息,获取到未删除成的key

5、再次重新删除缓存。

这样就可以确保,我们每次请求获取到的数据都是准确的数据了。但是这个方案有一个缺点,就是和主线业务代耦合度太高,那么我们来看下第二种方案。

2、使用消息队列+订阅

在这里插入图片描述

这种方案比较第一种方案的优点在于,启动一个订阅程序去订阅数据库的binlog,获取到要操作的数据,在另外一个程序中去获取订阅程序传递过来的数据,然后通过利用消息队列去删除缓存,确保缓存会被删除。

ps:上述的订阅binlog程序在mysql中有现成的中间件叫canal,可以完成订阅binlog日志的功能。

去订阅数据库的binlog,获取到要操作的数据,在另外一个程序中去获取订阅程序传递过来的数据,然后通过利用消息队列去删除缓存,确保缓存会被删除。

ps:上述的订阅binlog程序在mysql中有现成的中间件叫canal,可以完成订阅binlog日志的功能。

文章编写参考了:https://blog.csdn.net/chen134225/article/details/81669047

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值