RocketMQ特性--事务消息是个啥,咋发出去的呢?

RocketMQ事务消息如何保证数据的最终一致性 前言 在面过的几家大厂中,几乎每轮的面试官(没写错,几乎是每轮面试官)都问了同样一个问题:你们的系统是分布式的系统吗? 答:是。 面试官:那么你们分布式的系统是如何解决分布式事务这个问题的呢?也就是如何保证数据的一致性。 答:我们的系统中通过 RocketMQ 的事务消息来保证数据的最终一致性。 面试官:那你说说它是如何来保证数据的最终一致性的? 答:分两部分来回答,第一部分先回答事务消息的实现流程,第二部分解释为什么它能保证数据的最终一致性。 事务消息的实现流程 [外链图片转存失败,源站可能有防盗链机 阅读详情

1 概述

分布式事务时指在多个系统或者多个数据库中的多个操作要么全部成功,要么全部失败,并且需要满足ACID四个特性:

  • A Atomicity,原子性。操作是一个整体,要么全部成功,要么全部失败
  • C Consistency,一致性。事务操作前后,数据必须是一致的。
  • I Isolation,隔离性。多个事务同时执行时,不能互相干扰。
  • D Durability,持久性。一旦事务被提交,数据改变就是永久性的。

2 事务消息机制

2.1 生产者发送事务消息

事务消息的发送过程分为两个阶段,

  1. 发送事务消息
  2. 发送endTransaction消息

2.2 TransactionMQProducer类

发送过程的实现类是org.apache.rocketmq.client.producer.TransactionMQProducer,不仅能发送事务消息还能发送其他的消息。

public class TransactionMQProducer extends DefaultMQProducer {
    private TransactionCheckListener transactionCheckListener;
    private int checkThreadPoolMinSize = 1;
    private int checkThreadPoolMaxSize = 1;
    private int checkRequestHoldMax = 2000;

    private ExecutorService executorService;

    private TransactionListener transactionListener;
}

2.2.3 transactionListener

transactionListener是事务监听器,主要功能是执行本地事务和执行事务回查。

public interface TransactionListener {
    LocalTransactionState executeLocalTransaction(final Message msg, final Object arg);
    LocalTransactionState checkLocalTransaction(final MessageExt msg);
}

TransactionListener包含两个方法,

  • executeLocalTransaction()方法执行本地事务
  • checkLocalTransaction()方法是当生产者由于各种问题未发Commit或者Rollback消息给Broker时,Broker回调生产者查询本地事务状态的处理方法。

2.2.4 executorService Broker回查请求处理的线程池。

2.2.5 start()方法

start()方法添加了一个initTransactionEnv(),作用是初始化事务消息的环境信息。

@Override
public void start() throws MQClientException {
    this.defaultMQProducerImpl.initTransactionEnv();
    super.start();
}

从代码中我们可以看出,这里主要初始化了Broker回查请求处理的线程池,初始化的时候,可以指定初始化对象,当不指定的时候,将初始化一个单线程的线程池。

public void initTransactionEnv() {
    TransactionMQProducer producer = (TransactionMQProducer) this.defaultMQProducer;
    if (producer.getExecutorService() != null) {
        this.checkExecutor = producer.getExecutorService();
    } else {
        this.checkRequestQueue = new LinkedBlockingQueue<Runnable>(producer.getCheckRequestHoldMax());
        this.checkExecutor = new ThreadPoolExecutor(
            producer.getCheckThreadPoolMinSize(),
            producer.getCheckThreadPoolMaxSize(),
            1000 * 60,
            TimeUnit.MILLISECONDS,
            this.checkRequestQueue);
    }
}

2.2.6 shutdown()

关闭生产者,回收生产者资源。

@Override
public void shutdown() {
    super.shutdown();
    this.defaultMQProducerImpl.destroyTransactionEnv();
}

destroyTransactionEnv()销毁了事务回查线程池,清除了回查任务队列

3 发送事务消息的步骤

3.1 发送Half消息

事务消息的发送是通过sendMessageInTransaction方法来完成的。

@Override
public TransactionSendResult sendMessageInTransaction(final Message msg,
    final Object arg) throws MQClientException {

    if (null == this.transactionListener) {
        throw new MQClientException("TransactionListener is null", null);
    }

    msg.setTopic(NamespaceUtil.wrapNamespace(this.getNamespace(), msg.getTopic()));
    return this.defaultMQProducerImpl.sendMessageInTransaction(msg, null, arg);
}

3.1.1 数据校验

判断transactionListener的值是否为null,topic和Namespace的空判断以及一些基础校验。还校验了message:

Validators.checkMessage(msg, this.defaultMQProducer);

3.1.2 消息预处理

在方法sendMessageInTransaction()中,消息预处理主要是通过扩展字段设置消息类型。

//表示当前消息是事务half消息
MessageAccessor.putProperty(msg, MessageConst.PROPERTY_TRANSACTION_PREPARED, "true");
//设置消息的group name和扩展字段。
MessageAccessor.putProperty(msg, MessageConst.PROPERTY_PRODUCER_GROUP, this.defaultMQProducer.getProducerGroup());

3.1.3 发送事务消息

这里直接调用了同步发送消息的发送方法,之前写过一节专门讲这个的。 juejin.cn/post/712652…

try {
    sendResult = this.send(msg);
} catch (Exception e) {
    throw new MQClientException("send message Exception", e);
}

3.1.4 执行本地事务

消息发送成功之后,执行本地事务

LocalTransactionState localTransactionState = LocalTransactionState.UNKNOW;
Throwable localException = null;
switch (sendResult.getSendStatus()) {
    case SEND_OK: {
        try {
            if (sendResult.getTransactionId() != null) {
                msg.putUserProperty("__transactionId__", sendResult.getTransactionId());
            }
            String transactionId = msg.getProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX);
            if (null != transactionId && !"".equals(transactionId)) {
                msg.setTransactionId(transactionId);
            }
            if (null != localTransactionExecuter) {
                localTransactionState = localTransactionExecuter.executeLocalTransactionBranch(msg, arg);
            } else if (transactionListener != null) {
                log.debug("Used new transaction API");
                localTransactionState = transactionListener.executeLocalTransaction(msg, arg);
            }
            if (null == localTransactionState) {
                localTransactionState = LocalTransactionState.UNKNOW;
            }

            if (localTransactionState != LocalTransactionState.COMMIT_MESSAGE) {
                log.info("executeLocalTransactionBranch return {}", localTransactionState);
                log.info(msg.toString());
            }
        } catch (Throwable e) {
            log.info("executeLocalTransactionBranch exception", e);
            log.info(msg.toString());
            localException = e;
        }
    }
    break;
    case FLUSH_DISK_TIMEOUT:
    case FLUSH_SLAVE_TIMEOUT:
    case SLAVE_NOT_AVAILABLE:
        localTransactionState = LocalTransactionState.ROLLBACK_MESSAGE;
        break;
    default:
        break;
}

3.2 发送Commit或者RollBack消息

本地事务处理完成之后,会调用endTransaction()方法,通知Broker进行Commit或者Rollback.

try {
    this.endTransaction(msg, sendResult, localTransactionState, localException);
} catch (Exception e) {
    log.warn("local transaction execute " + localTransactionState + ", but end broker transaction failed", e);
}

public void endTransaction(
    final Message msg,
    //Half消息的发送结果
    final SendResult sendResult,
    //本地事务执行结果
    final LocalTransactionState localTransactionState,
    final Throwable localException) throws RemotingException, MQBrokerException, InterruptedException, UnknownHostException {
    final MessageId id;
    if (sendResult.getOffsetMsgId() != null) {
        id = MessageDecoder.decodeMessageId(sendResult.getOffsetMsgId());
    } else {
        id = MessageDecoder.decodeMessageId(sendResult.getMsgId());
    }
    //事务消息的事务id
    String transactionId = sendResult.getTransactionId();
    final String destBrokerName = this.mQClientFactory.getBrokerNameFromMessageQueue(defaultMQProducer.queueWithNamespace(sendResult.getMessageQueue()));
    //存储当前Half消息的Broker的服务器地址
    final String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(destBrokerName);
    EndTransactionRequestHeader requestHeader = new EndTransactionRequestHeader();
    requestHeader.setTransactionId(transactionId);
    requestHeader.setCommitLogOffset(id.getOffset());
    switch (localTransactionState) {
        case COMMIT_MESSAGE:
            requestHeader.setCommitOrRollback(MessageSysFlag.TRANSACTION_COMMIT_TYPE);
            break;
        case ROLLBACK_MESSAGE:
            requestHeader.setCommitOrRollback(MessageSysFlag.TRANSACTION_ROLLBACK_TYPE);
            break;
        case UNKNOW:
            requestHeader.setCommitOrRollback(MessageSysFlag.TRANSACTION_NOT_TYPE);
            break;
        default:
            break;
    }

    doExecuteEndTransactionHook(msg, sendResult.getMsgId(), brokerAddr, localTransactionState, false);
    //生产者group
    requestHeader.setProducerGroup(this.defaultMQProducer.getProducerGroup());
    //消息位移
    requestHeader.setTranStateTableOffset(sendResult.getQueueOffset());
    requestHeader.setMsgId(sendResult.getMsgId());
    String remark = localException != null ? ("executeLocalTransactionBranch exception: " + localException.toString()) : null;
    //以发送oneway消息的方式通知Broker进行Commit或者Rollback
    this.mQClientFactory.getMQClientAPIImpl().endTransactionOneway(brokerAddr, requestHeader, remark,
        this.defaultMQProducer.getSendMsgTimeout());
}

至此,事务消息发送并处理完毕。

 

RocketMQ 生产者源码分析:DefaultMQProducer、DefaultMQProducerImpl 该篇博文主要介绍生产者侧相关的类:MQProducer 能力定义者、DefaultMQProducer 能力抽象者、DefaultMQProducerImpl 能力实现者、ClientConfig RocketMQ 客户端公共的配置类,主要围绕它们内部的参数配置以及属性先初步开展介绍了一下,后续主要在启动 Producer 以及通过 Producer 发送消息时,在生产者这一侧处理的工作 阅读详情

相关推荐

保护数据完整性:探索 RocketMQ 分布式事务消息的力量

该篇博文主要分析了 RocketMQ 事务消息流程中的事务 Half 消息发送以及本地事务消息的补偿流程,重点的是结合 GitHub 部分对 RocketMQ 这块的设计部分进行整体了阐述,博主通过时序图+源码具体实现的部分对其进行了更深层次的描述,RocketMQ 源码部分在接收到事务消息时的处理过程以及在对事务消息这一块的补偿处理部分,希望能够加深你对事务消息这块的理解

vnjohn 博主 > 主线分享后端领域业务设计知识、源码思想、架构设计 6551

RocketMQ事务实例

生成者:发送半消息 使用很简单,但是参数还是说明一下吧: //可以自定义些MQ配置参数,我们这里不需要所以一切默认 @Autowired private RocketMQTemplate rocketMQTemplate; public TransactionSendResult sendMessageInTransaction(String txProducerGroup, String destination, Message<?> messa.

天生我材必有用,千金散尽还复来。 1342

RocketMQ消息幂等(去重)通用解决方案

消息中间件是分布式系统常用的组件,无论是异步化、解耦、削峰等都有广泛的应用价值。我们通常会认为,消息中间件是一个可靠的组件——这里所谓的可靠是指,只要我把消息成功投递到了消息中间件,消息就不会丢失,即消息肯定会至少保证消息能被消费者成功消费一次,这是消息中间件最基本的特性之一,也就是我们常说的“AT LEAST ONCE”,即消息至少会被“成功消费一遍”。 举个例子,一个消息M发送到了消息中间件,消息投递到了消费程序A,A接受到了消息,然后进行消费,但在消费到一半的时候程序重启了,这时候这个消息并没有标记

azhou的代码园 3821

RocketMQ进阶-事务消息

这个月马上就又要过去了,还在找工作的小伙伴要做好准备了,小编整理了大厂java程序员面试涉及到的绝大部分面试题及答案,希望能帮助到大家;这一块的代码逻辑都是在生产端,即Order-Server,大家不要搞错了这个月马上就又要过去了,还在找工作的小伙伴要做好准备了,小编整理了大厂java程序员面试涉及到的绝大部分面试题及答案,希望能帮助到大家[外链图片转存中…(img-M5SsUCsG-1714443570462)][外链图片转存中…(img-qzj9SZPw-1714443570462)]本文已被。

2301_79987255的博客 941

RocketMQ 事务消息

RocketMQ 事务消息是支持在分布式场景下保障消息生产和本地事务的最终一致性。编写一个实战例子并不复杂,但使用事务消息时需要注意如下三点:1、事务生产者和消费者共同协作才能保证业务数据的最终一致性;2、事务生产者需要实现事务监听器,并且保存事务的执行结果(比如事务日志表);3、消费者要保证幂等。消费失败时,通过重试告警+人工介入等手段保证消费结果正确。

2301_78526383的博客 1730

最终一致性之本地消息表、消息事务

互联网大背景下,微服务盛行,平时开发中难免会遇到分布式事务问题。大家经常会听到CAP原理,即一致性(Consistency)、可用性(Availability)、分区容错性(Partition tolerance)三个指标不可能同时满足,而一致性是其中一个最大的难题。本文会从分布式事务产生背景开始,介绍常见分布式事务解决方案,最后会详细介绍基于消息中间件实现最终一致性的两种方案 文章目录分布式事务产生背景数据库水平拆分微服务拆分分布式事务解决方案TCCXA事务(2PC)最终一致性(消息事务)错误场景一错误

hosaos的博客 3286

RocketMQ事务消息原理

基于MQ的分布式事务方案,本质上是对本地消息表的一个封装,整体流程与本地消息表一致,唯一不同的就是将本地消息表存在了MQ内部,而不是业务数据库,事务消息解决的是生产端的消息发送与本地事务执行的原子性问题,确保 MQ 生产端正确无误地将消息发送出来,没有多发,也不会漏发,至于发送后消费端有没有正常的消费消息,这种异常场景将由 MQ 消息消费失败重试机制来保证。

张维鹏的博客 1万+

RocketMQ——TransactionMQProducer发送事务消息源码分析

TransactionMQProducer 事务模式下的生产者,继承自DefaultMQProducer类,用于发送事务消息。成员变量TransactionListener 是实现事务消息的关键。 同时需要明确TransactionMQProducer是非线程安全的(因为TransactionListener成员的存在),因此每一种事务消息都应该使用自己单独的TransactionMQProducer实例发送消息。 public class TransactionMQProducer extends

WannaRunning的博客 870

详解RocketMQ事务消息

详解RocketMQ顺序消息 顺序消息是RocketMQ提供的非常重要的一个特性,在4.x版本之后开源,可以利用事务消息轻松地实现分布式事务。本文对RocketMQ的事务消息进行详细介绍,并给出了代码示例。 一. 相关概念 RocketMQ在其消息定义的基础上,对事务消息扩展了两个相关的概念: Half(Prepare) Message——半消息(预处理消息) 半消息是一种特殊的消息类型,该状...

weixin_34452850的博客 8100

PICO+Unity MR空间网格开发实战:从零构建透视交互场景

本文详细介绍了使用PICO 4 Ultra与Unity引擎进行MR空间网格开发的完整实战流程。从环境准备、项目初始化、核心组件配置,到空间网格的生成、可视化与物理交互实现,手把手教你构建一个能识别真实环境并发射虚拟小球的透视交互场景。文章重点解析了PICO SDK中空间网格功能的关键配置与优化技巧,并提供了真机部署与调试的实用指南。

e3f4g5的博客 490

拆解博世、大陆的EMB方案:自增力机构如何省下83%的能耗?

本文深度解析了博世和大陆的电子机械制动(EMB)技术,重点探讨了自增力机构如何实现83%的能耗节省。通过对比电机直驱型和自增力型EMB的技术参数,揭示了自增力机构在能量优化和系统集成方面的创新优势,为汽车电动化和智能化提供了关键技术支持。

weixin_42665725的博客 396

RocketMQ高级特性总结(事务消息、顺序消息、延迟消息)

文章目录事务消息场景分析基本思路及问题所在基本概念RocketMQ-事务消息设计思路两阶段提交事务状态定时回查延迟消息场景分析延迟消息Producer DemoRocketMQ延迟级别实现原理顺序消息全局顺序分区顺序Producer DemoConsumer Demo实现思路及原理 事务消息 场景分析 假设有两个用户,其银行账户不在一个银行中,服务不同,数据库也不同。现在要实现一个功能:用户A给B...

hosaos的博客 4569

深入解析Unity3D中PostLateUpdate突然占用大量时间的原因

在Unity3D的生命周期中,是一个在LateUpdate之后被调用的方法。它通常用于处理一些需要在所有更新(包括物理更新和动画更新)之后进行的逻辑。由于它在游戏循环的后期阶段被调用,因此它通常用于处理与渲染直接相关的任务,如相机调整、UI更新等。突然占用大量时间的问题可能由多种原因引起,包括复杂的逻辑处理、频繁的资源加载、不恰当的渲染优化、插件或第三方库的副作用以及内存泄漏或垃圾回收等。为了解决这个问题,开发者需要仔细审查和优化中的代码,同时利用Unity提供的工具来监控和分析游戏性能。

Yori_22的博客 981

【消息队列RocketMQ】三、RocketMQ 高级特性:事务消息、顺序消息与延时消息

在分布式系统日益复杂的当下,RocketMQ 作为一款功能强大的消息中间件,其高级特性如事务消息、顺序消息与延时消息,为解决复杂业务场景的问题提供了有力支持。本文将基于 CentOS 7 操作系统,详细介绍这些高级特性的原理、配置、使用方法及实践案例,帮助读者深入理解并灵活运用这些特性。

qq_58611691的博客 1647

5分钟搞懂SCMA:稀疏码多址接入的核心原理与实战应用

本文深入浅出地解析了SCMA(稀疏码多址接入)的核心原理与实战应用。通过对比传统正交多址技术,阐释了SCMA如何通过非正交叠加和稀疏码本设计,实现远超资源单元数的用户接入,从而有效应对5G海量机器通信(mMTC)的挑战。文章还探讨了其在上行海量连接、免授权接入等场景的应用潜力及工程实现中的关键考量。

zeta9的博客 264

中国银行信息科技岗位笔试

收到笔试只剩四天了,收集的资料,后续会更新

weixin_46050242的博客 4597

RocketMQ事务消息

在文末我们提到了使用RocketMQ提供的一种新思路:异步队列方案,去解决我们的分布式事务问题,接下来我们直入主题看看它是如何解决的。

m0_71836706的博客 957

uniapp - 详细实现移动端公众号 H5 网页授权登录流程及示例代码,申请测试公众号全流程及本地调试无需部署上线服务器就能直接调起微信登陆授权页面,手机移动端h5公众号页面利用微登录拿到用户数据!

uni-app,微信公众号,h5网页,uniapp网页端,手机端H5公众号页面,微信网页登陆,uniApp详细实现公众号的登录,微信授权第三方网站登录,流程及逻辑详细教程,OAuth2.0,公众号网页授权,微信网页授权通过code换取网页授权,redirect_url,参数错误,解决,报错,无法调起,如何申请测试公众号,微信公众平台测试号的申请与使用,获取用户openid手机号昵称头像,利用测试号实现本地调试微信登录授权,本地无法调试怎么办,测试公众号,在开发环境下怎么测试,用项目本地ip地址,微信公众号平

🏆 前端领域优质创作者 5064

RocketMQ高级特性一事务顺序延时普通消息

本文主要详细讲解 RocketMQ 的四种消息类型:普通消息、定时或延迟消息、顺序消息、事务消息。每种消息类型包括使用场景、原理机制、优缺点和代码示例。注:部分图片集信息来源于Apache通过这些实践,可以深入理解 RocketMQ 的高级特性,并在实际项目中应用这些功能来处理复杂的消息传递需求。

StaticKing的博客 1655

Backtrader Web:开源量化交易全栈平台

Backtrader Web是一个基于Backtrader框架的现代化量化交易全栈平台,将专业回测引擎与Web技术结合,提供从策略开发到实盘交易的完整解决方案。平台采用FastAPI+Vue3技术栈,具有118个内置策略模板、专业可视化分析工具和完整的策略生命周期管理功能。支持策略版本控制、参数优化、模拟/实盘交易及监控告警,通过RESTful API和WebSocket实现前后端交互。系统采用模块化设计,包含策略服务、回测服务等核心模块,支持SQLite/PostgreSQL等多种数据库。安装简单,5分钟

云金杞 647

《C/C++等级考试(1~8级)历届真题解析》专栏总目录

中国电子学会《全国青少年软件编程等级考试》C/C++编程(1~8级)历届真题解析。订阅后可阅读专栏内所有真题解析,真题持续更新中!

人生就是一个不断学习的过程 2万+

图解RocketMQ之如何实现事务消息

我们先想象一个场景:从用户 A 的账户中扣减 100 元。向用户 B 的账户中增加 100 元。这两个步骤要么都成功,要么都失败。如果在步骤 1 扣减了100元,但步骤 2 增加时发生了错误,我们希望,用户A的账户金额恢复到原来的状态。上面是一个典型的事务场景,什么是事务?,简而言之,事务是逻辑上的一组操作,要么都执行,要么都不执行。下面有一道经典的八股,衡量事务的四大特性的了解程度,理解的背一下就好了。

一个成长中的程序员,期待用技术的眼光生活 1738

SIMATIC S7-1200, S7-1500 PID 控制[手册]

SIMATIC S7-1200, S7-1500 PID 控制[手册]

Springboot+RocketMQ(7):实现事务消息

分布式事务通俗来说,一次操作由若干分支组成,这些分支操作分属于不同的应用,分布在不同的服务器上,分布式事务需要保证这些操作,分支事务与普通事务一样,就是为了保证操作结果的一致性。

EvanDeveloper的专栏 3874

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

VC中sqlite的使用

在VC中对sqlite的使用,实现数据库大批数据的快速插入,读取数据库中的数据在ListCtrl中显示

RocketMQ事务消息实战

   我们以一个订单流转流程来举例,例如订单子系统创建订单,需要将订单数据下发到其他子系统(与第三方系统对接)这个场景,我们通常会将两个系统进行解耦,不直接使用服务调用的方式进行交互。其业务实现步骤通常为:    1、A系统创建订单并入库。    2、发送消息到MQ。    3、MQ消费者消费消息,发送远程RPC服务调用,完成订单数据的同步。    1、方案一    方案弊端:  ...

中间件兴趣圈 4万+

一文详解RocketMQ的事务消息原理

事务消息不支持延时消息和批量消息。为了避免单个消息被检查太多次而导致半队列消息累积,MQ默认将单个消息的检查次数限制为 15 次,用户可以通过参数来修改此限制。如果已经回查某条消息超过次的话,则Broker将丢弃此消息,并打印错误日志。事务消息将在参数指定的时间长度之后被回查。当发送事务消息时,用户还可以通过设置用户属性来改变这个限制,该参数优先于参数。事务消息的生产者 ID 不能与其他类型消息的生产者 ID 共享。

2301_76607156的博客 2373
上一篇: Spring5源码3-BeanDefinition
下一篇: 发布60分钟!霸榜Github的阿里10w字Java面试手册到底有多强?
Java技术那些事儿
博客等级 码龄5年 2306粉丝 2584原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值