前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >MySQL与缓存一致性问题

MySQL与缓存一致性问题

作者头像
leobhao
发布2022-06-28 18:48:07
6380
发布2022-06-28 18:48:07
举报
文章被收录于专栏:涓流涓流

数据一致性问题

“数据一致”一般指的是:缓存中有数据,缓存的数据值 = 数据库中的值。一致性又分为几种程度:

  • 强一致性:这种一致性级别是最符合用户直觉的,它要求系统写入什么,读出来的也会是什么,用户体验好,但实现起来往往对系统的性能影响大
  • 弱一致性:这种一致性级别约束了系统在写入成功后,不承诺立即可以读到写入的值,也不承诺多久之后数据能够达到一致,但会尽可能地保证到某个时间级别(比如秒级别)后,数据能够达到一致状态
  • 最终一致性:最终一致性是弱一致性的一个特例,系统会保证在一定时间内,能够达到一个数据一致的状态。这里之所以将最终一致性单独提出来,是因为它是弱一致性中非常推崇的一种一致性模型,也是业界在大型分布式系统的数据一致性上比较推崇的模型

只读缓存情况

只读缓存:新增数据时,直接写入数据库;更新(修改/删除)数据时,先删除缓存。 后续,访问这些增删改的数据时,会发生缓存缺失,进而查询数据库,更新缓存。

读取数据流程:

更新数据流程:

在更新数据的流程中会有个时序问题:更新数据库与删除缓存的顺序,这里会发生数据不一致的问题

无并发情况下

先更新数据库再删除缓存:

  1. 更新数据库(成功)
  2. 删除缓存(失败)

如果第二步执行失败则会导致, 缓存中还是旧值, 数据不一致

反之如果是先删除缓存, 再更新数据库, 就第二步更新数据库失败了, 只是缓存被删除了, 读操作回源的时候还是会把数据库中的值load回缓存, 数据还是一致的

解决方案;消息队列 + 异步重试

高并发情况下
先删除缓存再更新数据库

两个线程同时做更新操作, 由于网络问题可能发生如下时序:

时序

线程A

线程B

T1

删除数据X的缓存

T2

读取X,缓存MISS

T3

从数据库Load X 的值到缓存

T4

更新数据库中X的值

或者:

时序

线程A

线程B

T1

删除数据X的缓存

T2

读取X,缓存MISS

T3

更新数据库中X的值

T4

从数据库Load X 的值到缓存

这种情况下会导致缓存中是旧值(线程B Load 进去的值)而数据库中是新值

解决方案: 设置缓存过期时间 + 延时双删, 时序如下:

时序

线程A

线程C

线程D

T5

Sleep(N)

读取到缓存旧值

T6

删除缓存数据

T7

更新数据库中X的值

缓存miss, load数据库值到缓存

先更新数据库再删除缓存
  1. 线程A先更新了数据库还没来得及删除缓存,此时线程B读取了缓存中还未来得及更新的值 时序线程A线程BT1更新数据库中数据XT2读取X,命中缓存T3删除缓存X
  2. Mysql读写分离架构下如果产生主从延迟也会导致不一致

时序

线程A

线程C

线程D

T1

更新主库X=1

T2

删除缓存

T3

缓存miss,查询从库得到从库旧值(X=0)

T4

旧值写入缓存

T5

此刻从库同步完成

解决方案:

(1) 针对读写分离的场景, 可以采取延迟消息删除缓存(这个延迟的时间要根据项目情况控制下), 另外也要控制主从延迟的时间

(2) 针对第一种情况, 其实只是小段时间的不一致, 一般业务可以接受这种情况, 保证最终一致即可。如果业务一定要强一致, 可以采取: a. 更新数据加写锁 b. 查询数据加读锁

(3) 采取数据库binlog来淘汰缓存, 这种方法成本较高

总结

优先使用“先更新数据库再删除缓存”的执行时序,原因主要有两个:

  • 先删除缓存值再更新数据库,有可能导致请求因缓存缺失而访问数据库,给数据库带来压力;
  • 业务应用中读取数据库和写缓存的时间有时不好估算,进而导致延迟双删中的sleep时间不好设置。

读写缓存情况

读写缓存:增删改在缓存中进行,并采取相应的回写策略,同步数据到数据库中

  • 同步直写:使用事务,保证缓存和数据更新的原子性,并进行失败重试(如果Redis 本身出现故障,会降低服务的性能和可用性)
  • 异步回写:写缓存时不同步写数据库,等到数据从缓存中淘汰时,再写回数据库(没写回数据库前,缓存发生故障,会造成数据丢失。该策略在秒杀场中有见到过,业务层直接对缓存中的秒杀商品库存信息进行操作,一段时间后再回写数据库。
无并发情况

执行顺序性

潜在问题

结果

解决策略

先更新缓存,后更新数据库

更新缓存成功,更新数据库失败

数据库中为旧值

消息队列+重试

先更新数据库,后更新缓存

更新数据库成功,更新缓存失败

请求命中缓存,读取缓存旧值

消息队列+重试机制;订阅Binlog日志

高并发情况
写+读并发
  • 先更新数据库,再更新缓存 1.线程A先更新数据库 2.线程B读取数据,命中缓存,读取到旧值 3.线程A更新缓存成功,后续的读请求会命中缓存得到最新值 A 更新了数据库, 还没来得及更新缓存, 这个时候B Load 了缓存, 导致缓存是旧值
  • 先更新缓存,再更新数据库 1.线程A先更新缓存成功 2.线程B读取数据,此时线程B命中缓存,读取到最新值后返回 3.线程A更新数据库成功 这种场景下,虽然线程A还未更新完数据库,数据库会与缓存存在短暂不一致,但在这之前进来的读请求都能直接命中缓存,获取到最新值对业务影响较小

解决方案: 保存请求对缓存的读取记录,延时消息比较,发现不一致后,做业务补偿

写+写并发
  • 先更新数据库,再更新缓存

1.线程A和线程B同时更新同一条数据 2.更新数据库的顺序是先A后B 3.更新缓存时顺序是先B后A 这种场景下会导致缓存更新覆盖,缓存中其实是旧值(A的值, 应该是后更新数据库中B的值)

解决方案: 对于写请求,需要配合分布式锁使用。写请求进来时,针对同一个资源的修改操作,先加分布式锁,保证同一时间只有一个线程去更新数据库和缓存;没有拿到锁的线程把操作放入到队列中,延时处理。用这种方式保证多个线程操作同一资源的顺序性,以此保证一致性。

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2021-10-18 ,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 数据一致性问题
  • 只读缓存情况
    • 无并发情况下
      • 高并发情况下
        • 先删除缓存再更新数据库
      • 先更新数据库再删除缓存
        • 总结
        • 读写缓存情况
          • 无并发情况
            • 高并发情况
              • 写+读并发
              • 写+写并发
          相关产品与服务
          数据库
          云数据库为企业提供了完善的关系型数据库、非关系型数据库、分析型数据库和数据库生态工具。您可以通过产品选择和组合搭建,轻松实现高可靠、高可用性、高性能等数据库需求。云数据库服务也可大幅减少您的运维工作量,更专注于业务发展,让企业一站式享受数据上云及分布式架构的技术红利!
          领券
          问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档