问小白 wenxiaobai
资讯
历史
科技
环境与自然
成长
游戏
财经
文学与艺术
美食
健康
家居
文化
情感
汽车
三农
军事
旅行
运动
教育
生活
星座命理

亿级电商流量,高并发下Redis与MySQL的数据一致性如何保证

创作时间:
作者:
@小白创作中心

亿级电商流量,高并发下Redis与MySQL的数据一致性如何保证

引用
CSDN
1.
https://blog.csdn.net/liuguojiang1128/article/details/136329921

在高并发的电商系统中,如何保证Redis缓存与MySQL数据库之间的数据一致性是一个关键问题。本文详细探讨了四种常见的解决方案,并分析了它们的优劣和适用场景,为系统架构师和开发工程师提供了宝贵的参考。

前言

在高并发的电商系统中,使用Redis作为缓存可以有效减轻数据库压力,但也会带来数据一致性的问题。例如,当数据被更新或删除时,缓存可能无法及时感知这些变化,导致缓存数据与数据库数据不一致。为了解决这个问题,常见的解决方案有以下四种:

  1. 先更新缓存,再更新数据库
  2. 先更新数据库,再更新缓存
  3. 先删除缓存,后更新数据库
  4. 先更新数据库,后删除缓存

下面将逐一分析这些方案的可行性。

一、先更新缓存,再更新数据库

这个方案一般不推荐使用。原因是如果更新缓存成功,但更新数据库出现异常,会导致缓存数据与数据库数据完全不一致,且这种问题很难察觉,因为缓存中的数据会一直存在。

二、先更新DB,再更新缓存

这个方案也一般不推荐使用。原因与方案1类似,如果数据库更新成功但缓存更新失败,同样会出现数据不一致的问题,且这种问题不容易被发现,因为缓存中一直存在数据。

三、先删除缓存,后更新DB

这个方案在并发场景下也会出现问题。具体原因如下:

  1. 请求A先删除Redis中的数据,然后去更新数据库
  2. 此时请求B看到Redis中的数据是空的,回去数据库中查询该值,补充到Redis缓存中
  3. 此时请求A并没有更新成功,或者是事务还未提交,请求B去数据库查询得到旧值

为了解决这个问题,可以采用延时双删的策略:

  1. 先淘汰缓存
  2. 再写数据库
  3. 休眠1s,再次淘汰缓存

这样做可以将1s内造成的缓存脏数据再次删除。但是,这个1s的具体时间需要根据项目的实际情况来评估:

  1. 评估读数据业务逻辑的耗时(可以利用SkyWalking等监控工具)
  2. 评估写数据的休眠时间(在读数据业务耗时的基础上加几百ms)

然而,这种方法并不能完全解决问题:

  1. 评估的延时时间可能因系统压力而变得不准确
  2. 如果使用MySQL的读写分离,主从同步之间也会有时间差

解决方案有两个:

  1. 在主从同步的延时时间基础上加几百毫秒
  2. 对Redis进行填充数据查询时强制走主库查询

如果担心同步延时双删会影响接口吞吐量,可以将第二次删除操作改为异步处理。

总的来说,先删除缓存再更新数据库的方式存在以下问题:

  1. 可能导致大量读请求因缓存缺失而访问数据库
  2. 读请求和写缓存的时间估算不够准确,导致延时双删的sleep时间难以设置

四、先更新DB,后删除缓存

这种方案下依然存在数据不一致的问题,尤其是在高并发场景下:

  1. 缓存刚好失效
  2. 请求A查询数据库得到旧值
  3. 请求B将新值写入数据库
  4. 请求B删除缓存
  5. 请求A将查到的旧值写入缓存

但是,这种情况发生的概率较低,因为数据库的读操作通常比写操作快。如果一定要解决这个问题,可以采用以下方案:

  1. 给缓存设置过期时间
  2. 采用异步延时删除策略
  3. 在读请求加读锁,写请求加写锁(但会损失性能)

方案补充(重要)

3、4都属于删除缓存类,其实删除缓存类都会有一个共同的问题,那就是在删除缓存的阶段出错了怎么办?此时再读取缓存的时候每次都是错误的数据了。
此时解决方案有两个:

一、利用消息队列进行删除失败的补偿

具体的业务逻辑如下:

  1. 请求 A 先对数据库进行更新操作
  2. 在对 Redis 进行删除操作的时候发现报错,删除失败
  3. 此时将 Redis 的 key 作为消息体发送到消息队列中
  4. 系统接收到消息队列发送的消息后
  5. 再次对 Redis 进行删除操作

    但是这个方案会有一个缺点,就是会对业务代码造成大量的侵入,深深的耦合
    在一起。
    所以还有一个优化的方案

二、订阅MySQL的binlog日志,异步删除

我们知道对 Mysql 数据库更新操作后 ,在binlog日志中我们都能够找到相应的操作,那么我们可以订阅 Mysql数据库 的binlog日志对缓存进行操作,这样就达到了一个解耦的目的了。
业务代码流程如下:

  1. 更新数据库,更新完成后,触发binlog消息
  2. 经常B(消费者)订阅binlog消息,执行缓存删除操作
  3. 缓存删除失败,将删除任务丢到消息队列中
  4. 进程B获取删除失败任务
  5. 执行二次删除redis缓存

    说到底就是通过数据库的 binlog 来异步淘汰 key,利用工具(canal)将 binlog
    日志采集发送到 MQ 中,然后通过 ACK 机制确认处理删除缓存。
    先更新 DB,后删除缓存,这种方式,被称为Cache Aside Pattern,属于缓存更新的经典设计模式之一。
    所以如果大家做缓存与数据库的同步,推荐大家选择这一种方式。

总结

在高并发的电商系统中,保证Redis与MySQL的数据一致性是一个复杂但重要的问题。通过分析四种常见的解决方案,我们可以看到每种方案都有其优劣。推荐使用"先更新数据库,后删除缓存"的方案(Cache Aside Pattern),并结合消息队列或binlog日志等技术手段来进一步优化数据一致性问题。

© 2023 北京元石科技有限公司 ◎ 京公网安备 11010802042949号