热点
容器化与智能编排:系统无碍新范式,reasoning_content:我们要求以全栈工程师的口吻,写一个与“技术、科技”相关,关于“容器化部署与智能编排:构建系统无碍新范式”的标题直接输出一个标题,不要加说明,简短精炼,30字以内nn全栈工程师口吻:可能偏向于实战、工程化、技术深度,也可以有“我”、“我们”视角,但标题通常不需要人称也可以结合“容器化”、“智能编排”、“无碍”(无障碍?或无缝?可能是“无碍”意为无障碍、无故障?)原题“构建系统无碍新范式”可能意思是构建系统无故障/无缝的新范式所以标题要体现容器化部署+智能编排,并突出新范式nn构思:例如“容器化+智能编排:系统无碍新范式” 但字数较多或者“容器化部署与智能编排:构建无碍系统新范式” 但超过30字?数一下:容器化部署与智能编排:构建无碍系统新范式 – 共18字?中文字符:容器化部署与智能编排:构建无碍系统新范式 -> 容器化部署(4) 与(1) 智能编排(4) :(1) 构建(2) 无碍(2) 系统(2) 新范式(3) 总共4+1+4+1+2+2+2+3=19字,在30以内但原题要求“简短精炼”,可以更精炼例如“容器+智能编排:系统无碍新范式” 13字或者“容器化部署+智能编排:无碍系统新范式” 15字nn全栈工程师口吻:可能喜欢用“实战”、“指南”、“之道”等,但标题直接给出即可我选择“容器化部署+智能编排:系统无碍新范式” 共15字,看是否更精炼?或者去掉“部署”:“容器化+智能编排:系统无碍新范式” 13字但“容器化”本身包含部署概念?可能可以或者“容器化与智能编排:系统无碍新范式” 14字最终决定使用“容器化与智能编排:系统无碍新范式”注意不要加引号,直接输出
UI测试工程师的Windows运行库配置与开发环境全攻略,reasoning_content:我们要求以UI测试工程师的口吻写一个与技术、科技相关,关于[Windows运行库配置与管理:轻松搭建开发环境全攻略]的标题需要直接输出标题,不要加说明字数30字以内,简短精炼nn思路:UI测试工程师口吻,可能注重稳定性、兼容性、环境配置等可以结合运行库、配置、开发环境、攻略等词例如:UI测试工程师必看:Windows运行库配置一步到位 但需要更精简或者Windows运行库配置:UI测试环境搭建指南注意不要超过30字nn尝试:UI测试工程师:Windows运行库配置与开发环境搭建全攻略 字数?数一下:UI测试工程师:Windows运行库配置与开发环境搭建全攻略 – 共22个汉字+标点?实际汉字:UI(2字母算2个? 通常中文字数按汉字算,但UI是英文字母,题目说字数控制在30字以内,一般指汉字或字符?安全起见,用中文字符可以写成UI测试工程师即4个汉字+2个字母?严格来说,可以把UI视为两个字符为了简洁,可以写UI测试工程师共5个字符(U、I、测、试、工、程、师?不对,U,I,测,试,工,程,师 7个字符但通常算字数时英文字母算半个或一个?稳妥起见,用全中文表达,比如界面测试工程师但原要求是UI测试工程师,最好保留UInn可以写:【UI测试工程师】Windows运行库配置与环境搭建攻略 – 数一下:【UI测试工程师】Windows运行库配置与环境搭建攻略 – 汉字+符号:共? 【(1)、U(1)、I(1)、(1)、测(1)、试(1)、工(1)、程(1)、师(1)、】(1)、W(1)、i(1)、n(1)、d(1)、o(1)、w(1)、s(1)、运(1)、行(1)、库(1)、配(1)、置(1)、与(1)、环(1)、境(1)、搭(1)、建(1)、攻(1)、略(1) – 共29个字符?还有空格?实际有29个字符,但包含英文字母和符号可能超30?再算:符号算一个,英文字母每个算一个,总共:括号两个, UI两个, 汉字:测试工程师(4) Windows(7) 运行库配置与环境搭建攻略(13) 合计2+2+4+7+13=28加上中间可能没有空格28个,可以但【】可能不必要更简洁:nnUI测试工程师:Windows运行库配置与开发环境搭建 字数:UI测试工程师(7字符) :(1) Windows(7) 运行库配置与开发环境搭建(13) 合计7+1+7+13=28,可以但缺少全攻略意味或者轻松搭建nn另一个:Windows运行库配置:UI测试工程师开发环境搭建指南 字数:Windows运行库配置(11) :(1) UI测试工程师(7) 开发环境搭建指南(9) 合计11+1+7+9=28也不错nn最终选择简洁且有全攻略的考虑:UI测试工程师的Windows运行库配置与开发环境全攻略 字数:UI测试工程师的(8) Windows运行库配置与开发环境全攻略(16) 合计24字OKnn注意:要直接输出,不要加说明所以输出这个
16 9 月 2026, 周三

  目录
一、RocketMQ的分布式事务结构和说明二、搭建RocketMQ三、事务场景,然后准备工程,运行代码
  随着互联网公司的微服务越来越多,分布式事务已经成为了我们的经常使用的。所以我们来一步一步的实现基于RocketMQ的分布式事务。接下来,我们将要做的主题写出来。
 
RocketMQ的分布式事务结构和说明 搭建RocketMQ步骤 事务场景,然后准备工程,运行代码
一、RocketMQ的分布式事务结构和说明
  我们通过下图来了解一下RocketMQ实现分布式事务的结构。采用半消息机制实现分布式事务,半消息顾名思义,就是发送方将消息发送到MQ中的Broker端,这个消息被标记为“暂不投递”状态,这个时间订阅方是不能收到这个消息的,当发送方将提交了Commit后,这个消息才可以被订阅方收到。如果发送方提交了Rollback,那么订阅方就不会收到以该消息。
 
  RocketMQ采用的是最终一致性事务,因为它使用了半消息机制,订阅方可能收不到消息,但是最终状态是能保持一致的。
 
  我们在讲这个流程前,我们需要先看下图的红色部分,当发送方发给MQ的Commit/Rollback没有收到的时候,这个时候需要启用一个线程,从本地事务库里边查找到当前的状态,根据当前的状态来决定是否重新发送Commit/Rollback,相当于重试。
 
 
 
  接下来,我们说一下整个执行的过程。
 
发送方将半消息发送到MQ中,成功后MQ将消息保持好后将结果同步给发送方,即半消息发送成功。当MQ告诉发送方成功后,我们需要记录下发送到MQ的相关的事情,比如发送的消息内容,发送时间,发送的相关ID,比如事务ID,都要做好相应记录。当本地事务库,也可以理解为日志库,我们可以针对发生的问题进行追溯。本地库记录好事务后,发送方就可以将Commit/Rollback提交到MQ了。当MQ收到了Commit命令后,这个时候就会将该条消息发送给订阅方。收到了Rollback后,那该条消息不会投递给订阅方,消息保存3天后删除。
 
  整个过程就是这些,大家如果觉得有遗漏的,可以告诉我。
 
二、搭建RocketMQ
  我使用的是windos10,所以需要下载windows版本。
 
   进入到RocketMQ的官方地址:http://rocketmq.apache.org/release_notes/release-notes-4.3.0/
 
   下载二进制文件压缩包。如下图:
 
 
 
  先解压,然后进行环境变量配置,打开菜单,直接输入环境变量。
 
    变量名:ROCKETMQ_HOME
 
    值:解压的release的路径,如D:\rocketmq-all-4.3.0-bin-release
 
  然后打开CMD命令,进入到解压路径中的bin目标,进行nameserver启动。
 
start mqnameserv.cmd
  启动后的效果为:
 
 
 
  再进行broker的启动,启动需要连到的nameserver为127.0.0.1:9876,打开自动创建Topic,这个时候当用到某个Topic没有的情况下会自动创建一个。
 
start mqbroker.cmd -n 127.0.0.1:9876 autoCreateTopicEnable=true
 
 
  这个时候Windows的rocketmq就下载好并且启动成功了。
 
三、事务场景,然后准备工程,运行代码
  因为我们使用的RocketMq是使用的分布式事务是最终一致性的,柔性的,所以我们使用的场景也要考虑到。我们用的场景就是下订单充话费,用户支付了订单,那么直接给用户的进行充值。购买的订单不会立马充值到手机的,需要等一会才到账,这就是最终一致性。
 
  我们使用的如下代码版本号,SpringCloud的版本号要和SpringBoot保持一致,否则会出现类找不到的情况。
 
SpringCloud(Finchley.RELEASE) + SpringBoot2.0.4.RELEASE + RocketMQ4.3 +MySQL + lombok(插件)
 
  我们使用SpringCloud的几个组件:
 
  Euerka Server :用于提供服务注册的能力和发现
 
  Euerka Client : 用于进行服务注册
 
  Feign:用于服务间的调用
 
  Config :用于进行配置
 
  接下来,我们创建需要进行配置的表。
 
DROP TABLE IF EXISTS `spring_cloud_config`;
 
CREATE TABLE `spring_cloud_config` (
  `id` int(11) unsigned NOT NULL AUTO_INCREMENT,
  `akey` varchar(30) DEFAULT NULL,
  `avalue` varchar(128) DEFAULT NULL,
  `application` varchar(30) DEFAULT NULL,
  `aprofile` varchar(30) DEFAULT NULL,
  `label` varchar(30) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
 
 
 
INSERT INTO `spring_cloud_config` (`id`, `akey`, `avalue`, `application`, `aprofile`, `label`)
VALUES
    (2,'name_server','127.0.0.1:9876','product-service','dev','dev'),
    (3,'name_server','127.0.0.1:9876','order-service','dev','dev'),
    (4,'order_topic','order_topic','order-service','dev','dev'),
    (5,'order_topic','order_topic','product-service','dev','dev');
  我们构建了4个模块,eureka模块用于服务注册和发现,springcloud-config将数据库创建的配置加载供其他工程使用。charge-order-service用于下充值订单,phone-charge-service用于给手机进行充值业务。
 
 
 
  主要的流程为,通过charge-order-service产生了订单,然后将订单和数量到phone-charge-service进行二次确认看是否还有足够的数量可以使用,如果数量不够的话直接将事务进行rollback,这样消息就不会到消费端。如果下过去的订单号已经充值过了,那么该消息将会被直接丢掉,这也是消息端的幂等设计。
 
  接下来,我们看一下生产者以及事务的核心代码。主要用于连接RocketMQ, 执行本地事务,在本地事务中进行二次确认,根据结果进行Commit、Rollback。当连接RocketMQ出现问题的时候可能会收到UNKNOWN,这个时候会调用checkLocalTransaction()方法,用于检查是否将消息发送给RocketMQ了。
 
package com.hqs.chargeorderservice.mqservice;
 
import com.alibaba.fastjson.JSONObject;
import com.hqs.chargeorderservice.config.Jms;
import com.hqs.chargeorderservice.service.ProduceOrderService;
import lombok.extern.slf4j.Slf4j;
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 org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
 
import java.util.concurrent.*;
 
 
/**
 *
 * @Description: 分布式事务RocketMQ 生产者
 */
@Slf4j
@Component
public class TransactionProducer {
 
    /**
     * 需要自定义事务监听器 用于 事务的二次确认 和 事务回查
     */
    private TransactionListener transactionListener ;
 
    /**
     * 这里的生产者和之前的不一样
     */
    private TransactionMQProducer producer = null;
 
    /**
     * 官方建议自定义线程 给线程取自定义名称 发现问题更好排查
     */
    private ExecutorService executorService = new ThreadPoolExecutor(2, 5, 100, TimeUnit.SECONDS,
            new ArrayBlockingQueue<Runnable>(2000), new ThreadFactory() {
        @Override
        public Thread newThread(Runnable r) {
            Thread thread = new Thread(r);
            thread.setName("client-transaction-msg-check-thread");
            return thread;
        }
 
    });
 
    public TransactionProducer(@Autowired Jms jms, @Autowired ProduceOrderService produceOrderService) {
        transactionListener = new TransactionListenerImpl(produceOrderService);
        // 初始化 事务生产者
        producer = new TransactionMQProducer(jms.getOrderTopic());
        // 添加服务器地址
        producer.setNamesrvAddr(jms.getNameServer());
        // 添加事务监听器
        producer.setTransactionListener(transactionListener);
        // 添加自定义线程池
        producer.setExecutorService(executorService);
 
        start();
    }
 
    public TransactionMQProducer getProducer() {
        return this.producer;
    }
 
    /**
     * 对象在使用之前必须要调用一次,只能初始化一次
     */
    public void start() {
        try {
            this.producer.start();
        } catch (MQClientException e) {
            e.printStackTrace();
        }
    }
 
    /**
     * 一般在应用上下文,使用上下文监听器,进行关闭
     */
    public void shutdown() {
        this.producer.shutdown();
    }
}
 
/**
 * @Description: 自定义事务监听器
 */
@Slf4j
class TransactionListenerImpl implements TransactionListener {
 
    @Autowired
    private ProduceOrderService produceOrderService ;
 
    public TransactionListenerImpl( ProduceOrderService produceOrderService) {
        this.produceOrderService = produceOrderService;
    }
 
    @Override
    public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        log.info("=========本地事务开始执行=============");
        String message = new String(msg.getBody());
        JSONObject jsonObject = JSONObject.parseObject(message);
        Integer productId = jsonObject.getInteger("productId");
        Integer total = jsonObject.getInteger("total");
        Integer tradeNo = jsonObject.getInteger("tradeNo");
        int userId = Integer.parseInt(arg.toString());
        //模拟执行本地事务begin=======
        /**
         * 本地事务执行会有三种可能
         * 1、commit 成功
         * 2、Rollback 失败
         * 3、网络等原因服务宕机收不到返回结果
         */
        log.info("本地事务执行参数,用户id={},商品ID={},销售库存={}",userId,productId,total);
        int result = produceOrderService.save(userId, productId, total, tradeNo);
        //模拟执行本地事务end========
        //TODO 实际开发下面不需要我们手动返回,而是根据本地事务执行结果自动返回
        //1、二次确认消息,然后消费者可以消费
        if (result == 0) {
            return LocalTransactionState.COMMIT_MESSAGE;
        }
        //2、回滚消息,Broker端会删除半消息
        if (result == 1) {
            return LocalTransactionState.ROLLBACK_MESSAGE;
        }
        //3、Broker端会进行回查消息
        if (result == 2) {
            return LocalTransactionState.UNKNOW;
        }
        return LocalTransactionState.COMMIT_MESSAGE;
    }
 
    /**
     * 只有上面接口返回 LocalTransactionState.UNKNOW 才会调用查接口被调用
     *
     * @param msg 消息
     * @return
     */
    @Override
    public LocalTransactionState checkLocalTransaction(MessageExt msg) {
        log.info("==========回查接口=========");
        String key = msg.getKeys();
        //TODO 1、必须根据key先去检查本地事务消息是否完成。
        /**
         * 因为有种情况就是:上面本地事务执行成功了,但是return LocalTransactionState.COMMIT_MESSAG的时候
         * 服务挂了,那么最终 Brock还未收到消息的二次确定,还是个半消息 ,所以当重新启动的时候还是回调这个回调接口。
         * 如果不先查询上面本地事务的执行情况 直接在执行本地事务,那么就相当于成功执行了两次本地事务了。
         */
        // TODO 2、这里返回要么commit 要么rollback。没有必要在返回 UNKNOW
        return LocalTransactionState.COMMIT_MESSAGE;
    }
}
  我们启动一下程序。我们登录localhost:7001注册中心,看到3个服务都成功注册了。
 
 
  然后我们进行下订单调用接口,userId用户编码,productId产品号,total购买量,tradeNo交易订单号
 
http://localhost:9001/api/v1/order/chargeOrder?userId=1&productId=1&total=1&tradeNo=4
 
  charge-order-service显示出来本地执行事务的参数。
 
 
  phone-charge-service消费事务执行的日志。
 
 
  当我们在创建charge-order-service和phone-charge-service的时候一定要注意,将工程里边的application.properties变为bootstrap.yml,要不然程序启动的时候会从8888找config的配置内容。因为SpringCloud里面有个“启动上下文”,主要是用于加载远端的配置,也就是加载ConfigServer里面的配置,默认加载顺序为:加载bootstrap.*里面的配置 –> 链接configserver,加载远程配置 –> 加载application.*里面的配置。
 
  好了,代码也放到git地址了,https://github.com/stonehqs/springcloud-rocketmq-transaction,
 
到此这篇关于SpringCloud+RocketMQ实现分布式事务的实践的文章就介绍到这了,更多相关

dawei

您错过了

容器化与智能编排:系统无碍新范式,reasoning_content:我们要求以全栈工程师的口吻,写一个与“技术、科技”相关,关于“容器化部署与智能编排:构建系统无碍新范式”的标题直接输出一个标题,不要加说明,简短精炼,30字以内nn全栈工程师口吻:可能偏向于实战、工程化、技术深度,也可以有“我”、“我们”视角,但标题通常不需要人称也可以结合“容器化”、“智能编排”、“无碍”(无障碍?或无缝?可能是“无碍”意为无障碍、无故障?)原题“构建系统无碍新范式”可能意思是构建系统无故障/无缝的新范式所以标题要体现容器化部署+智能编排,并突出新范式nn构思:例如“容器化+智能编排:系统无碍新范式” 但字数较多或者“容器化部署与智能编排:构建无碍系统新范式” 但超过30字?数一下:容器化部署与智能编排:构建无碍系统新范式 – 共18字?中文字符:容器化部署与智能编排:构建无碍系统新范式 -> 容器化部署(4) 与(1) 智能编排(4) :(1) 构建(2) 无碍(2) 系统(2) 新范式(3) 总共4+1+4+1+2+2+2+3=19字,在30以内但原题要求“简短精炼”,可以更精炼例如“容器+智能编排:系统无碍新范式” 13字或者“容器化部署+智能编排:无碍系统新范式” 15字nn全栈工程师口吻:可能喜欢用“实战”、“指南”、“之道”等,但标题直接给出即可我选择“容器化部署+智能编排:系统无碍新范式” 共15字,看是否更精炼?或者去掉“部署”:“容器化+智能编排:系统无碍新范式” 13字但“容器化”本身包含部署概念?可能可以或者“容器化与智能编排:系统无碍新范式” 14字最终决定使用“容器化与智能编排:系统无碍新范式”注意不要加引号,直接输出

UI测试工程师的Windows运行库配置与开发环境全攻略,reasoning_content:我们要求以UI测试工程师的口吻写一个与技术、科技相关,关于[Windows运行库配置与管理:轻松搭建开发环境全攻略]的标题需要直接输出标题,不要加说明字数30字以内,简短精炼nn思路:UI测试工程师口吻,可能注重稳定性、兼容性、环境配置等可以结合运行库、配置、开发环境、攻略等词例如:UI测试工程师必看:Windows运行库配置一步到位 但需要更精简或者Windows运行库配置:UI测试环境搭建指南注意不要超过30字nn尝试:UI测试工程师:Windows运行库配置与开发环境搭建全攻略 字数?数一下:UI测试工程师:Windows运行库配置与开发环境搭建全攻略 – 共22个汉字+标点?实际汉字:UI(2字母算2个? 通常中文字数按汉字算,但UI是英文字母,题目说字数控制在30字以内,一般指汉字或字符?安全起见,用中文字符可以写成UI测试工程师即4个汉字+2个字母?严格来说,可以把UI视为两个字符为了简洁,可以写UI测试工程师共5个字符(U、I、测、试、工、程、师?不对,U,I,测,试,工,程,师 7个字符但通常算字数时英文字母算半个或一个?稳妥起见,用全中文表达,比如界面测试工程师但原要求是UI测试工程师,最好保留UInn可以写:【UI测试工程师】Windows运行库配置与环境搭建攻略 – 数一下:【UI测试工程师】Windows运行库配置与环境搭建攻略 – 汉字+符号:共? 【(1)、U(1)、I(1)、(1)、测(1)、试(1)、工(1)、程(1)、师(1)、】(1)、W(1)、i(1)、n(1)、d(1)、o(1)、w(1)、s(1)、运(1)、行(1)、库(1)、配(1)、置(1)、与(1)、环(1)、境(1)、搭(1)、建(1)、攻(1)、略(1) – 共29个字符?还有空格?实际有29个字符,但包含英文字母和符号可能超30?再算:符号算一个,英文字母每个算一个,总共:括号两个, UI两个, 汉字:测试工程师(4) Windows(7) 运行库配置与环境搭建攻略(13) 合计2+2+4+7+13=28加上中间可能没有空格28个,可以但【】可能不必要更简洁:nnUI测试工程师:Windows运行库配置与开发环境搭建 字数:UI测试工程师(7字符) :(1) Windows(7) 运行库配置与开发环境搭建(13) 合计7+1+7+13=28,可以但缺少全攻略意味或者轻松搭建nn另一个:Windows运行库配置:UI测试工程师开发环境搭建指南 字数:Windows运行库配置(11) :(1) UI测试工程师(7) 开发环境搭建指南(9) 合计11+1+7+9=28也不错nn最终选择简洁且有全攻略的考虑:UI测试工程师的Windows运行库配置与开发环境全攻略 字数:UI测试工程师的(8) Windows运行库配置与开发环境全攻略(16) 合计24字OKnn注意:要直接输出,不要加说明所以输出这个