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

前言

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...

相关推荐
李慕婉学姐13 小时前
【开题答辩过程】以《基于JAVA的校园即时配送系统的设计与实现》为例,不知道这个选题怎么做的,不知道这个选题怎么开题答辩的可以进来看看
java·开发语言·数据库
web小白成长日记13 小时前
企业级 Vue3 + Element Plus 主题定制架构:从“能用”到“好用”的进阶之路
前端·架构
奋进的芋圆15 小时前
Java 延时任务实现方案详解(适用于 Spring Boot 3)
java·spring boot·redis·rabbitmq
sxlishaobin15 小时前
设计模式之桥接模式
java·设计模式·桥接模式
model200515 小时前
alibaba linux3 系统盘网站迁移数据盘
java·服务器·前端
荒诞硬汉16 小时前
JavaBean相关补充
java·开发语言
提笔忘字的帝国16 小时前
【教程】macOS 如何完全卸载 Java 开发环境
java·开发语言·macos
2501_9418824816 小时前
从灰度发布到流量切分的互联网工程语法控制与多语言实现实践思路随笔分享
java·开发语言
jayaccc16 小时前
微前端架构实战全解析
前端·架构