12.MySQL和Redis一致性
# 01.如何保证MySQL和Redis一致性
没有完美的方案,只有最适合某场景的方案
这个是数据一致性、系统性能和系统复杂度的选择与取舍
# 1、先更新MySQL,再更新Redis
如果先更新MySQL成功了,还未对Redis进行更新的间隙期,这时如果请求过来,读到的都是Redis的更新前数据
如果先更新MySQL成功了,再更新Redis失败了的话,后面的请求读到的都是Redis的更新前数据,并且后续的补救方案很难做
补救方案一
为Redis更新失败,将MySQL中的对应数据也回滚了,以此达到两者数据的一致性
但MySQL是主数据源,它代表的是数据的“权威性”,这样做显然并不合理
补救方案二
通过Redis重试更新的方式进行补救。但如果重试也失败了,还要继续重试吗?
另外,重试时间间隔设置多少?时间间隔设置长了,影响业务的时间也会变长
时间间隔设置短了,重试成功率又会降低,这些其实都是问题
# 2、先更新Redis,再更新MySQL
这个方案,要比方案一的“先更新MySQL,再更新Redis”合理一些
原因在于更新完Redis的话,哪怕还没更新MySQL,这时如果请求过来,读到的都是Redis更新后的新数据
另外,先更新Redis成功,再更新MySQL失败,可以通过再删除Redis所对应的数据进行补救
缺点:读到的都是Redis未生效的新数据
# 3、先更新MySQL,再删除Redis
如果先更新MySQL成功了,还未对Redis进行删除的间隙期,这时如果请求过来,读到的都是Redis的删除前数据。
如果先更新MySQL成功了,再删除Redis失败了的话,后面的请求读到的都是Redis的删除前数据,并且后续的补救方案很难做
# 4、先删除Redis,再更新MySQL
如果先删除Redis成功了,还未对MySQL进行更新的间隙期
只存在于MySQL一个存储载体中,也就没有了数据一致性的问题
如果先删除Redis成功了,再更新MySQL失败了的话
只存在于MySQL一个存储载体中,所谓的补救方案也就不需要了,直接当这条数据没更新成功
产生问题
- 某商品的库存数为10个,用户A购买一件商品时进行库存扣减,因此第一步先删除了Redis中的库存数
- 这时,用户B查询该商品的库存,发现Redis中并没有该商品的库存,于是从MySQL中读取库存数后,写入到了Redis中(10个)
- 然后,用户A更新数据库,将库存数从10个扣减为9个
- 最终,Redis中的库存数是10个,MySQL中的库存数是9个
# 5、分布式锁
- 分布式锁完全可以解决一致性问题,但是性能会降低,我们设置缓存的目的就是为了性能
# 02.ES和MySQL双写
一致性要求适中
- 推荐使用
消息队列解耦方案
,既保证了高可用,又降低了系统耦合一致性要求高
- 使用
分布式事务方案
确保强一致性一致性要求不高
- 采用
异步校对修复机制
辅助即可
# 1、业务逻辑重试机制
单独重试失败的一方
- 当写入 MySQL 成功但写入 ES 失败时,将失败的操作记录在一个消息队列或本地事务日志中,并异步重试
- 反之,当写入 ES 成功但写入 MySQL 失败时,同样记录并异步重试
优点
- 易于实现,逻辑清晰
缺点
如果频繁失败,会增加重试的系统开销
在高并发场景下,可能造成数据延迟
# 2、基于消息队列的解耦
采用
消息队列实现写入的异步解耦
业务逻辑优先写入 MySQL
成功后,将数据变更事件(如新增、更新、删除操作)写入消息队列(如 Kafka、RabbitMQ)
监听消息队列的消费者负责将变更写入 ES,确保最终一致性
优点
提高系统解耦性
消息队列自带重试和持久化机制,减少失败影响
缺点
引入消息队列增加了系统复杂性
需要处理消息重复消费的问题
# 3、双写事务保证(TCC)
MySQL 新插入的记录在全局事务完成前,状态字段设为
pending
# 通过业务唯一标识(如订单号 `id`)约束插入,避免重复写入 INSERT INTO orders (id, status, data) VALUES (123, 'pending', 'order data'); # 通过业务唯一标识(如订单号 id)约束插入,避免重复写入 UPDATE orders SET status = 'completed' WHERE id = 123; # 如果 ES 写入失败,执行回滚(可物理删除或将状态更新为 failed) UPDATE orders SET status = 'failed' WHERE id = 123;
1
2
3
4
5
6
7
8
插入一条带标记的文档,标记字段如
status: "unconfirmed"
如果 MySQL 和 ES 的事务成功,更新状态为
confirmed
如果事务失败,删除文档或将状态标记为
failed
PUT /orders/_doc/123 {"id":123,"data":"order data","status":"unconfirmed"}
1
2
# 4、异步校对和修复机制
通过异步校验定期检查 MySQL 和 ES 的数据一致性,并修复不一致的数据
定期将 MySQL 数据和 ES 数据进行校验(如对比主键、版本号、哈希值等)
找出不一致的数据并重新写入 ES 或 MySQL,确保最终一致性
# 5、基于状态标识的方案
在 MySQL 中为每条数据维护一个状态标识,标记数据是否成功同步到 ES
在 MySQL 表中新增一个字段
0
表示未同步1
表示同步成功
在业务写入 MySQL 时,默认设置为
0
,然后通过异步任务同步到 ES,并更新状态为1
定期扫描
sync_status=0
的数据,重试同步