一文彻底搞懂 Redis 缓存与数据库一致性

打印 上一主题 下一主题

主题 518|帖子 518|积分 1554

1. 一致性问题

存储如mysql通常支持完备的ACID特性,因为可靠性,长期性等因素,性能普遍不高,高并发的查询会给mysql带来压力,造成数据库系统的不稳固。同时也轻易产生耽误。根据局部性原理,80%请求会落到20%的热点数据上,在读多写少场景,增加一层缓存非常有助提升系统吞吐量和健壮性。
一样平常的业务场景都是读多写少的,当客户端的请求太多,对数据库的压力越来越大,引入缓存来降低数据库的压力是必然选择,目前业内主流的选择基本是利用 Redis 作为数据库的缓存。但是引入缓存以后,对我们系统的设计带来了很大的挑战,其中缓存和数据库的数据一致性问题就是一个非常棘手的问题。
当数据库的数据发生变化的时间,引入 Redis 缓存以后,增加了数据操纵难度,既要操纵数据库又要操纵 Redis,对 Redis和数据库的操纵有 2 种方案:
1、先操纵 Redis,再操纵数据库
2、先操纵数据库,再操纵 Redis
我们无论选择以上的哪个方案,都希望数据操纵要么都乐成,要么都失败,我们不希望看到一个失败,一个乐成的结果,因为这样就产生了数据不一致的问题。
2. 针对读场景



  • 如果redis中存在用户所需数据,直接返回即可;如果不存在,则试图从db中读取数据,读取完成后,将该数据更新到redis中,避免下次再次查询或其他用户查询时,仍从db读取。
  • 在高并发场景下,两次请求同时请求redis,发现数据并不存在后,都会从db中加载数据,然后再重新将数据写入redis,无论两次请求的写操纵谁先谁后,数据本身是一样的,只不过额外举行了一次数据写覆盖。因此不会产生数据不一致的问题。

3. 针对写场景



  • 如果redis中本身不存在缓存数据,则直接修改db中的数据即可,不会产生数据不一致问题。
  • 如果redis中已存在缓存数据,则需要同时修改db和redis中的数据,但是二者修改操纵的执行必然存在先后次序。在高并发的场景下,就有大概产生数据不一致的情况。
那么针对此中数据不一致问题,就产生了以下两个疑问:
问题1、由于redis中的数据可有可无,那么当数据发生变化时,是对redis中的数据举行修改,照旧直接删除对应的redis,然后通过后续的读请求再回源db,将数据重新写入redis呢?
问题2、redis和db的数据写操纵的次序问题,是先更新redis,照旧先更新db?
4. 解决方案

4.1 问题1:缓存淘汰战略或缓存更新战略



  • 方案1: 淘汰缓存战略
    长处:操纵简单,直接将对应缓存删除即可
    缺点:由于缓存被删除后,下次的读请求无法掷中缓存,需回源db,将数据重新写入redis
  • 方案2: 更新缓存战略
    长处:缓存掷中率高,只要缓存举行了更新,后续的读请求不会出现缓存未掷中(cache miss)的情况
    缺点:在某些业务场景下,缓存更新的成本过大。且更新后的缓存不一定会被利用。
    综合分析
实在业界一样平常接纳的都是缓存淘汰战略,而非缓存更新战略。缘故起因有三:


  • 大多数情况下,redis缓存中的数据并不是完全copy db中的数据,而是将db中多张表的数据举行了重新盘算,筛选后更新到redis。如果在db某一张表的数据发生了变化的情况下,需要同步重新盘算redis中的值,成本过高。
  • 缓存更新后的新值,无法包管一定会有读请求掷中,如果不停没有请求掷中该部门冷数据,实在是产生了一定的资源浪费(盘算成本+存储成本)。
  • 相较于淘汰缓存战略中,仅有一次读请求cache miss的结果来说,淘汰缓存战略的缺点完全可以容忍。
举个例子来说,a表中的字段,1分钟更改了100次,如果接纳更新缓存战略,则需要盘算100次,哪怕1分钟内只有1次读请求;如果接纳淘汰缓存战略,如果1分钟内只有1次请求,则只需要盘算1次即可,开销大幅度降低。
4.2 问题2:redis和db写操纵,谁先谁后?

注意:以下的方案讨论都是基于redis淘汰缓存操纵以及数据库更新操纵包管乐成(可通过重试机制解决)的情况下,高并发的业务场景中的解决方案。
方案1,先淘汰缓存,后更新数据库的战略,有大概导致长时间的数据不一致问题,可以通过延时双删 or 异步更新缓存战略举行解决。
方案2,先更新数据库,后更新缓存,有大概导致极短时间内的数据不一致,但是数据终极是一致的。


  • 方案1: 先淘汰缓存,后更新数据库

    • 正常情况
      A请求举行写操纵,先淘汰缓存,再更新数据库
      B请求举行读操纵,由于A请求已将缓存淘汰,B请求没有在redis中发现所需数据,因此从数据库中读取数据,并更新缓存到redis中
    • 异常情况1
      A请求举行写操纵,先淘汰缓存
      B请求举行读操纵,由于A请求已将缓存淘汰,B请求没有在redis中发现所需数据,因此从数据库中读取数据,并更新缓存到redis中。注意,此时redis中被更新的依然是老数据,A请求的数据库更新操纵尚未完成
      A请求举行数据库更新操纵。此时,数据库中是新数据,redis缓存中是老数据,产生了数据不一致的问题。且该不一致会不停持续到缓存自然失效或者下次的更新操纵

      • 对于该种异常情况,提供两种解决思绪:

        • 异步更新缓存。
          A请求举行写操纵,先淘汰缓存
          B请求举行读操纵,由于A请求已将缓存淘汰,B请求没有在redis中发现所需数据,因此从数据库中读取数据。注意,此时不向redis写入新的缓存战略
          A请求通过订阅数据库binlog,对redis缓存数据举行异步更新
          该方案虽然解决了数据不一致的问题,但是在数据库更新操纵完成前,所有的读请求都会直接打到数据库上,具有比较大的风险。
        • 延时双删。
          A请求举行写操纵,先淘汰缓存
          B请求举行读操纵,由于A请求已将缓存淘汰,B请求没有在redis中发现所需数据,因此从数据库中读取数据,并更新缓存到redis中。注意,此时redis中被更新的依然是老数据,A请求的数据库更新操纵尚未完成。假设该步骤耗时N秒
          A请求举行数据库更新操纵。
          由于此时redis中写入了老数据,因此A请求在休眠M秒后(M略大于N),再次对redis举行淘汰缓存操纵
          该方案虽然解决了数据不一致的问题,但是由于请求A在更新完数据库之后,需要休眠M秒再次淘汰缓存,一定程度上影响了数据更新操纵的吞吐量。可以实验将等待M秒更新redis的操纵放到另一个单独的线程(比如消息队列 + 重试机制)。可以有用缓解吞吐量降低的问题。


    • 异常情况2
      A请求举行读操纵,此时redis缓存中没有数据,因此直接从数据库中读取数据
      B请求举行写操纵,先淘汰缓存,再更新数据库
      A请求举行将从数据库中读到的老数据,更新到redis。此时产生数据不一致问题。
      该种异常情况发生概率极低,一样平常读操纵比写操纵要快。如有担心,可以接纳上述的延时删除战略

  • 方案2: 先更新数据库,后更新淘汰缓存

    • 正常情况
      A请求举行写操纵,先更新数据库,再淘汰缓存
      B请求举行读操纵,由于A请求已将缓存淘汰,B请求没有在redis中发现所需数据,因此从数据库中读取数据,并更新缓存到redis中
    • 异常情况1
      A请求举行写操纵,先更新数据库
      B请求举行读操纵,由于A请求尚未淘汰缓存,B请求在redis中发现所需数据,因此直接返回老数据,产生了数据不一致的问题
      A请求淘汰缓存。
      C请求举行读操纵,发现redis中没有数据,因此从数据库中读取新数据,并更新至缓存。数据不一致的问题解决。
      该场景下,数据终极一致,只是在高并发下产生了一小段时间的数据不一致。
    • 异常情况2
      A请求举行读操纵,此时redis缓存中没有数据,因此直接从数据库中读取数据
      B请求举行写操纵,更新数据库,并将redis中缓存举行了淘汰(虽然此时redis中并没有任何的缓存)
      A请求将从数据库中读到的老数据,更新到redis。此时产生数据不一致问题。
      该种异常情况发生概率极低,一样平常读操纵比写操纵要快。如有担心,可以接纳上述的延时删除战略


免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!更多信息从访问主页:qidao123.com:ToB企服之家,中国第一个企服评测及商务社交产业平台。

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

天津储鑫盛钢材现货供应商

金牌会员
这个人很懒什么都没写!

标签云

快速回复 返回顶部 返回列表