目录
背景:
最近在复习Redis的知识,发现很多面试题提到双写一致性的强一致性解决方案都会使用延时双删,但是仔细研究之后发现延时双删还是有很多问题。我们先来复习一下这个知识点,有基础的可以直接点目录看延迟双删。
介绍:
在上一篇文章里,我们介绍了Redis持久化的两种方式,持久化能确保Redis中的数据存到磁盘中的一致性,那把磁盘的数据修改之后又如何保证Redis中的数据同步修改呢?这就是今天的题,Redis双写一致性。
解决方案:
方案一:先删后写
在这种方案中,如果线程2因为缓存过期等原因要去读数据库且在线程1修改之前读,然后做了一些业务处理,在线程1之后写入缓存的话,就会造成Redis中长期存在脏数据。
方案二:先写后删
在这种方案中,由于线程1删除了缓存,导致线程2读去缓存失败,直接读数据库,如果这个行为发生在线程1写数据库之前,那么线程2读到并写入缓存的还是脏数据。
方案三:延迟双删
在前两种特殊情况中,都出现了两个点:
① 线程2在线程1更新数据前查了数据库;
② 线程2在线程1删除缓存之前把脏数据写入了缓存。
那么延迟双删的解决方法是在写完之后隔一段时间才删除缓存,避免了写入脏数据,相信有的朋友就有疑问了,那么延迟双删的第一次删除有什么作用呢?其实也不是完全没作用,第一次删除是可以减少高并发情况下中间读到脏数据的量的,不过只能减少不能完全避免(从工作的角度来说强一致性很多时候是允许误差的,要不然也不会有最终一致性,但是从编程思维来说,稳定性不够的系统得分要大打折扣)。
延迟双删缺陷:
在低并发的情况下,我们完全可以通过加锁的方式来达成双写一致性,所以这里就不讨论了,但是高并发的情况下问题就来了,高并发我们删除缓存必然会造成缓存击穿的问题,其实缓存击穿影响不大,因为能解决,但是它的解决方案数据逻辑过期中有个问题,在数据真正写入缓存前,其他线程读到的数据都是过期数据。
从这我们就看出来了,延迟双删的第一次删除本来是减少数据真正写入缓存前的旧数据,但是数据逻辑删除做的是在数据真正写入缓存之前返回的全是旧数据,那第一次删除好像完全没有意义了。
改进方案--CAS乐观锁:
阿里开发手册里提到一条,所有表都要有一个gmt_modified字段,也就是以格林尼治标准时间为基准的updateTime,我们可以以这个字段为CAS的比较对象,然后利用上面提到的逻辑过期思想,得到我们的改进方案:
第1步:更新数据库
第2步:比较数据库和缓存中gmt_modified字段,写入gmt_modified较新的一个数据
方案四:最终一致性
上面提到的几种方案都是强一致性方案,如果业务不强制要求强一致性,那么用最终一致性方案是最好的,比如售票业务,我们并不要求用户买票的时候看到有没有票,只要要求每张票只能卖一个人就够了,也就是保证最终一致性就够了。
一、延迟双删
虽然延迟双删是一种同步的处理思路,但是由于有休眠时间,所以跟异步差别不大,并不算严格意义上的强一致性,这里就把它放到了最终一致性里。
二、Canal监听Redis的binlog文件
在Redis的主从集群的知识点里我们知道从节点可以读取到主节点的所有数据,Mysql也有类似的主从模式,而Canal可以把自己伪装成Mysql的从节点从而获得mysql的binlog日志,然后通过异步的方式去更新Redis里的内容,从而达到最终一致性。
最后:
所有技术都需要为业务服务,离开业务对比哪种方案好是没有意义的,只有最适合业务的技术,没有最好的技术。