SpringBoot使用RocketMQ发送事务消息

一、事务消息与普通消息区别

1. 事务消息
事务消息是一种支持事务性操作的消息,主要用于满足分布式事务的一致性需求。事务消息的发送者可以将一系列的操作包装在一个事务中,如果整个事务的操作都成功执行,则消息被发送到消息队列,如果有任何一个操作失败,则消息被丢弃。事务消息的消费者可以通过回查机制来保证消息的最终一致性,即通过询问消息发送者的本地事务状态来判断是否需要提交或回滚。

2. 普通消息:
普通消息是一种简单的消息模式,发送者发送消息到消息队列后,消费者可以立即消费这些消息。普通消息没有事务性的特点,发送者和消费者之间没有强一致性的要求。

因此,事务消息和普通消息的主要区别在于:
- 事务消息支持事务性操作,发送者可以将一系列的操作包装在一个事务中,并通过回查机制来保证消息的最终一致性。
- 普通消息没有事务性的特点,发送者和消费者之间没有强一致性的要求,消息发送后可以立即被消费。

二、消息流程

RocketMQ事务消息的处理步骤可以分为以下几个阶段:

1、系统业务方发送半事务消息
系统业务方在执行本地事务之前,首先会向消息队列发送一条半事务消息(表示事务未完成)。

2、Broker存储半事务消息
Broker收到半事务消息后,将其存储,并向业务方返回发送结果。

3、系统业务方执行本地事务
业务方根据实际业务逻辑执行本地事务,如订单创建、库存扣减等。

4、业务方提交事务状态
业务方在本地事务处理完毕后,会发送事务状态(提交或回滚)的确认消息给Broker。

5、Broker回查本地事务
如果Broker在设定的时间内(默认60秒)未收到事务确认消息,它会向业务方发送回查请求,询问本地事务的状态。

6、业务方处理回查请求
业务方根据本地事务的实际执行结果,响应Broker的回查请求。

7、Broker处理回查结果
根据业务方返回的回查结果,Broker决定是否提交或回滚该事务消息,并将处理后的消息更新到实际的消息队列中,供消费者消费。

RocketMQ通过两阶段提交(2PC)+ 事后补偿机制,确保消息的一致性,避免因网络或系统故障导致的消息丢失或重复消费问题。

三、事务消息的内部机制

RocketMQ在处理事务消息时,涉及到以下特殊的Topic:

RMQ_SYS_TRANS_HALF_TOPIC:存储半事务消息的Topic。
RMQ_SYS_TRANS_OP_HALF_TOPIC:已处理的半事务消息Topic,记录半事务消息的状态(如消息位移),便于后续处理。

事务消息的具体流程:
1、发送半事务消息
系统业务方使用TransactionMQProducer发送事务消息至TopicA。在发送时,消息的属性中会添加TRAN_MSG=true的标记,表明这是事务消息。

2、Broker存储半事务消息
Broker收到事务消息后,检查TRAN_MSG属性,如果为true,则将消息的原始Topic(TopicA)保存在消息属性REAL_TOPIC中,并将消息的Topic替换为RMQ_SYS_TRANS_HALF_TOPIC,存储消息并返回结果。

3、业务方处理本地事务
业务方收到消息发送结果后,执行本地事务操作,如完成订单创建。

4、发送事务结果
业务方根据本地事务的执行情况,发送END_TRANSACTION消息给Broker,通知事务提交回滚

5、Broker提交或回滚消息
Broker根据收到的END_TRANSACTION消息判断事务结果:

如果是COMMIT,则恢复消息的原始Topic(TopicA),并将消息发送到实际的队列中,供消费者消费。
如果是ROLLBACK,则丢弃消息,不做进一步处理。
6、保存处理结果
Broker在处理完成后,将半事务消息状态存储到RMQ_SYS_TRANS_OP_HALF_TOPIC中,标记该消息已处理。

7、事务回查机制
Broker在启动时会启动一个定时回查服务,定期检查未完成的半事务消息。如果发现有超时未处理的半事务消息(UNKNOWN状态),则发起回查请求(CHECK_TRANSACTION_STATE)给业务方,询问本地事务的执行结果。

8、处理回查请求
业务方收到回查请求后,查询本地事务状态并回复Broker,Broker根据返回结果进行消息的提交或回滚操作。

四、代码示例

SpringBoot版本

<parent>
   <groupId>org.springframework.boot</groupId>
   <artifactId>spring-boot-starter-parent</artifactId>
   <version>2.7.18</version>
</parent>

RocketMq版本

<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-spring-boot-starter</artifactId>
    <version>2.2.3</version>
</dependency>

自定义RocketMqTemplate

/**
 * 自定义的RocketMQTemplat 发送事务消息
 */
@ExtRocketMQTemplateConfiguration(group = "${rocketmq.group}")
@Component
public class MyDiyMQTemplate extends RocketMQTemplate {

}

发送消息

    @Autowired
    private MyDiyMQTemplate myDiyMQTemplate;

    public void sendMessage(){
        JSONObject message= new JSONObject()
        //发送消息
        myDiyMQTemplate.sendMessageInTransaction(
                "Topic:Tag",
                MessageBuilder.withPayload(message).build(),
                ""
        );
    }

配置事务消息监听器

package com.chat.app.stream.listener;

import cn.hutool.core.date.DateUtil;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionListener;
import org.apache.rocketmq.spring.core.RocketMQLocalTransactionState;
import org.apache.rocketmq.spring.support.RocketMQUtil;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.Message;
import org.springframework.messaging.converter.StringMessageConverter;
import org.springframework.transaction.annotation.Transactional;

import java.nio.charset.StandardCharsets;

/**
 * 一个事物监听器对应一个事物流程,采取对应的rocketMQTemplate 发送事务消息
 */
@RocketMQTransactionListener(rocketMQTemplateBeanName = "myDiyMQTemplate")
@Slf4j
public class PurchaseDressUpTransactionListener implements RocketMQLocalTransactionListener {


    /**
     * 执行本地事务
     *
     * @param msg
     * @param arg
     * @return
     */
    @Override
    @Transactional
    public RocketMQLocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        try {
            //消息转换
            org.apache.rocketmq.common.message.Message rocketMsg = RocketMQUtil.convertToRocketMessage(new StringMessageConverter(), "UTF-8", TopicConstants.TAG_PAY_EVENT_PURCHASE_DRESS, msg);
            JsonObject message= JSONObject.parseObject(new String(rocketMsg.getBody(), StandardCharsets.UTF_8), JsonObject .class);
            log.info("执行本地事务--》:{}", message);
            .......
            return RocketMQLocalTransactionState.COMMIT;
        } catch (Exception e) {
            e.printStackTrace();
            return RocketMQLocalTransactionState.ROLLBACK;
        }
    }

    /**
     * 检查本地事务状态
     *
     * @param msg
     * @return
     */
    @Override
    public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
        try {
            //消息转换
            org.apache.rocketmq.common.message.Message rocketMsg = RocketMQUtil.convertToRocketMessage(new StringMessageConverter(), "UTF-8", TopicConstants.TAG_PAY_EVENT_PURCHASE_DRESS, msg);
            JsonObject message= JSONObject.parseObject(new String(rocketMsg.getBody(), StandardCharsets.UTF_8), JsonObject .class);
            log.info("检查本地事务状态--》:{}", message);
            ........
            return RocketMQLocalTransactionState.COMMIT;
        } catch (Exception e) {
            e.printStackTrace();
            return RocketMQLocalTransactionState.ROLLBACK;
        }
    }
}

接受事务消息

package com.chat.app.stream.consumer;

import cn.hutool.core.date.DateUtil;
import cn.hutool.core.date.LocalDateTimeUtil;
import com.alibaba.fastjson2.JSONObject;
import lombok.extern.slf4j.Slf4j;
import org.apache.rocketmq.spring.annotation.RocketMQMessageListener;
import org.apache.rocketmq.spring.core.RocketMQListener;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

@RocketMQMessageListener(topic ="topic",
        consumerGroup = "group",
        enableMsgTrace = true,
        selectorExpression = "tag")
@Slf4j
@Component
public class MyTransactionMessageConsumer implements RocketMQListener<String> {

   

    @Override
    public void onMessage(String msg) {
        log.info("收到事务消息:{}", msg);
        JSONObject message = JSONObject.parseObject(msg);
       
    }

    

}

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

W先生'

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

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

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

打赏作者

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

抵扣说明:

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

余额充值