当前位置: 首页 > news >正文

分布式事务-MQ事务消息学习与落地方案

本文参考:

Apache RocketMQ 事务消息

基础概念

分布式事务解决的核心问题:降低分布式环境下如网络超时、系统宕机带来的不良影响,保证分布式系统中不同应用程序的数据一致性(重点关注事务 ACID 特性中的 A 原子性和 C 一致性,因为 I 隔离性在分布式环境中天然存在,持久化 D取决于业务是否需要将结果落库了)。

MQ 事务消息实现分布式事务「最终一致性」的核心:基于 half 消息 + commit/rollback 消息的两阶段提交,配合回调反查、MQ 重试机制,实现本地事务和下游消息发送的一致性。

看起来概念是有点复杂,想想看为啥别的方案就不行呢?主要还是有几个问题

  1. MQ 不可靠,可能会丢消息,而且我们会被 MQ 超时影响
  2. 发 MQ 消息和本地事务没法保证一致性
  • 本地事务和发 MQ 消息放一块
    本地事务发送成功了,机器宕机,MQ 发消息执行失败了,两者不一致

  • 把 MQ 发送放到本地事务中

    MQ 发送超时失败了,但有可能 MQ 是由于网络延迟,其实是发送到消费者且执行成功了。但本地事务捕获到异常直接回滚了,两者不一致

基本原理

在这里插入图片描述

在这里插入图片描述

拆解下来,主要有以下4个步骤:

  1. 发送半消息:上游向 RocketMQ Broker 发送一条半消息,该消息在 Broker 端的事务消息日志中被标记为 “prepared” 状态
  2. 执行本地事务:RocketMQ 会通知上游执行本地事务。如果本地事务执行成功,发送方通知 RocketMQ Broker 提交该事务消息
  3. 提交事务消息:RocketMQ 收到该提交消息以后,会讲该消息的状态从“prepared” 改为 “committed”,并使该消息可以被消费者消费
  4. 回滚事务消息:如果本地事务执行失败,应用程序通知 RocketMQ Broker 回滚该事务消息,RocketMQ 将该消息状态从 “prepared” 标记为 “rollback”,并将该消息从事务消息日志中删除,从而保证该消息不会被消费者消费。

在正常情况下,一切顺利,那么就是本地事务和发送消息都成功,下游也把消息消费成功。

我们看下非正常情况下的几种表现:

  1. 第一步半消息发送失败

    不用执行本地事务,上游发送者直接会感知到发送失败,可以由上游决定是否需要重试

  2. 本地事务执行失败

    直接发送 rollback 给 MQ broker,下游也收不到消息无需执行

  3. 第二个半消息发送失败
    MQ 会在第 5 步发送回查消息,拿到上游回查的结果,第 7 步类似第 4 步,根据回查结果 commit/rollback 决定半消息是否提交或回滚

  4. 消息接收方消费失败
    依赖 MQ 重投机制不断重试,也需要注意幂等实现

具体实现

首先 MQ 版本得支持事务消息,应该 RocketMQ 4.3.0正式引入了事务消息,上游发送者能感知到就行了。

假设上游业务是订单服务,下游业务是库存服务,我们想实现订单关单、库存回滚的分布式事务。我们重点是需要去实现 org.apache.rocketmq.client.producer.TransactionListener 这个接口,假设在 spring cloud 环境内:

  1. 在配置环境中,标明 producer 是事务的,并且 OrderCloseTransactionListener 实现了 TransactionListener 接口

在这里插入图片描述

  1. 实现 TransactionListener 的核心方法,executeLocalTransaction 是执行本地事务的方法,checkLocalTransaction 是用于 MQ 检查本地事务状态的方法

在这里插入图片描述

虽然上面 OrderCloseTransactionListener 看起来是通过 MQ 消费者的形式去配置的,但其实在发送者发送的时候,half 消息 + OrderCloseTransactionListener + commit/rollback 三步都是同步执行的。所以应该只是 spring cloud 接入写法的问题,这样子方便去拓展。

  1. 实现消费者去监听上述发送方消息

总结思考

这里主要是一些延伸思考,看下 MQ 事务消息的限制,以及它与其他分布式事务解决方案,比方说本地消息表、TCC 的对比和选型建议

使用限制

  1. 消息类型一致性

    事务消息仅支持在 MessageType 为 Transaction 的主题内使用,即事务消息只能发送至类型为事务消息的主题中,发送的消息的类型必须和主题的类型一致。

  2. 消费事务性
    事务消息仅保证上游本地事务和发送消息给下游两者的一致性,并不保证下游消费结果和上游事务的一致性。因此需要下游业务自行保障,有短暂的消费失败,也可以通过消息重试来达到最终处理成功,但需要做好幂等

  3. 中间状态可见性

    MQ 事务消息为最终一致性,即在发送者消息提交到下游消费端处理完成前,下游分支和上游事务之间可能会存在不一致,因此事务消息仅适合接受异步执行短暂不一致的事务场景。需要业务去合理处理中间状态,有些中间态不需要对用户披露,后端不处理也行,有些会对用户表达,那就需要设计一下。

  4. 事务超时机制

    事务消息的生命周期存在超时机制,即半事务消息被生产者发送服务端后,如果在指定时间内服务端无法确认提交或者回滚状态,则消息默认会被回滚。事务超时时间,可以参考 Apache RocketMQ 参数限制

选型思考建议

  1. 和本地消息表一样,都依赖 MQ 消息重投来保障最终一致性,非强一致性

  2. 实现成本上
    MQ 事务消息首先需要 MQ 支持事务消息,需要实现 TransactionListener 接口,业务侵入性不低。
    本地消息表不需要依赖 MQ 版本,对业务侵入性没那么高,但是也需要单独创建一张新表,由定时任务去轮询投递,改造成本也不低。

  3. MQ 事务消息不像 TCC 一样,需要去关心业务回滚逻辑,回滚逻辑一般在长链路里都是蛮复杂的,并且 TCC 需要推动下游去提供接口,相比于让他们直接消费消息,TCC 接口的业务侵入性明显更高。

  4. MQ 事务消息在消息可追踪性上可能不如本地消息表,它不会去额外存储消息。如果有一些消息持久化、对账的需求,可以使用本地消息表的方案,因为其会存储一遍待发送的消息,所以在消息回溯上会更好。

http://www.dtcms.com/a/301217.html

相关文章:

  • 前端面试专栏-前沿技术:32.AI辅助开发工具应用
  • 0-1BFS(双端队列,洛谷P4667 [BalticOI 2011] Switch the Lamp On 电路维修 (Day1)题解)
  • 用 Flask 打造宠物店线上平台:从 0 到 1 的全栈开发实践
  • 电商项目_核心业务_数据归档
  • esp32-使用虚拟机开发-部署esp32的linux的环境
  • 如何关闭浏览器的迅雷图标 - 去掉浏览器左下角的迅雷图标 - chrome - 关闭迅雷检测
  • C/C++核心知识点详解
  • 剑指offer——搜索算法:数字序列中某一位的数字
  • Python操作Excel文件完整指南
  • 深度强化学习走向多智能体:从AlphaGo到智能车队
  • AtCoder Beginner Contest 416(A~D)
  • Leetcode 3629. Minimum Jumps to Reach End via Prime Teleportation
  • 由于主库切换归档路径导致的 Oracle DG 无法同步问题的解决过程
  • Java异常处理
  • 使用Python,OpenCV计算跑图的图像彩色度
  • 移植pbrt中的并行化到ray trace in weeks中
  • JVM内存结构
  • 零基础学习性能测试第五章:JVM性能分析与调优-JVM运行时内存区域介绍
  • 企业级数据架构构建之道
  • Unity FXAA
  • SQL注入SQLi-LABS 靶场less21-25详细通关攻略
  • Ubuntu普通用户环境异常问题
  • Linux->自定义shell
  • Acrobat 文件夹级脚本扩展表单功能
  • 【奔跑吧!Linux 内核(第二版)】第4章:内核编译和调试
  • 物联网安装调试-物联网网关
  • Python数据分析基础(二)
  • 两个函数的卷积
  • Kafka——消费者组消费进度监控都怎么实现?
  • 数字签名与数字证书