ICode9

精准搜索请尝试: 精确搜索
首页 > 其他分享> 文章详细

缓存双写一致性总体介绍

2021-10-18 16:02:20  阅读:159  来源: 互联网

标签:缓存 删除 数据库 redis 更新 线程 一致性 双写


缓存双写一致性之更新策略

缓存双写一致性的理解

  1. 如果redis中有数据,需要和数据库中的值相同

  2. 如果redis中没有数据,数据库中的值要是最新值

缓存按照操作来分,有细分两种

只读缓存

读写缓存

  1. 同步直写策略: 写缓存时同步写数据库,数据库和缓存中的数据一致
  2. 对于读写缓存,要想保证数据库和缓存中的数据一致,就要采用同步直写策略

数据库和缓存一致性的几种更新策略

挂牌报错,凌晨升级

单线程,这种重量级的操作最好不要多线程

目的

总之,我们要达到最终一致性

给缓存设置过期时间,是保证最终一致性的解决方案。

我们可以对存入缓存的数据设置过期时间,所有的写操作以数据库为准,对缓存操作只是尽最大努力即可。也就是说如果数据库写成功,缓存更新失败,那么只要到达过期时间,则后面的读请求自然会从数据库中读取新值然后回填缓存,达到一致性,切记以mysql的数据库写入库为准。

三种更新策略(写操作要以数据库为准)

1. 先更新数据库,再更新缓存

案例

  1. 先更新mysql的某商品的库存,当前商品的库存是100,更新为99个。
  2. 先更新mysql修改为99成功,然后更新redis
  3. 此时假设异常出现,更新redis失败了,这导致mysql里面的库存是99而redis里面是100
  4. 上述情况发生,会让数据库里面和缓存redis里面数据不一致,读到脏数据。

结论

可以用,先更新数据库,再更新缓存,但是如果数据库更新成功,redis异常更新失败,会导致从redis中读取到了老的脏数据。

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

案例

  1. A线程先成功删除了redis里面的数据,然后去更新mysql,此时mysql正在更新中,还没有结束。(比如网络延时),B突然出现要来读取缓存数据。
  2. 此时redis里面的数据是空的,B线程来读取,先去读redis里面数据(已经被A线程delete掉了),此处出来2个问题:
    • B从mysql中获得了旧值
      • B线程发现redis中乜有(缓存缺失)马上去mysql里面读取,从数据库里面读出来的是旧值。
    • B会把获得的旧值写回redis
      • 获得旧值数据后返回前台并回写进redis(刚被A线程删除的旧数据有极大可能又被写回了)。
  3. A线程更新完mysql,发现redis里面的缓存是脏数据,A线程直接懵逼了。

两个并发操作,一个是更新操作,另一个是查询操作,A更新操作删除缓存后,B查询操作没有命中缓存,B线程把老数据读出来后放到缓存中,然后更新操作更新了数据库。于是,在缓存中的数据还是老的数据,导致缓存中的数据时脏的,而且还一直这样脏下去了

异常情况

低并发: 旧值被写回redis中
高并发: 缓存击穿

解决方案(延时双删策略)

在这里插入图片描述

线程A加上sleep的这段时间,就是为了让线程B能够先从数据库读取数据,再把缺失的数据写入缓存,然后,线程A再进行删除。所以,线程Asleep的时间,就需要大于线程B读取数据再写入缓存的时间。这样一来,其他线程读取数据时,会发现缓存缺失,所以会从数据库中读取最新值。因为这个方案会在第一次删除缓存值后,延迟一段时间再次进行删除,所以我们也把它叫做"延迟双删"。

双删方案面试题

1. 这个删除该休眠多久呢

线程Asleep的时间,就需要大于线程B读取数据再写入缓存的时间。
这个时间怎么确定呢?
在业务程序运行的时候,统计下线程读数据和写缓存的操作时间,自行评估自己的项目的读数据业务逻辑的耗时,以此为基础进行估算。然后写数据的休眠时间则在读数据业务逻辑的耗时基础上加百毫秒即可。

这么做的目的,就是确保请求结束,写请求可以删除读请求造成的缓存脏数据

2. 当前演示的效果是mysql单机,如果mysql主从读写分离结构如何?
  1. 请求A进行写操作,删除缓存
  2. 请求A将数据写入数据库了
  3. 请求B查缓存发现,缓存没有值
  4. 请求B去从库查询,这时,还没有完成主从同步,因此查询到的是旧值
  5. 请求B将旧值写入缓存
  6. 数据库完成主从同步,从库变为新值 上述情形,就是数据不一致的原因。还是使用延时双删策略

只是睡眠时间修改为在主从同步的延时时间基础上,再加几百毫秒。

3. 这种同步淘汰策略,吞吐量降低怎么办?

第二次删除使用异步线程,一异步删除。这样,写的请求就不用沉睡一段时间后,再返回。这么做,加大吞吐量。

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

假如缓存删除失败或者来不及,导致请求再次访问redis时缓存命中,读到的是缓存旧值。

业务指导思想

  1. 老外论文
  2. 知名社交网站facebook也在论文《Scaling Memcache at Facebook》书中提出
  3. 我们上面的canal也是类似的思想
    订阅binlog程序在mysql中有现成的中间件叫做canal,可以完成订阅binlog日志的功能。

解决方案(重试机制+引入MQ)

在这里插入图片描述
流程:

  1. 更新数据库数据
  2. 数据库会将操作信息写入binlog日志当中
  3. 订阅程序提取出所需要的数据以及key
  4. 另起一段非业务代码,获得该信息
  5. 尝试删除缓存操作,发现删除失败
  6. 将这些信息发送至消息队列
  7. 重新从消息队列中获得改数据,重试操作。
重试机制
  1. 可以把要删除的缓存值或者是要更新的数据库值暂存到消息队列中(六使用Kafka/RabbitMQ等)。
  2. 当程序没有能够成功地删除缓存值或者是更新数据库值时,可以从消息队列中重新读取这些值,然后再次进行删除或更新。
  3. 如果能够成功地删除或更新,我们就要把这些值从消息队列中去除,一面重复操作,此时,我们也可以保证数据库和缓存的数据一致了,否则还需要再次进行重试
  4. 如果重试超过的一定次数后还是没有成功,我们就需要项业务层发送报错信息了,通知运维人员。

总结

在这里插入图片描述

标签:缓存,删除,数据库,redis,更新,线程,一致性,双写
来源: https://blog.csdn.net/qq_43478625/article/details/120817953

本站声明: 1. iCode9 技术分享网(下文简称本站)提供的所有内容,仅供技术学习、探讨和分享;
2. 关于本站的所有留言、评论、转载及引用,纯属内容发起人的个人观点,与本站观点和立场无关;
3. 关于本站的所有言论和文字,纯属内容发起人的个人观点,与本站观点和立场无关;
4. 本站文章均是网友提供,不完全保证技术分享内容的完整性、准确性、时效性、风险性和版权归属;如您发现该文章侵犯了您的权益,可联系我们第一时间进行删除;
5. 本站为非盈利性的个人网站,所有内容不会用来进行牟利,也不会利用任何形式的广告来间接获益,纯粹是为了广大技术爱好者提供技术内容和技术思想的分享性交流网站。

专注分享技术,共同学习,共同进步。侵权联系[81616952@qq.com]

Copyright (C)ICode9.com, All Rights Reserved.

ICode9版权所有