亿级电商流量,高并发下Redis与MySQL的数据一致性如何保证
亿级电商流量,高并发下Redis与MySQL的数据一致性如何保证
在高并发的电商系统中,如何保证Redis缓存与MySQL数据库之间的数据一致性是一个关键问题。本文详细探讨了四种常见的解决方案,并分析了它们的优劣和适用场景,为系统架构师和开发工程师提供了宝贵的参考。
前言
在高并发的电商系统中,使用Redis作为缓存可以有效减轻数据库压力,但也会带来数据一致性的问题。例如,当数据被更新或删除时,缓存可能无法及时感知这些变化,导致缓存数据与数据库数据不一致。为了解决这个问题,常见的解决方案有以下四种:
- 先更新缓存,再更新数据库
- 先更新数据库,再更新缓存
- 先删除缓存,后更新数据库
- 先更新数据库,后删除缓存
下面将逐一分析这些方案的可行性。
一、先更新缓存,再更新数据库
这个方案一般不推荐使用。原因是如果更新缓存成功,但更新数据库出现异常,会导致缓存数据与数据库数据完全不一致,且这种问题很难察觉,因为缓存中的数据会一直存在。
二、先更新DB,再更新缓存
这个方案也一般不推荐使用。原因与方案1类似,如果数据库更新成功但缓存更新失败,同样会出现数据不一致的问题,且这种问题不容易被发现,因为缓存中一直存在数据。
三、先删除缓存,后更新DB
这个方案在并发场景下也会出现问题。具体原因如下:
- 请求A先删除Redis中的数据,然后去更新数据库
- 此时请求B看到Redis中的数据是空的,回去数据库中查询该值,补充到Redis缓存中
- 此时请求A并没有更新成功,或者是事务还未提交,请求B去数据库查询得到旧值
为了解决这个问题,可以采用延时双删的策略:
- 先淘汰缓存
- 再写数据库
- 休眠1s,再次淘汰缓存
这样做可以将1s内造成的缓存脏数据再次删除。但是,这个1s的具体时间需要根据项目的实际情况来评估:
- 评估读数据业务逻辑的耗时(可以利用SkyWalking等监控工具)
- 评估写数据的休眠时间(在读数据业务耗时的基础上加几百ms)
然而,这种方法并不能完全解决问题:
- 评估的延时时间可能因系统压力而变得不准确
- 如果使用MySQL的读写分离,主从同步之间也会有时间差
解决方案有两个:
- 在主从同步的延时时间基础上加几百毫秒
- 对Redis进行填充数据查询时强制走主库查询
如果担心同步延时双删会影响接口吞吐量,可以将第二次删除操作改为异步处理。
总的来说,先删除缓存再更新数据库的方式存在以下问题:
- 可能导致大量读请求因缓存缺失而访问数据库
- 读请求和写缓存的时间估算不够准确,导致延时双删的sleep时间难以设置
四、先更新DB,后删除缓存
这种方案下依然存在数据不一致的问题,尤其是在高并发场景下:
- 缓存刚好失效
- 请求A查询数据库得到旧值
- 请求B将新值写入数据库
- 请求B删除缓存
- 请求A将查到的旧值写入缓存
但是,这种情况发生的概率较低,因为数据库的读操作通常比写操作快。如果一定要解决这个问题,可以采用以下方案:
- 给缓存设置过期时间
- 采用异步延时删除策略
- 在读请求加读锁,写请求加写锁(但会损失性能)
方案补充(重要)
3、4都属于删除缓存类,其实删除缓存类都会有一个共同的问题,那就是在删除缓存的阶段出错了怎么办?此时再读取缓存的时候每次都是错误的数据了。
此时解决方案有两个:
一、利用消息队列进行删除失败的补偿
具体的业务逻辑如下:
- 请求 A 先对数据库进行更新操作
- 在对 Redis 进行删除操作的时候发现报错,删除失败
- 此时将 Redis 的 key 作为消息体发送到消息队列中
- 系统接收到消息队列发送的消息后
- 再次对 Redis 进行删除操作
但是这个方案会有一个缺点,就是会对业务代码造成大量的侵入,深深的耦合
在一起。
所以还有一个优化的方案
二、订阅MySQL的binlog日志,异步删除
我们知道对 Mysql 数据库更新操作后 ,在binlog日志中我们都能够找到相应的操作,那么我们可以订阅 Mysql数据库 的binlog日志对缓存进行操作,这样就达到了一个解耦的目的了。
业务代码流程如下:
- 更新数据库,更新完成后,触发binlog消息
- 经常B(消费者)订阅binlog消息,执行缓存删除操作
- 缓存删除失败,将删除任务丢到消息队列中
- 进程B获取删除失败任务
- 执行二次删除redis缓存
说到底就是通过数据库的 binlog 来异步淘汰 key,利用工具(canal)将 binlog
日志采集发送到 MQ 中,然后通过 ACK 机制确认处理删除缓存。
先更新 DB,后删除缓存,这种方式,被称为Cache Aside Pattern,属于缓存更新的经典设计模式之一。
所以如果大家做缓存与数据库的同步,推荐大家选择这一种方式。
总结
在高并发的电商系统中,保证Redis与MySQL的数据一致性是一个复杂但重要的问题。通过分析四种常见的解决方案,我们可以看到每种方案都有其优劣。推荐使用"先更新数据库,后删除缓存"的方案(Cache Aside Pattern),并结合消息队列或binlog日志等技术手段来进一步优化数据一致性问题。