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;
      

  • 插入一条带标记的文档,标记字段如 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 的数据,重试同步

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

不做大哥好多年xw

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值