RocketMQ源码分析之RocketMQ事务消息实现原理中篇----事务消息状态回查

boolean isNeedCheck = (opMsg == null && valueOfCurrentMinusBorn > checkImmunityTime)

|| (opMsg != null && (opMsg.get(opMsg.size() - 1).getBornTimestamp() - startTime > transactionTimeout))

|| (valueOfCurrentMinusBorn <= -1); // @10

if (isNeedCheck) {

if (!putBackHalfMsgQueue(msgExt, i)) { // @11

continue;

}

listener.resolveHalfMsg(msgExt);

} else {

pullResult = fillOpRemoveMap(removeMap, opQueue, pullResult.getNextBeginOffset(), halfOffset, doneOpOffset); // @12

log.info(“The miss offset:{} in messageQueue:{} need to get more opMsg, result is:{}”, i,

messageQueue, pullResult);

continue;

}

}

newOffset = i + 1;

i++;

}

if (newOffset != halfOffset) { // @13

transactionalMessageBridge.updateConsumeOffset(messageQueue, newOffset);

}

long newOpOffset = calculateOpOffset(doneOpOffset, opOffset);

if (newOpOffset != opOffset) { // @14

transactionalMessageBridge.updateConsumeOffset(opQueue, newOpOffset);

}

本段代码比较长,却是事务状态回查的重点实现。

代码@1:先解释几个局部变量的含义。

getMessageNullCount :获取空消息的次数。

newOffset :当前处理RMQ_SYS_TRANS_HALF_TOPIC#queueId的最新进度。

i:当前处理消息的队列偏移量,其主题依然为RMQ_SYS_TRANS_HALF_TOPIC。

代码@2:这段代码应该不陌生,这是RocketMQ处理任务的一个通用处理逻辑,就是一个任务处理,可以限制每次最多处理的时间,RocketMQ为待检测主题RMQ_SYS_TRANS_HALF_TOPIC的每个队列,做事务状态回查,一次最多不超过60S,目前该值不可配置。

代码@3:如果removeMap中包含当前处理的消息,则继续下一条,removeMap中 的值是通过Step3中,填充的,具体实现逻辑是从RMQ_SYS_TRANS_OP_HALF_TOPIC主题中拉取32条,如果拉取的消息队列偏移量大于等于RMQ_SYS_TRANS_HALF_TOPIC#queueId当前的处理进度时,会添加到removeMap中,表示已处理过。

代码@4:根据消息队列偏移量i从消费队列中获取消息。

代码@5:如果消息为空,则根据允许重复次数进行操作,默认重试一次,目前不可配置。其具体实现为:

1、如果超过重试次数,直接跳出,结束该消息队列的事务状态回查。

2、如果是由于没有新的消息而返回为空(拉取状态为:PullStatus.NO_NEW_MSG),则结束该消息队列的事务状态回查。

3、其他原因,则将偏移量i设置为: getResult.getPullResult().getNextBeginOffset(),重新拉取。

代码@6:判断该消息是否需要discard(吞没,丢弃,不处理)、或skip(跳过),其依据如下:

1、needDiscard 依据:如果该消息回查的次数超过允许的最大回查次数,则该消息将被丢弃,即事务消息提交失败,不能被消费者消费,其做法,主要是每回查一次,在消息属性TRANSACTION_CHECK_TIMES中增1,默认最大回查次数为5次。

2、needSkip依据:如果事务消息超过文件的过期时间,默认72小时(具体请查看RocketMQ过期文件相关内容),则跳过该消息。

代码@7:处理事务超时相关概念,先解释几个局部变量:、

valueOfCurrentMinusBorn :该消息已存储的时间,等于系统当前时间减去消息存储的时间戳。

checkImmunityTime :立即检测事务消息的时间,其设计的意义是,应用程序在发送事务消息后,事务不会马上提交,该时间就是假设事务消息发送成功后,应用程序事务提交的时间,在这段时间内,RocketMQ任务事务未提交,故不应该在这个时间段向应用程序发送回查请求。

transactionTimeout:事务消息的超时时间,这个时间是从OP拉取的消息的最后一条消息的存储时间与check方法开始的时间,如果时间差超过了transactionTimeout,就算时间小于checkImmunityTime时间,也发送事务回查指令。

代码@8:如果消息指定了事务消息过期时间属性(PROPERTY_CHECK_IMMUNITY_TIME_IN_SECONDS),如果当前时间已超过该值。

代码@9:如果当前时间还未过(应用程序事务结束时间),则跳出本次回查处理的,等下一次再试。

代码@10:判断是否需要发送事务回查消息,具体逻辑:

1、如果从操作队列(RMQ_SYS_TRANS_OP_HALF_TOPIC)中没有已处理消息并且已经超过(应用程序事务结束时间),参数transactionTimeOut值。

2、如果操作队列不为空,并且最后一天条消息的存储时间已经超过transactionTimeOut值。

代码@11:如果需要发送事务状态回查消息,则先将消息再次发送到RMQ_SYS_TRANS_HALF_TOPIC主题中,发送成功则返回true,否则返回false,这里还有一个实现关键点:

if (putMessageResult != null

&& putMessageResult.getPutMessageStatus() == PutMessageStatus.PUT_OK) {

msgExt.setQueueOffset(

putMessageResult.getAppendMessageResult().getLogicsOffset());

msgExt.setCommitLogOffset(

putMessageResult.getAppendMessageResult().getWroteOffset());

msgExt.setMsgId(putMessageResult.getAppendMessageResult().getMsgId());

}

如果发送成功,会将该消息的queueOffset、commitLogOffset设置为重新存入的偏移量,为什么需要这样呢,答案在listener.resolveHalfMsg(msgExt)中。

AbstractTransactionalMessageCheckListener#resolveHalfMsg

public void resolveHalfMsg(final MessageExt msgExt) {

executorService.execute(new Runnable() {

@Override

public void run() {

try {

sendCheckMessage(msgExt);

} catch (Exception e) {

LOGGER.error(“Send check message error!”, e);

}

}

});

}

发送具体的事务回查机制,这里用一个线程池来异步发送回查消息,为了回查进度保存的简化,这里只要发送了回查消息,当前回查进度会向前推动,如果回查失败,上一步骤新增的消息将可以再次发送回查消息,那如果回查消息发送成功,那会不会下一次又重复发送回查消息呢?这个可以根据OP队列中的消息来判断是否重复,如果回查消息发送成功并且消息服务器完成提交或回滚操作,这条消息会发送到OP队列中,然后首先会通过fillOpRemoveMap根据处理进度获取一批已处理的消息,来与消息判断是否重复,由于fillopRemoveMap一次只拉32条消息,那又如何保证一定能拉取到与当前消息的处理记录呢?其实就是通过代码@10,如果此批消息最后一条未超过事务延迟消息,则继续拉取更多消息进行判断(@12)和(@14),op队列也会随着回查进度的推进而推进。

代码@12:如果无法判断是否发送回查消息,则加载更多的已处理消息进行刷选。

代码@13:保存(Prepare)消息队列的回查进度。

代码@14:保存处理队列(op)的进度。

上述讲解了TransactionalMessageCheckService回查定时线程的发送回查消息的整体流程与实现细节,接下来重点分析一下上述步骤@11,通过异步方式发送消息回查的实现过程。

AbstractTransactionalMessageCheckListener#sendCheckMessage

public void sendCheckMessage(MessageExt msgExt) throws Exception {

CheckTransactionStateRequestHeader checkTransactionStateRequestHeader = new CheckTransactionStateRequestHeader();

checkTransactionStateRequestHeader.setCommitLogOffset(msgExt.getCommitLogOffset());

checkTransactionStateRequestHeader.setOffsetMsgId(msgExt.getMsgId());

checkTransactionStateRequestHeader.setMsgId(msgExt.getUserProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX));

checkTransactionStateRequestHeader.setTransactionId(checkTransactionStateRequestHeader.getMsgId());

checkTransactionStateRequestHeader.setTranStateTableOffset(msgExt.getQueueOffset()); // @1

msgExt.setTopic(msgExt.getUserProperty(MessageConst.PROPERTY_REAL_TOPIC));

msgExt.setQueueId(Integer.parseInt(msgExt.getUserProperty(MessageConst.PROPERTY_REAL_QUEUE_ID)));

msgExt.setStoreSize(0); // @2

String groupId = msgExt.getProperty(MessageConst.PROPERTY_PRODUCER_GROUP); // @3

Channel channel = brokerController.getProducerManager().getAvaliableChannel(groupId);

if (channel != null) {

brokerController.getBroker2Client().checkProducerTransactionState(groupId, channel, checkTransactionStateRequestHeader, msgExt); // @4

} else {

LOGGER.warn(“Check transaction failed, channel is null. groupId={}”, groupId);

}

}

代码@1:首先构建回查事务状态请求消息,请求核心参数包括:消息offsetId、消息ID(索引)、消息事务ID、事务消息队列中的偏移量(RMQ_SYS_TRANS_HALF_TOPIC)。

代码@2:恢复原消息的主题、队列,并设置storeSize为0。

代码@3:获取生产者组名称。

代码@4:根据生产者组获取任意一个生产者,通过与其连接发送事务回查消息,回查消息的请求者为【Broker服务器】,接收者为(client,具体为消息生产者)。

其处理类为:org.apache.rocketmq.client.impl.ClientRemotingProcessor#processRequest,其详细逻辑实现方法为:

ClientRemotingProcessor#checkTransactionState

public RemotingCommand checkTransactionState(ChannelHandlerContext ctx,

RemotingCommand request) throws RemotingCommandException {

final CheckTransactionStateRequestHeader requestHeader =

(CheckTransactionStateRequestHeader) request.decodeCommandCustomHeader(CheckTransactionStateRequestHeader.class);

final ByteBuffer byteBuffer = ByteBuffer.wrap(request.getBody());

final MessageExt messageExt = MessageDecoder.decode(byteBuffer);

if (messageExt != null) {

String transactionId = messageExt.getProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX);

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

messageExt.setTransactionId(transactionId);

}

final String group = messageExt.getProperty(MessageConst.PROPERTY_PRODUCER_GROUP);

if (group != null) {

MQProducerInner producer = this.mqClientFactory.selectProducer(group);

if (producer != null) {

final String addr = RemotingHelper.parseChannelRemoteAddr(ctx.channel());

producer.checkTransactionState(addr, messageExt, requestHeader); // @1

} else {

log.debug(“checkTransactionState, pick producer by group[{}] failed”, group);

}

} else {

log.warn(“checkTransactionState, pick producer group failed”);

}

} else {

log.warn(“checkTransactionState, decode message failed”);

}

return null;

}

代码@1:最终调用生产者的checkTransactionState方法。

DefaultMQProducerImpl#checkTransactionState

public void checkTransactionState(final String addr, final MessageExt msg,

final CheckTransactionStateRequestHeader header) {

Runnable request = new Runnable() { // @1

private final String brokerAddr = addr;

private final MessageExt message = msg;

private final CheckTransactionStateRequestHeader checkRequestHeader = header;

private final String group = DefaultMQProducerImpl.this.defaultMQProducer.getProducerGroup();

@Override

public void run() {

TransactionListener transactionCheckListener = DefaultMQProducerImpl.this.checkListener(); // @1

if (transactionCheckListener != null) {

LocalTransactionState localTransactionState = LocalTransactionState.UNKNOW;

Throwable exception = null;

try {

localTransactionState = transactionCheckListener.checkLocalTransaction(message); // @2

} catch (Throwable e) {

log.error(“Broker call checkTransactionState, but checkLocalTransactionState exception”, e);

exception = e;

}

this.processTransactionState( // @3

localTransactionState,

group,

exception);

} else {

log.warn(“checkTransactionState, pick transactionCheckListener by group[{}] failed”, group);

}

}

private void processTransactionState(

final LocalTransactionState localTransactionState,

final String producerGroup,

final Throwable exception) {

final EndTransactionRequestHeader thisHeader = new EndTransactionRequestHeader();

thisHeader.setCommitLogOffset(checkRequestHeader.getCommitLogOffset());

thisHeader.setProducerGroup(producerGroup);

thisHeader.setTranStateTableOffset(checkRequestHeader.getTranStateTableOffset());

thisHeader.setFromTransactionCheck(true);

String uniqueKey = message.getProperties().get(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX);

if (uniqueKey == null) {

uniqueKey = message.getMsgId();

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

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

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

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

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

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
img

最后

这份《“java高分面试指南”-25分类227页1000+题50w+字解析》同样可分享给有需要的朋友,感兴趣的伙伴们可挑战一下自我,在不看答案解析的情况,测试测试自己的解题水平,这样也能达到事半功倍的效果!(好东西要大家一起看才香)

image

image

的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!**

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

如果你觉得这些内容对你有帮助,可以添加V获取:vip1024b (备注Java)
[外链图片转存中…(img-HmWvHbWG-1711718644228)]

最后

这份《“java高分面试指南”-25分类227页1000+题50w+字解析》同样可分享给有需要的朋友,感兴趣的伙伴们可挑战一下自我,在不看答案解析的情况,测试测试自己的解题水平,这样也能达到事半功倍的效果!(好东西要大家一起看才香)

[外链图片转存中…(img-HQYgaEfc-1711718644229)]

[外链图片转存中…(img-BMFbwTbB-1711718644229)]

  • 10
    点赞
  • 30
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
很抱歉,根据提供的引用内容,我无法提供NSGA-III算法的MATLAB代码。引用提到了一种快速的非支配排序遗传算法扩展,但没有提供具体的代码。引用则提到了NSGA-III算法的最终迭代结果,但没有提供代码。引用则是对NSGA-III算法的一些公式进行了修正,同样没有提供具体的MATLAB代码。如果您需要获取NSGA-III算法的MATLAB代码,建议您在相关的学术论文或研究资料中查找。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* [nsgaii算法代码MATLAB-NSGA-III:基于坎普尔遗传算法实验室代码的NSGA-III,A-NSGA-III和A^2-NSGA-I](https://download.csdn.net/download/weixin_38743235/18917414)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *2* [NSGA-Ⅲ算法设计思路及Matlab代码](https://blog.csdn.net/qq_45823589/article/details/130591172)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] - *3* [多目标优化 | NSGA-Ⅲ(中篇,附MATLAB代码)](https://blog.csdn.net/weixin_40730979/article/details/130437411)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_2"}}] [.reference_item style="max-width: 33.333333333333336%"] [ .reference_list ]

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值