目录

事务消息用在用么场景?如何使用

前言

MQ 相信程序员们都用过?但是MQ中的事务消息,估计用过的人不多。MQ的事务消息 在实现分布式事务中 可靠消息最终一致性这个方案中用得还是很多的🚀

还有就是,面试经常问到的消息丢失问题,使用事务消息当然也能解决这个问题咯👍

事务消息

了解事务消息,就得先了解,其常使用的场景。下面看看分布式事务基于MQ实现的两种方式吧。

分布式事务基于MQ的实现方式

在分布式事务场景中,实现的方案也比较多,比较常用的就是基于MQ来实现。在MQ的实现的方案中,常用两个案有以下两种

  • 最大努力通知:
    他不需要保证消费者一定能接收到,只是尽自己最大的努力去通知就行了。最多就是在发消息的地方加一个重试的机制,这个方案可能会导致消息的重复和丢失 ,不适合用在一些一致性要求较高的场景,用来发通知等还可以。涉及金额的谨慎使用这种方式了。
  • 可靠消息最终一致性:
    依赖可靠的消息 ,来实现一种最终一致性的模型。 普通的消息发送,可能会因为网络原因,超时或者失败,或者成功但是没有收到响应。 (事务消息可以保证MQ一定可以发送成功,或者是如果失败了,也有机制能够重试让他成功,这就是实现了消息的可靠性。)

实现可靠消息 除了 事务消息 这种方式,还有一种就是本地消息,在提交业务事务中,将消息也进行写入到数据库,利用定时任务去扫描 数据库中 消息的状态,进行消息的重试或者消息状态的更正。

所以事务消息 用来保证消息的可靠性的一种方式,从而实现可靠消息最终一致性的分布式事务。

可靠消息最终一致性

利用 事务消息 和 本地事务,实现事务的最终一致性。实现的主要步骤如下

1、客户端A执行本地事务

2、客户端A向客户端B发送MQ消息

3、客户端B收到MQ消息后执行自己的本地事务

RocketMQ实现事务消息

实现原理

RocketMQ 的事务消息实现基于两阶段提交思想,主要分为三个阶段:

  1. 发送半消息:生产者先向消息队列发送半消息(Half Message),半消息对于消费者是不可见的,只有在后续被确认提交后才会对消费者可见。

  2. 执行本地事务:发送半消息成功后,生产者执行本地事务,根据本地事务的执行结果决定是提交还是回滚半消息。

  3. 消息状态确认

    • 如果本地事务执行成功,生产者向消息队列发送提交请求,将半消息变为可消费消息。
    • 如果本地事务执行失败,生产者向消息队列发送回滚请求,消息队列会删除该半消息。
    • 如果生产者没有及时发送提交或回滚请求,消息队列会主动回查生产者,询问该半消息对应的本地事务状态。

代码示例

1.引入依赖

xml 复制代码
<dependency>
    <groupId>org.apache.rocketmq</groupId>
    <artifactId>rocketmq-client</artifactId>
    <version>4.9.2</version>
</dependency>
  1. 创建生产者
java 复制代码
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.client.producer.LocalTransactionState;
import org.apache.rocketmq.client.producer.TransactionListener;
import org.apache.rocketmq.client.producer.TransactionMQProducer;
import org.apache.rocketmq.common.message.Message;
import org.apache.rocketmq.common.message.MessageExt;

import java.util.concurrent.*;

public class TransactionalProducer {
    public static void main(String[] args) throws MQClientException, InterruptedException {
        // 创建事务消息生产者
        TransactionMQProducer producer = new TransactionMQProducer("transaction_producer_group");
        // 设置 NameServer 地址
        producer.setNamesrvAddr("localhost:9876");

        // 创建线程池用于执行本地事务
        ExecutorService executorService = new ThreadPoolExecutor(2, 5, 100, TimeUnit.SECONDS, new ArrayBlockingQueue<>(2000), r -> {
            Thread thread = new Thread(r);
            thread.setName("client-transaction-msg-check-thread");
            return thread;
        });

        // 设置线程池
        producer.setExecutorService(executorService);

        // 设置事务监听器
        producer.setTransactionListener(new TransactionListener() {
            @Override
            public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
                // 执行本地事务
                try {
                    System.out.println("Executing local transaction...");
                    // 模拟本地事务执行
                    Thread.sleep(1000);
                    // 假设本地事务执行成功
                    return LocalTransactionState.COMMIT_MESSAGE;
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    return LocalTransactionState.ROLLBACK_MESSAGE;
                }
            }

            @Override
            public LocalTransactionState checkLocalTransaction(MessageExt msg) {
                // 消息队列回查本地事务状态
                System.out.println("Checking local transaction state...");
                // 假设本地事务执行成功
                return LocalTransactionState.COMMIT_MESSAGE;
            }
        });

        // 启动生产者
        producer.start();

        // 发送半消息
        try {
            Message msg = new Message("TransactionTopic", "TransactionTag", "Hello, RocketMQ Transaction Message".getBytes());
            producer.sendMessageInTransaction(msg, null);
        } catch (MQClientException e) {
            e.printStackTrace();
        }

        // 等待一段时间后关闭生产者
        Thread.sleep(5000);
        producer.shutdown();
    }
}    

3.创建消费者

java 复制代码
import org.apache.rocketmq.client.consumer.DefaultMQPushConsumer;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyContext;
import org.apache.rocketmq.client.consumer.listener.ConsumeConcurrentlyStatus;
import org.apache.rocketmq.client.consumer.listener.MessageListenerConcurrently;
import org.apache.rocketmq.client.exception.MQClientException;
import org.apache.rocketmq.common.message.MessageExt;

import java.util.List;

public class TransactionalConsumer {
    public static void main(String[] args) throws InterruptedException, MQClientException {
        // 创建消费者
        DefaultMQPushConsumer consumer = new DefaultMQPushConsumer("transaction_consumer_group");
        // 设置 NameServer 地址
        consumer.setNamesrvAddr("localhost:9876");
        // 订阅主题
        consumer.subscribe("TransactionTopic", "*");

        // 设置消息监听器
        consumer.registerMessageListener((MessageListenerConcurrently) (msgs, context) -> {
            for (MessageExt msg : msgs) {
                System.out.printf("%s Receive New Messages: %s %n", Thread.currentThread().getName(), new String(msg.getBody()));
            }
            return ConsumeConcurrentlyStatus.CONSUME_SUCCESS;
        });

        // 启动消费者
        consumer.start();
        System.out.printf("Consumer Started.%n");
    }
}    

总结

本篇文章,介绍了什么是事务消息,以及事务消息的运用场景和使用案例。如果觉得有用欢迎一键三连👍👍👍

RocketMQ 官网也对 事务消息 有详细的说明,并且还有 java 案例,非常值得阅读的 :rocketmq.apache.org/zh/docs/fea...

本文是转载文章,点击查看原文
如有侵权,请联系 xyy@jishuzhan.net 删除
相关推荐
写bug写bug13 分钟前
Java并发编程:什么是线程组?它有什么作用?
java·后端
Andya_net19 分钟前
SpringBoot | 构建客户树及其关联关系的设计思路和实践Demo
java·spring boot·后端
申城异乡人21 分钟前
【踩坑系列】使用Comparator.comparing对中文字符串排序结果不对
java
Brian_Lucky23 分钟前
在 macOS 上合并 IntelliJ IDEA 的项目窗口
java·macos·intellij-idea
周杰伦_Jay26 分钟前
continue插件实现IDEA接入本地离线部署的deepseek等大模型
java·数据结构·ide·人工智能·算法·数据挖掘·intellij-idea
江沉晚呤时28 分钟前
深入了解递归、堆与栈:C#中的内存管理与函数调用
java·jvm·算法
天草二十六_简村人32 分钟前
Macbook IntelliJ IDEA终端无法运行mvn命令
java·jdk·maven·intellij-idea
掘金-我是哪吒36 分钟前
分布式微服务系统架构第120集:专业WebSocket鉴权
分布式·websocket·微服务·云原生·架构
等什么君!41 分钟前
SpringMVC处理请求映射路径和接收参数
java·开发语言·spring