RocketMQ高手之路系列之九:RocketMQ之事务消息(二)

@Override

public TransactionSendResult sendMessageInTransaction(final Message msg,

final Object arg) throws MQClientException {

if (null == this.transactionListener) {

throw new MQClientException(“TransactionListener is null”, null);

}

return this.defaultMQProducerImpl.sendMessageInTransaction(msg, null, arg);

}

}

DefaultMQProducerImpl中进行事务消息调用,关键步骤在代码中进行了标注,如下所示:

public class DefaultMQProducerImpl implements MQProducerInner {

public TransactionSendResult sendMessageInTransaction(final Message msg,

final LocalTransactionExecuter localTransactionExecuter, final Object arg)

throws MQClientException {

TransactionListener transactionListener = getCheckListener();

if (null == localTransactionExecuter && null == transactionListener) {

throw new MQClientException(“tranExecutor is null”, null);

}

//1、进行消息检测

Validators.checkMessage(msg, this.defaultMQProducer);

SendResult sendResult = null;

//2、设置事务消息

MessageAccessor.putProperty(msg, MessageConst.PROPERTY_TRANSACTION_PREPARED, “true”);

MessageAccessor.putProperty(msg, MessageConst.PROPERTY_PRODUCER_GROUP, this.defaultMQProducer.getProducerGroup());

try {

//3、同步发送事务消息

sendResult = this.send(msg);

} catch (Exception e) {

throw new MQClientException(“send message Exception”, e);

}

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);

//4、发送成功,设置transactionId

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”);

//5、执行本地事务,获取本地事务执行状态

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;

}

try {

//6、更具本地事务执行状态,执行半消息的提交或者回滚

this.endTransaction(sendResult, localTransactionState, localException);

} catch (Exception e) {

log.warn("local transaction execute " + localTransactionState + “, but end broker transaction failed”, e);

}

TransactionSendResult transactionSendResult = new TransactionSendResult();

transactionSendResult.setSendStatus(sendResult.getSendStatus());

transactionSendResult.setMessageQueue(sendResult.getMessageQueue());

transactionSendResult.setMsgId(sendResult.getMsgId());

transactionSendResult.setQueueOffset(sendResult.getQueueOffset());

transactionSendResult.setTransactionId(sendResult.getTransactionId());

transactionSendResult.setLocalTransactionState(localTransactionState);

return transactionSendResult;

}

}

为了方便童鞋们理解,特地将关键步骤提取成了处理流程,大致的交互如下所示:

在这里插入图片描述

2、事务消息发送

在消息的发送过程中,实际调用到了DefaultMQProducerImplsendKernelImpl方法进行消息发送。

在这里插入图片描述

在消息发送过程中,对消息的属性进行判断,如果TRAN_MSGtrue,则消息属性设置sysFlagTRANSACTION_PREPARED_TYPE,将消息类型设置为办消息类型。

final String tranMsg = msg.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED);

if (tranMsg != null && Boolean.parseBoolean(tranMsg)) {

sysFlag |= MessageSysFlag.TRANSACTION_PREPARED_TYPE;

}

String isTrans = msg.getProperty(MessageConst.PROPERTY_TRANSACTION_PREPARED);

if (isTrans != null && isTrans.equals(“true”)) {

context.setMsgType(MessageType.Trans_Msg_Half);

}

3、事务消息存储

client端进行消息发送之后,broker server端接收到信息。在TransactionalMessageBridge 进行事务消息的处理,如下所示:

public class TransactionalMessageBridge {

public PutMessageResult putHalfMessage(MessageExtBrokerInner messageInner) {

return store.putMessage(parseHalfMessageInner(messageInner));

}

private MessageExtBrokerInner parseHalfMessageInner(MessageExtBrokerInner msgInner) {

MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_REAL_TOPIC, msgInner.getTopic());

MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_REAL_QUEUE_ID,

String.valueOf(msgInner.getQueueId()));

msgInner.setSysFlag(

MessageSysFlag.resetTransactionValue(msgInner.getSysFlag(), MessageSysFlag.TRANSACTION_NOT_TYPE));

//设置消息的topic为RMQ_SYS_TRANS_HALF_TOPIC

msgInner.setTopic(TransactionalMessageUtil.buildHalfTopic());

//设置queueId为0

msgInner.setQueueId(0);

msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties()));

return msgInner;

}

}

我们可以看到Broker端将事务消息统一设置topicRMQ_SYS_TRANS_HALF_TOPIC,将消息投递到同一队列中。然后进行消息的存储操作,比如写入CommitLog以及ConsumerQueue中,此处不再展开,后续对于消息的存储进行源码分析的时候再进行详细的阐述。

4、事务消息后置处理

在事务消息发送过后,更具事务消息的发送情况决定该消息进行提交还是进行回滚。该部分主要逻辑在如下代码中实现:

public void endTransaction(

final SendResult sendResult,

final LocalTransactionState localTransactionState,

final Throwable localException) throws RemotingException, MQBrokerException, InterruptedException, UnknownHostException {

final MessageId id;

//从broker返回的信息中获取half消息的offset

if (sendResult.getOffsetMsgId() != null) {

id = MessageDecoder.decodeMessageId(sendResult.getOffsetMsgId());

} else {

id = MessageDecoder.decodeMessageId(sendResult.getMsgId());

}

String transactionId = sendResult.getTransactionId();

final String brokerAddr = this.mQClientFactory.findBrokerAddressInPublish(sendResult.getMessageQueue().getBrokerName());

EndTransactionRequestHeader requestHeader = new EndTransactionRequestHeader();

// 将transactionId和offset发送给broker,以便于broker查找half消息位置

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;

}

requestHeader.setProducerGroup(this.defaultMQProducer.getProducerGroup());

requestHeader.setTranStateTableOffset(sendResult.getQueueOffset());

requestHeader.setMsgId(sendResult.getMsgId());

String remark = localException != null ? ("executeLocalTransactionBranch exception: " + localException.toString()) : null;

//onway形式发送,不会等待返回

this.mQClientFactory.getMQClientAPIImpl().endTransactionOneway(brokerAddr, requestHeader, remark,

this.defaultMQProducer.getSendMsgTimeout());

}

消息发送之后,broker端处理收到的事务消息操作步骤,主要在EndTransactionProcessor进行处理,具体代码如下所示:

public class EndTransactionProcessor implements NettyRequestProcessor {

@Override

public RemotingCommand processRequest(ChannelHandlerContext ctx, RemotingCommand request) throws

RemotingCommandException {

final RemotingCommand response = RemotingCommand.createResponseCommand(null);

final EndTransactionRequestHeader requestHeader =

(EndTransactionRequestHeader)request.decodeCommandCustomHeader(EndTransactionRequestHeader.class);

LOGGER.info(“Transaction request:{}”, requestHeader);

if (BrokerRole.SLAVE == brokerController.getMessageStoreConfig().getBrokerRole()) {

response.setCode(ResponseCode.SLAVE_NOT_AVAILABLE);

LOGGER.warn("Message store is slave mode, so end transaction is forbidden. ");

return response;

}

if (requestHeader.getFromTransactionCheck()) {

switch (requestHeader.getCommitOrRollback()) {

case MessageSysFlag.TRANSACTION_NOT_TYPE: {

LOGGER.warn(“Check producer[{}] transaction state, but it’s pending status.”

  • “RequestHeader: {} Remark: {}”,

RemotingHelper.parseChannelRemoteAddr(ctx.channel()),

requestHeader.toString(),

request.getRemark());

return null;

}

case MessageSysFlag.TRANSACTION_COMMIT_TYPE: {

LOGGER.warn(“Check producer[{}] transaction state, the producer commit the message.”

  • “RequestHeader: {} Remark: {}”,

RemotingHelper.parseChannelRemoteAddr(ctx.channel()),

requestHeader.toString(),

request.getRemark());

break;

}

case MessageSysFlag.TRANSACTION_ROLLBACK_TYPE: {

LOGGER.warn(“Check producer[{}] transaction state, the producer rollback the message.”

  • “RequestHeader: {} Remark: {}”,

RemotingHelper.parseChannelRemoteAddr(ctx.channel()),

requestHeader.toString(),

request.getRemark());

break;

}

default:

return null;

}

} else {

switch (requestHeader.getCommitOrRollback()) {

case MessageSysFlag.TRANSACTION_NOT_TYPE: {

LOGGER.warn(“The producer[{}] end transaction in sending message, and it’s pending status.”

  • “RequestHeader: {} Remark: {}”,

RemotingHelper.parseChannelRemoteAddr(ctx.channel()),

requestHeader.toString(),

request.getRemark());

return null;

}

case MessageSysFlag.TRANSACTION_COMMIT_TYPE: {

break;

}

case MessageSysFlag.TRANSACTION_ROLLBACK_TYPE: {

LOGGER.warn(“The producer[{}] end transaction in sending message, rollback the message.”

  • “RequestHeader: {} Remark: {}”,

RemotingHelper.parseChannelRemoteAddr(ctx.channel()),

requestHeader.toString(),

request.getRemark());

break;

}

default:

return null;

}

}

OperationResult result = new OperationResult();

if (MessageSysFlag.TRANSACTION_COMMIT_TYPE == requestHeader.getCommitOrRollback()) {

result = this.brokerController.getTransactionalMessageService().commitMessage(requestHeader);

if (result.getResponseCode() == ResponseCode.SUCCESS) {

RemotingCommand res = checkPrepareMessage(result.getPrepareMessage(), requestHeader);

if (res.getCode() == ResponseCode.SUCCESS) {

MessageExtBrokerInner msgInner = endMessageTransaction(result.getPrepareMessage());

msgInner.setSysFlag(MessageSysFlag.resetTransactionValue(msgInner.getSysFlag(), requestHeader.getCommitOrRollback()));

msgInner.setQueueOffset(requestHeader.getTranStateTableOffset());

msgInner.setPreparedTransactionOffset(requestHeader.getCommitLogOffset());

msgInner.setStoreTimestamp(result.getPrepareMessage().getStoreTimestamp());

RemotingCommand sendResult = sendFinalMessage(msgInner);

if (sendResult.getCode() == ResponseCode.SUCCESS) {

this.brokerController.getTransactionalMessageService().deletePrepareMessage(result.getPrepareMessage());

}

return sendResult;

}

return res;

}

} else if (MessageSysFlag.TRANSACTION_ROLLBACK_TYPE == requestHeader.getCommitOrRollback()) {

result = this.brokerController.getTransactionalMessageService().rollbackMessage(requestHeader);

if (result.getResponseCode() == ResponseCode.SUCCESS) {

RemotingCommand res = checkPrepareMessage(result.getPrepareMessage(), requestHeader);

if (res.getCode() == ResponseCode.SUCCESS) {

this.brokerController.getTransactionalMessageService().deletePrepareMessage(result.getPrepareMessage());

}

return res;

}

}

response.setCode(result.getResponseCode());

response.setRemark(result.getResponseRemark());

return response;

}

}

5、本地事务状态回查

通过上述的代码分析可知,事务消息的提交和回滚操作是不等待消息响应的,所以说发送方并不会根据消息发送情况进行后续的处理。为了解决这一问题,客户端需要提供本地事务状态回查接口,这样Broker可以定时检查未处理的半消息对应的本地事务到底是执行成功还是执行失败。实现在TransactionalMessageCheckService中,如下所示:

public class TransactionalMessageCheckService extends ServiceThread {

@Override

protected void onWaitEnd() {

//获取transactionTimeOut参数

long timeout = brokerController.getBrokerConfig().getTransactionTimeOut();

//获取最大检测次数

int checkMax = brokerController.getBrokerConfig().getTransactionCheckMax();

long begin = System.currentTimeMillis();

log.info(“Begin to check prepare message, begin time:{}”, begin);

this.brokerController.getTransactionalMessageService().check(timeout, checkMax, this.brokerController.getTransactionalMessageCheckListener());

log.info(“End to check prepare message, consumed time:{}”, System.currentTimeMillis() - begin);

}

}

具体的检查流程在TransactionalMessageServiceImpl中的check方法中,如下所示:
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

我的面试宝典:一线互联网大厂Java核心面试题库

以下是我个人的一些做法,希望可以给各位提供一些帮助:

整理了很长一段时间,拿来复习面试刷题非常合适,其中包括了Java基础、异常、集合、并发编程、JVM、Spring全家桶、MyBatis、Redis、数据库、中间件MQ、Dubbo、Linux、Tomcat、ZooKeeper、Netty等等,且还会持续的更新…可star一下!

image

283页的Java进阶核心pdf文档

Java部分:Java基础,集合,并发,多线程,JVM,设计模式

数据结构算法:Java算法,数据结构

开源框架部分:Spring,MyBatis,MVC,netty,tomcat

分布式部分:架构设计,Redis缓存,Zookeeper,kafka,RabbitMQ,负载均衡等

微服务部分:SpringBoot,SpringCloud,Dubbo,Docker

image

还有源码相关的阅读学习

image

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
" alt=“img” style=“zoom: 33%;” />

我的面试宝典:一线互联网大厂Java核心面试题库

以下是我个人的一些做法,希望可以给各位提供一些帮助:

整理了很长一段时间,拿来复习面试刷题非常合适,其中包括了Java基础、异常、集合、并发编程、JVM、Spring全家桶、MyBatis、Redis、数据库、中间件MQ、Dubbo、Linux、Tomcat、ZooKeeper、Netty等等,且还会持续的更新…可star一下!

[外链图片转存中…(img-iWQ2fLon-1713380756941)]

283页的Java进阶核心pdf文档

Java部分:Java基础,集合,并发,多线程,JVM,设计模式

数据结构算法:Java算法,数据结构

开源框架部分:Spring,MyBatis,MVC,netty,tomcat

分布式部分:架构设计,Redis缓存,Zookeeper,kafka,RabbitMQ,负载均衡等

微服务部分:SpringBoot,SpringCloud,Dubbo,Docker

[外链图片转存中…(img-ImcsKeOL-1713380756942)]

还有源码相关的阅读学习

[外链图片转存中…(img-wyj22elc-1713380756942)]

《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!

  • 17
    点赞
  • 27
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值