Redis与数据库如何保持数据一致性


读写操作一致性分析

引言

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

跟新:应用程序先从cache取数据,没有得到,则从数据库中取数据,成功后,放到缓存中。
命中:应用程序从cache中取数据,取到后返回。
失效:先把数据存到数据库中,成功后,再让缓存失效。
另外,知名社交网站facebook也在论文《Scaling Memcache at Facebook》中提出,他们用的是先更新数据库,再删缓存的策略

读操作业务流程,大家应该没啥疑问,操作流程如下:

写操作流程分歧比较严重,如下分析三种更新缓存策略

  • 先更新数据库,再更新缓存
  • 先删除缓存,再更新数据库
  • 先更新数据库,再删除缓存

第一种:先更新数据库,再更新缓存分析

这种业界比较统一,从性能,业务,技术角度都不建议

  1. 线程安全角度
    同时有请求A和请求B进行更新操作,那么会出现
    (1)线程A更新了数据库
    (2)线程B更新了数据库
    (3)线程B更新了缓存
    (4)线程A更新了缓存
    这就出现请求A更新缓存应该比请求B更新缓存早才对,但是因为网络等原因,B却比A更早更新了缓存。这就导致了脏数据,因此不考虑。

  2. 业务场景角度
    有如下两点:
    (1)如果你是一个写数据库场景比较多,而读数据场景比较少的业务需求,采用这种方案就会导致,数据压根还没读到,缓存就被频繁的更新,浪费性能。
    (2)如果你写入数据库的值,并不是直接写入缓存的,而是要经过一系列复杂的计算再写入缓存。那么,每次写入数据库后,都再次计算写入缓存的值,无疑是浪费性能的。显然,删除缓存更为适合。
    接下来讨论的就是争议最大的,先删缓存,再更新数据库。还是先更新数据库,再删缓存的问题。

第二种:先删缓存,再更新数据库

该方案会导致不一致的原因是。同时有一个请求A进行更新操作,另一个请求B进行查询操作。那么会出现如下情形:
(1)请求A进行写操作,删除缓存
(2)请求B查询发现缓存不存在
(3)请求B去数据库查询得到旧值
(4)请求B将旧值写入缓存
(5)请求A将新值写入数据库

  • 上述情况就会导致不一致的情形出现。而且,如果不采用给缓存设置过期时间策略,该数据永远都是脏数据。
    如何解决呢?采用延时双删策略,伪代码如下:
 redis.deleteKey(key);
 userService.update(id);
       
 Thread.sleep(1000);
 redis.deleteKey(key);
  • 双删策略,休眠时间是考虑的重点,是休眠1s还是多久? 需要根据业务情况您的写请求耗时多长,然后再此基础上加上几百ms即可
  • 假如删除缓存失败,如何处理?两种方案处理,主要思想通过重试的机制删除,直到成功为止
  1. 第一种方案:将删除失败的key放入消息队列,再业务系统订阅再重试机制删除

  2. 第二种方案:将删除失败的key放入消息队列,处理机制是将删除失败的key不再由业务系统处理,单独启独立的线程及不影响业务系统的操作来做重试删除机制

第三种:先更新数据库,再删除缓存

这种情况极端情况会存在并发问题么,假设这会有两个请求,一个请求A做查询操作,一个请求B做更新操作,那么会有如下情形产生
(1)缓存刚好失效
(2)请求A查询数据库,得一个旧值
(3)请求B将新值写入数据库
(4)请求B删除缓存
(5)请求A将查到的旧值写入缓存

分析发生这种情况的概率又有多少呢?发生上述情况有一个先天性条件,就是步骤(3)的写数据库操作比步骤(2)的读数据库操作耗时更短,才有可能使得步骤(4)先于步骤(5)可是,大家想想,数据库的读操作的速度远快于写操作的(不然做读写分离干嘛,做读写分离的意义就是因为读操作比较快,耗资源少),因此步骤(3)耗时比步骤(2)更短;这种情况发生概览极其低的, 正如引言所言fackbook采用的是这种方案;

小结: 没有一种方案策略是完美的,一致性问题是分布式存储解决方案一直以来的痛点, 问题都需要根据具体的业务场景再具体的分析,如上方案仅供参考;


免责声明!

本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系本站邮箱yoyou2525@163.com删除。



 
粤ICP备18138465号  © 2018-2025 CODEPRJ.COM