RocketMQ事务消息解析!

开发 架构
事务消息是 RocketMQ 提供的一种消息类型,支持在分布式场景下保障消息生产和本地事务的最终一致性。RocketMQ​采用了2PC的思想来实现了提交事务消息,同时增加一个补偿逻辑来处理二阶段超时或者失败的消息。

单体架构下的事务

在单体系统的开发过程中,假如某个场景下需要对数据库的多张表进行操作,为了保证数据的一致性,一般会使用事务,将所有的操作全部提交或者在出错的时候全部回滚。

以创建订单为例,假设下单后需要做两个操作:

在订单表生成订单。

在积分表增加本次订单增加的积分记录。

在单体架构下只需使用@Transactional开启事务,就可以保证数据的一致性。

@Transactional
    public void order() {
        String orderId = UUID.randomUUID().toString();
        // 生成订单
        orderService.createOrder(orderId);
        // 增加积分
        creditService.addCredits(orderId);
    }

但在分布式架构下,订单系统和积分系统可能是两个独立的服务,此时就不能使用上述的方法开启事务了,因为它们不处于同一个事务中。

  • 在出错的情况下,无法进行全部回滚,只能对当前服务的事务进行回滚。

所以就有可能出现订单生成成功但是积分服务增加积分失败的情况(也可能相反),此时数据处于不一致的状态。

分布式架构下的事务

以下单流程为例,在分布式架构下的处理流程如下:

订单服务生成订单。

发送订单生成的MQ消息,积分服务订阅消息,有新的订单生成之后消费消息,增加对应的积分记录。

普通MQ消息存在的问题

假如订单创建成功,MQ消息发送成功,但是order方法在返回的前一刻,服务突然宕机。

由于开启了事务,事务还未提交(方法结束后才会正常提交)。

所以订单表并未生成记录,但是MQ却已经发送成功并且被积分服务消费,此时就会存在订单未创建但是积分记录增加的情况。

假如先发送MQ消息再创建订单,如果MQ消息发送成功,创建订单失败,那么同样处于不一致的状态。

@Transactional
    public void order() {
        String orderId = UUID.randomUUID().toString();
        // 创建订单
        Order order = orderService.createOrder(orderDTO.getOrderId());
        // 发送订单创建的MQ消息
        sendOrderMessge(order);
        return;
    }

可以使用RocketMQ事务消息解决上述问题。

RocketMQ事务消息基础流程

❝Apache RocketMQ在4.3.0版中已经支持分布式事务消息。

事务消息是 RocketMQ 提供的一种消息类型,支持在分布式场景下保障消息生产和本地事务的最终一致性。

RocketMQ采用了2PC的思想来实现了提交事务消息,同时增加一个补偿逻辑来处理二阶段超时或者失败的消息。

基本流程

第一阶段

  • 发送 Message,Half Message,即半事务消息。
  • 此类型的 Message 是不会被 Consumer 消费。

第二阶段:如果半事务消息投递成功,则会开始执行本地事务。

分为如下三种 Case:

  • 本地事务执行成功:
  • 会向 Broker 发送 commit 消息,被 commit 过后的 Message 才能被 Consumer 消费到。
  • 本地事务执行失败:
  • 会向 Broker 发送 rollback 消息,Broker 则会将刚刚投递的半事务消息删除,从而保证上下游数据的一致性。

  • 如果 Producer 实例或者网络出现了问题,Producer 没能及时地将本地事务执行的结果通知 Broker。

  • Broker 会通过扫描发现某条 Message 长时间处于半事务消息状态。

  • Broker 会主动地向 Producer 询问此 Message 对应的事务状态。

值得注意的是:

RocketMQ 并不会无休止的的信息事务状态回查,默认回查 15 次。

如果 15 次回查还是无法得知事务状态,RocketMQ 默认回滚该消息。

RocketMQ事务消息使用限制

事务消息不支持延时消息和批量消息。

事务性消息可能不止一次被检查或消费,所以消费者端需要做好消费幂等。

事务消息的生产者 ID 不能与其他类型消息的生产者 ID 共享。

  • 与其他类型的消息不同,事务消息允许反向查询、MQ服务器能通过它们的生产者 ID 查询到消费者。

RocketMQ事务消息基本原理

采用2PC两阶段设计。

将 Message 原本真实的 Topic 和 MessageQueue 进行备份。

  • 放入到PROPERTY_REAL_TOPIC、PROPERTY_REAL_QUEUE_ID中保存。

将消息投递到一个内部Topic中RMQ_SYS_TRANS_HALF_TOPIC,该队列专门存储事务消息。

所有的 Half Message 全部都写入到 queueId 为 0 的 MessageQueue。

因为一个 Topic 下只有 1 个 MessageQueue:

  • 这个 Topic 下的所有 Message 就是全局有序的,它们会按照先来后到的顺序被消费。

如果本地事务执行成功进行Commit,则将RMQ_SYS_TRANS_HALF_TOPIC 队列中的消息投递到真实的Topic中,供后续流程执行。

  • 并删除这条 Half Message ,但删除也是假删除,只是给 Message 打上一个删除的 Tag。

如果本地事务执行失败进行rollback,则直接删除这条 Half Message ,但删除也是假删除。

如果本地事务迟迟没有返回结果 (默认时间是6s),则会触发事务回查机制

  • 执行回查之前需要校验检查次数是否到达了最大值(需要手动设置,没有默认值)。
  • 或者是当前 Half Message 存在是否超过了 Message 保存的上限,即 3天。
  • 如果满足上面条件中的一种Half Message 会被放进 TRANS_CHECK_MAX_TIME_TOPIC Topic 当中。
  • 一旦判定为需要执行事务回查逻辑,那么当前这条 Half Message 就算已经被消费了。
  • 在没达到最大的校验次数之前,都还需要将其投递到事务队列当中,以便下次重试时再次执行 Check 逻辑。
  • 如果回查成功则删除投递的 Half Message。

源码解读

发送事务消息调用的是TransactionMQProducer的sendMessageInTransaction方法:

主要有以下几个步骤:

获取事务监听器TransactionListener,如果获取为空或者本地事务执行器LocalTransactionExecuter为空将抛出异常。

因为需要通过TransactionListener或者LocalTransactionExecuter来执行本地事务,所以不能为空。

在消息中设置prepared属性,此时与普通消息(非事务消息)相比多了PROPERTY_TRANSACTION_PREPARED属性。

调用send方法发送prepared消息也就是half消息,发送消息的流程与普通消息一致。

根据消息的发送结果判断:

  • 如果发送成功执行本地事务,并返回本地事务执行结果状态,如果返回的执行状态结果为空,将本地事务状态设置为UNKNOW。
  • 发送成功之外的其他情况,包括FLUSH_DISK_TIMEOUT刷盘超时、FLUSH_SLAVE_TIMEOUT和SLAVE_NOT_AVAILABLE从节点不可用三种情况。
  • 此时意味着half消息发送失败,本地事务状态置为ROLLBACK_MESSAGE回滚消息。

调用endTransaction方法结束事务。

参考

《RocketMQ技术内幕》

https://github.com/apache/rocketmq/blob/master/docs/cn/RocketMQ_Example.md。

https://github.com/apache/rocketmq/blob/master/docs/cn/design.md。

责任编辑:姜华 来源: 月伴飞鱼
相关推荐

2023-07-17 08:34:03

RocketMQ消息初体验

2024-11-11 13:28:11

RocketMQ消息类型FIFO

2021-04-15 09:17:01

SpringBootRocketMQ

2021-10-03 21:41:13

RocketMQKafkaPulsar

2024-08-22 18:49:23

2024-09-25 08:32:05

2024-02-04 09:02:29

RocketMQ项目处理器

2023-09-04 08:00:53

提交事务消息

2024-10-11 09:15:33

2022-07-04 11:06:02

RocketMQ事务消息实现

2024-06-13 09:25:14

2024-10-22 08:01:15

2021-03-04 06:49:53

RocketMQ事务

2022-12-22 10:03:18

消息集成

2023-12-21 08:01:41

RocketMQ消息堆积

2022-03-31 08:26:44

RocketMQ消息排查

2023-07-18 09:03:01

RocketMQ场景消息

2022-06-02 08:21:07

RocketMQ消息中间件

2020-11-13 16:40:05

RocketMQ延迟消息架构

2021-07-14 17:18:14

RocketMQ消息分布式
点赞
收藏

51CTO技术栈公众号