事务消息
什么是事务消息 笔者这里就不说了 笔者主要是通过源码去描述 RocketMQ如何实现事务消息,在RocketMQ中事务消息的设计以及状态和源码实现.
如何使用
通过RocketMQ的源码中的example中的看下如何使用 主要涉及到TransactionMQProducer
和TransactionListener
两个类。
public static void main(String[] args) throws MQClientException, InterruptedException {
TransactionListener transactionListener = new TransactionListenerImpl();
TransactionMQProducer producer = new TransactionMQProducer("TestTranscationProducerName");
... 省略部分代码
producer.setTransactionListener(transactionListener);
producer.start();
String[] tags = new String[]{"TagA", "TagB", "TagC", "TagD", "TagE"};
for (int i = 0; i < 10; i++) {
try {
Message msg =
new Message("TopicTest", tags[i % tags.length], "KEY" + i,
("Hello RocketMQ " + i).getBytes(RemotingHelper.DEFAULT_CHARSET));
SendResult sendResult = producer.sendMessageInTransaction(msg, null);
Thread.sleep(10);
} catch (MQClientException | UnsupportedEncodingException e) {
e.printStackTrace();
}
}
....
}
TransactionListener 中包含2个方法
//当事务prepare消息发送到broker后 会执行的方法
LocalTransactionState executeLocalTransaction(final Message msg, final Object arg);
// 当执行后事务消息 没有返回响应的时候 比如返回的是UN_KNOW状态的时候会执行此方法 其实就Broker对消息生产者的一个询问事务状态
LocalTransactionState checkLocalTransaction(final MessageExt msg);
public class TransactionListenerImpl implements TransactionListener {
private AtomicInteger transactionIndex = new AtomicInteger(0);
private ConcurrentHashMap<String, Integer> localTrans = new ConcurrentHashMap<>();
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
int value = transactionIndex.getAndIncrement();
int status = value % 3;
localTrans.put(msg.getTransactionId(), status);
return LocalTransactionState.UNKNOW;
}
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
Integer status = localTrans.get(msg.getTransactionId());
if (null != status) {
switch (status) {
case 0:
return LocalTransactionState.UNKNOW;
case 1:
return LocalTransactionState.COMMIT_MESSAGE;
case 2:
return LocalTransactionState.ROLLBACK_MESSAGE;
default:
return LocalTransactionState.COMMIT_MESSAGE;
}
}
return LocalTransactionState.COMMIT_MESSAGE;
}
}
上面代码是RocketMQ给的Example代码,主要是为说明 事务消息的一个用法
关键点在于TransactionMQProducer
发送消息 和 TransactionListener
一个事务消息的事件监听 可以看到有COMMIT_MESSAGE
,ROLLBACK_MESSAGE
,UNKNOW
三种状态.
那么接下来我们主要将分析放在 TransactionMQProducer
和 TransactionListener
以及状态之间的关系 如何工作的。
三种状态
COMMIT_MESSAGE
commit消息 也就是代表事务完成 提交的一个消息
ROLLBACK_MESSAGE
rollback消息 也就是回滚消息
UNKNOW
UNKNOW是代表事件的一个check状态 需要broker询问到Producer 该消息是否完成
上面是整个事务消息所存在的三种状态 接下来我们看下具体的工作流程.
源码分析工作流程
TransactionMQProducer
我们通过源码分析事务消息的一个具体工作流程 就从发送消息的TransactionMQProducer#sendMessageInTransaction
开始。
因为源码方法太长,笔者将主分支的代码留下 会删减其他不重要的判断和检查代码
public TransactionSendResult sendMessageInTransaction(final Message msg,
final LocalTransactionExecuter localTransactionExecuter, final Object arg)
....
//添加消息属性PROPERTY_TRANSACTION_PREPARED 为true 表示为事务消息
MessageAccessor.putProperty(msg, MessageConst.PROPERTY_TRANSACTION_PREPARED, "true");
//设置当前生产组
MessageAccessor.putProperty(msg, MessageConst.PROPERTY_PRODUCER_GROUP, this.defaultMQProducer.getProducerGroup());
...
SEND_OK: //发送成功后
String transactionId = msg.getProperty(MessageConst.PROPERTY_UNIQ_CLIENT_MESSAGE_ID_KEYIDX);
if (null != transactionId && !"".equals(transactionId)) {
msg.setTransactionId(transactionId);
}
// 省略 localTransactionState 过时兼容的逻辑 已经是过时的 5.0之后会删除 这里就不说了。 ...
if (transactionListener != null) {
//执行本地 executeLocalTransaction 方法
localTransactionState = transactionListener.executeLocalTransaction(msg, arg);
}
if (null == localTransactionState) {
localTransactionState = LocalTransactionState.UNKNOW;
}
// 下面会详细说明这个方法
this.endTransaction(msg, sendResult, localTransactionState, localException);
可以看到sendMessageInTransaction
主要实现了 消息发送,添加事务消息的标志以及 发送成功后会执行executeLocalTransaction
的方法返回对应的localTransactionState
给endTransaction
去执行。
public void endTransaction(
final Message msg,
final SendResult sendResult,
final LocalTransactionState localTransactionState,
final Throwable localException) throws RemotingException, MQBrokerException, InterruptedException, UnknownHostException {
final MessageId id;
//... 省略id 和 连接的处理
//创建EndTransactionRequestHeader
EndTransactionRequestHeader requestHeader = new EndTransactionRequestHeader();
//设置事务ID
requestHeader.setTransactionId(transactionId);
// 设置事务消息对应的CommitLogOffset
requestHeader.setCommitLogOffset(id.getOffset());
// 设置事务消息的状态 对应我们执行executeLocalTransaction返回的状态
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;
}
// 一些hook操作
doExecuteEndTransactionHook(msg, sendResult.getMsgId(), brokerAddr, localTransactionState, false);
// 设置当前发送消息的生产者组
requestHeader.setProducerGroup(this.defaultMQProducer.getProducerGroup());
// 设置对应队列的offset
requestHeader.setTranStateTableOffset(sendResult.getQueueOffset());
// 设置消息ID
requestHeader.setMsgId(sendResult.getMsgId());
// executeLocalTransaction 是否出现异常
String remark = localException != null ? ("executeLocalTransactionBranch exception: " + localException.toString()) : null;
...
//发送消息状态给Broker this.mQClientFactory.getMQClientAPIImpl().endTransactionOneway(brokerAddr, requestHeader, remark,
this.defaultMQProducer.getSendMsgTimeout());
}
主流程的发送流程基本如下
1.通过
TransactionMQProducer
发送事务消息 设置对应事务的监听TransactionListener
2.sendMessageInTransaction 将消息标识为事务消息,发送成功后执行TransactionListener#executeLocalTransaction
返回对应事务消息的状态
3.封装EndTransactionRequestHeader
消息类型发送给broker当前事务处理的状态
通过上面我们大致知道 TransactionListener#executeLocalTransaction
执行的实际,但是没有看到对应消息状态和整体逻辑的关系 所以接下来我们需要去broker
中找到对应事务消息的处理
Broker
SendMessageProcessor
同样我们从Broker接受到事务消息开始分析SendMessageProcessor#asyncSendMessage
开始分析.
下面带是从asyncSendMessage
中摘抄出来的有关事务消息的处理
if (transFlag != null && Boolean.parseBoolean(transFlag)) {
//判断当前Broker是否接收事务消息
if (this.brokerController.getBrokerConfig().isRejectTransactionMessage()) {
response.setCode(ResponseCode.NO_PERMISSION);
response.setRemark(
"the broker[" + this.brokerController.getBrokerConfig().getBrokerIP1()
+ "] sending transaction message is forbidden");
return CompletableFuture.completedFuture(response);
}
//通过TransactionalMessageService单独处理事务消息
putMessageResult = this.brokerController.getTransactionalMessageService().asyncPrepareMessage(msgInner);
}else{
// 其他消息交给DefaultMessageStore处理
putMessageResult = this.brokerController.getMessageStore().asyncPutMessage(msgInner);
}
return handlePutMessageResultFuture(putMessageResult, response, request, msgInner, responseHeader, mqtraceContext, ctx, queueIdInt);
通过上面代码看到事务消息的处理机制是单独由TransactionalMessageService
处理 接下来就通过分析TransactionalMessageService
查看消息的处理.
@Override
public CompletableFuture<PutMessageResult> asyncPrepareMessage(MessageExtBrokerInner messageInner) {
return transactionalMessageBridge.asyncPutHalfMessage(messageInner);
}
可以看到在TransactionalMessageService
又使用了TransactionalMessageBridge#asyncPutHalfMessage
的方法处理消息 这里注意这个方法的名字HalfMessage 一半消息,我们经常说的HalfMessage
其实就这里的意思。
继续往下看
public CompletableFuture<PutMessageResult> asyncPutHalfMessage(MessageExtBrokerInner messageInner) {
return store.asyncPutMessage(parseHalfMessageInner(messageInner));
}
private MessageExtBrokerInner parseHalfMessageInner(MessageExtBrokerInner msgInner) {
//保存原有消息的Topic
MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_REAL_TOPIC, msgInner.getTopic());
//保存原有消息的Queue
MessageAccessor.putProperty(msgInner, MessageConst.PROPERTY_REAL_QUEUE_ID,
String.valueOf(msgInner.getQueueId()));
// 设置事务状态为 TRANSACTION_NOT_TYPE
msgInner.setSysFlag(
MessageSysFlag.resetTransactionValue(msgInner.getSysFlag(), MessageSysFlag.TRANSACTION_NOT_TYPE));
// 设置Topic为 RMQ_SYS_TRANS_HALF_TOPIC
msgInner.setTopic(TransactionalMessageUtil.buildHalfTopic());
// 设置队列ID为0
msgInner.setQueueId(0);
msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties()));
return msgInner;
}
发现还是使用的DefaultMessageStore
, 只不过添加一层消息的包装 和我们的延时消息做法差不多 都是将Topic和Queue转换成 特殊消息的Topic和Queue。
那么这个消息处理流程的第一步就能知道了
1.消息生产者生产事务消息发送给broker
2.Broker接收消息后将对应消息包装为HalfMessage消息(替换Topic 和 Queue) 存储至CommitLog
3.返回给生产者 发送成功
4.消息生产者 处理本地事务TransactionListener#executeLocalTransaction
5.将对应消息的事物处理结果EndTransactionRequestHeader
发送给Broker
分析到这里基本上面5步是可以确定,接下来就是主要分析 Broker接受到EndTransactionRequestHeader
是如何处理不同状态的。
EndTransactionProcessor
processRequest方法是用来处理Producer发来的EndTransactionRequestHeader
消息
... // 省略判断 以及日志消息
OperationResult result = new OperationResult();
// 当状态为TRANSACTION_COMMIT_TYPE 的时候
if (MessageSysFlag.TRANSACTION_COMMIT_TYPE == requestHeader.getCommitOrRollback()) {
//通过offset 获取之前存储的HalfMessage 也就是转换过Topic的消息
result = this.brokerController.getTransactionalMessageService().commitMessage(requestHeader);
if (result.getResponseCode() == ResponseCode.SUCCESS) {
// 检查消息跟 提交过来的参数是否匹配
RemotingCommand res = checkPrepareMessage(result.getPrepareMessage(), requestHeader);
if (res.getCode() == ResponseCode.SUCCESS) {
//将之前的HalfMessage 转换为 MessageExtBrokerInner
MessageExtBrokerInner msgInner = endMessageTransaction(result.getPrepareMessage());
//清除事务状态标识
msgInner.setSysFlag(MessageSysFlag.resetTransactionValue(msgInner.getSysFlag(), requestHeader.getCommitOrRollback()));
//设置TranStateTable偏移量
msgInner.setQueueOffset(requestHeader.getTranStateTableOffset());
//设置Half消息的偏移量
msgInner.setPreparedTransactionOffset(requestHeader.getCommitLogOffset());
//设置存储时间与Half消息一致
msgInner.setStoreTimestamp(result.getPrepareMessage().getStoreTimestamp());
//清除事务消息属性数据
MessageAccessor.clearProperty(msgInner, MessageConst.PROPERTY_TRANSACTION_PREPARED);
//存储消息到CommitLog中
RemotingCommand sendResult = sendFinalMessage(msgInner);
if (sendResult.getCode() == ResponseCode.SUCCESS) {
//删除Half消息 这里需要注意 并不是真正的删除 只是添加一个消息 用来标识 Half已经被删除
this.brokerController.getTransactionalMessageService().deletePrepareMessage(result.getPrepareMessage());
}
return sendResult;
}
return res;
}
} else if (MessageSysFlag.TRANSACTION_ROLLBACK_TYPE == requestHeader.getCommitOrRollback()) {
//通过offset 获取之前存储的HalfMessage 也就是转换过Topic的消息
result = this.brokerController.getTransactionalMessageService().rollbackMessage(requestHeader);
if (result.getResponseCode() == ResponseCode.SUCCESS) {
//校验消息
RemotingCommand res = checkPrepareMessage(result.getPrepareMessage(), requestHeader);
if (res.getCode() == ResponseCode.SUCCESS) {
//删除Half消息 和Commit是一样的 只是添加一条消息用来标识 Half被删除
this.brokerController.getTransactionalMessageService().deletePrepareMessage(result.getPrepareMessage());
}
return res;
}
}
...
可以通过上面看到 处理了两种状态值 TRANSACTION_COMMIT_TYPE
和 TRANSACTION_ROLLBACK_TYPE
处理步骤如下:
1.通过offset 找到之前的 Half消息也就是转换过Topic 和 Queue的消息
2.如果是COMMIT消息则将Half消息还原为之前的Message并存储到CommitLog中
3.添加一条消息用来标志Half消息已被删除
这里还需要说明下deletePrepareMessage
最终会调用到addRemoveTagInTransactionOp
这个方法中 可以看到OP中的body存储的是Half消息队列的Offset。
主要OP消息的Body为messageExt.getQueueOffset()
private boolean addRemoveTagInTransactionOp(MessageExt messageExt, MessageQueue messageQueue) {
Message message = new Message(TransactionalMessageUtil.buildOpTopic(), TransactionalMessageUtil.REMOVETAG,
String.valueOf(messageExt.getQueueOffset()).getBytes(TransactionalMessageUtil.charset));
writeOp(message, messageQueue);
return true;
}
需要区分下两个Topic这个比较重要
RMQ_SYS_TRANS_OP_HALF_TOPIC
public static String buildOpTopic() {
return TopicValidator.RMQ_SYS_TRANS_OP_HALF_TOPIC;
}
RMQ_SYS_TRANS_HALF_TOPIC
public static String buildHalfTopic() {
return TopicValidator.RMQ_SYS_TRANS_HALF_TOPIC;
}
RMQ_SYS_TRANS_HALF_TOPIC 这个是消息转换为事务消息存储的 Topic 也就是 当事务消息发送过来 转换的Topic为
buildHalfTopic()
这个方法。
RMQ_SYS_TRANS_OP_HALF_TOPIC 而这个消息 是在消息EndTransactionProcessor
中回传了COMMIT
或则ROLLBACK
的时候 新增加的一条删除Half消息的标识 可以理解为记录half消息的处理状态的一条消息 这个在下面的Check
会详细说明
这样就可以完善事务消息的一些状态处理,但是我们一直都没看到一种状态叫UNKNOW
,那么如果返回的是UNKNOW
状态 Broker又是如何处理的呢?
这个就要从 TransactionalMessageService
去查看如何处理UNKNOW
的状态
TransactionalMessageService
笔者上面说过TransactionalMessageService#prepareMessage
方法是用来将消息包装成Half
的消息。
那么如果一直没有收到COMMIT
和ROLLBACK
我们通过check
方法来查看,我们在网上看到的check
的状态指的就是UNKNOW
的状态或者说是prepare
的状态,主要实现逻辑在check
方法中
首先我们确定check方法运行的时机. 在TransactionalMessageCheckService
中会执行到check方法 并且 TransactionalMessageCheckService
是继承于ServiceThread
的 代码如下
@Override
public void run() {
log.info("Start transaction check service thread!");
long checkInterval = brokerController.getBrokerConfig().getTransactionCheckInterval();
while (!this.isStopped()) {
this.waitForRunning(checkInterval);
}
log.info("End transaction check service thread!");
}
@Override
protected void onWaitEnd() {
// 超时时间
long timeout = brokerController.getBrokerConfig().getTransactionTimeOut();
// 最大询问次数
int checkMax = brokerController.getBrokerConfig().getTransactionCheckMax();
// 开始执行时间
long begin = System.currentTimeMillis();
log.info("Begin to check prepare message, begin time:{}", begin);
// 执行TransactionalMessageService的check方法
this.brokerController.getTransactionalMessageService().check(timeout, checkMax, this.brokerController.getTransactionalMessageCheckListener());
log.info("End to check prepare message, consumed time:{}", System.currentTimeMillis() - begin);
}
}
通过上面代码我们可以看到 是根据
transactionCheckInterval
配置每隔多长时间执行一次的,也就是说相当于一个定时器一样 隔一段时间执行一次 隔一段时间执行一次 , 调用service的时候 传递了超时时间 最大询问次数 和 TransactionalMessageCheckListener
我们先分析下 this.brokerController.getTransactionalMessageCheckListener()
是什么?
对应到实现类DefaultTransactionalMessageCheckListener
包含几个比较重要的方法 我这里先列出来
resolveHalfMsg 发送check消息给Producer
public void resolveHalfMsg(final MessageExt msgExt) {
executorService.execute(new Runnable() {
@Override
public void run() {
try {
//发送check消息给Producer
sendCheckMessage(msgExt);
} catch (Exception e) {
LOGGER.error("Send check message error!", e);
}
}
});
}
resolveDiscardMsg 丢弃消息 通过时间和次数判断的事务消息超时或者超过最大check次数 采取丢弃消息的策略
@Override
public void resolveDiscardMsg(MessageExt msgExt) {
try {
//将消息存的Topic改为 TRANS_CHECK_MAX_TIME_TOPIC 写入Commitlog 相当于丢弃消息的操作了
MessageExtBrokerInner brokerInner = toMessageExtBrokerInner(msgExt);
PutMessageResult putMessageResult = this.getBrokerController().getMessageStore().putMessage(brokerInner);
if (putMessageResult != null && putMessageResult.getPutMessageStatus() == PutMessageStatus.PUT_OK) {
。。。
} catch (Exception e) {
log.warn("Put checked-too-many-time message to TRANS_CHECK_MAXTIME_TOPIC error. {}", e);
}
}
接下来我们分析check
方法中的实现细节,check
这个方法相对来说 实现的比较长 所以我将内部的源码关键点提出来一个一个分析。
- 通过Topic获取对应事务的MessageQueue
String topic = TopicValidator.RMQ_SYS_TRANS_HALF_TOPIC;
Set<MessageQueue> msgQueues = transactionalMessageBridge.fetchMessageQueues(topic);
- 遍历获取MessageQueue两种不同的Topic消费的偏移量
RMQ_SYS_TRANS_OP_HALF_TOPIC
和RMQ_SYS_TRANS_HALF_TOPIC
for (MessageQueue messageQueue : msgQueues) {
long halfOffset = transactionalMessageBridge.fetchConsumeOffset(messageQueue);
long opOffset = transactionalMessageBridge.fetchConsumeOffset(opQueue);
...
}
- 通过OP消息将那些属于需要删除的消息记录到removeMap中 具体实现如下
List<Long> doneOpOffset = new ArrayList<>();
HashMap<Long, Long> removeMap = new HashMap<>();
PullResult pullResult = fillOpRemoveMap(removeMap, opQueue, opOffset, halfOffset, doneOpOffset);
private PullResult fillOpRemoveMap(HashMap<Long, Long> removeMap,
MessageQueue opQueue, long pullOffsetOfOp, long miniOffset, List<Long> doneOpOffset) {
//通过队列偏移量获取对应OP具体消息
PullResult pullResult = pullOpMsg(opQueue, pullOffsetOfOp, 32);
... 删除部分判断
List<MessageExt> opMsg = pullResult.getMsgFoundList();
//遍历获取OP消息
for (MessageExt opMessageExt : opMsg) {
//通过OP消息可以得到 Half消息在队列中的偏移量
Long queueOffset = getLong(new String(opMessageExt.getBody(), TransactionalMessageUtil.charset));
// ....
if (TransactionalMessageUtil.REMOVETAG.equals(opMessageExt.getTags())) {
//当标识这个Half消息已经被删除
if (queueOffset < miniOffset) {
//记录到已处理过的List中
doneOpOffset.add(opMessageExt.getQueueOffset());
} else {
// 添加到需要删除的Map中
removeMap.put(queueOffset, opMessageExt.getQueueOffset());
}
}
}
return pullResult;
}
- 我们可以通过以上几步确认我们需要检查的数据 那些已经删除过了 那么下面代码就是一个循环操作,处理每一个Half消息,向Producer去询问消息以及丢弃消息的逻辑。
while (true) {
...
if (removeMap.containsKey(i)) {
log.debug("Half offset {} has been committed/rolled back", i);
Long removedOpOffset = removeMap.remove(i);
// 已经有删除标识的偏移量 加入到已处理的集合中
doneOpOffset.add(removedOpOffset);
} else {
// 通过offset 和 具体的队列 topic 去commitlog中获取消息
GetResult getResult = getHalfMsg(messageQueue, i);
MessageExt msgExt = getResult.getMsg();
if (msgExt == null) {
if (getMessageNullCount++ > MAX_RETRY_COUNT_WHEN_HALF_NULL) {
break;
}
if (getResult.getPullResult().getPullStatus() == PullStatus.NO_NEW_MSG) {
log.debug("No new msg, the miss offset={} in={}, continue check={}, pull result={}", i,
messageQueue, getMessageNullCount, getResult.getPullResult());
break;
} else {
log.info("Illegal offset, the miss offset={} in={}, continue check={}, pull result={}",
i, messageQueue, getMessageNullCount, getResult.getPullResult());
i = getResult.getPullResult().getNextBeginOffset();
newOffset = i;
continue;
}
}
// needDiscard 需要丢弃判断 并且同时增加了一次次数 check次数判断 或者 needSkip 超过消息预留的时间
if (needDiscard(msgExt, transactionCheckMax) || needSkip(msgExt)) {
// 丢弃消息 上面有描述这个方法的实现
listener.resolveDiscardMsg(msgExt);
newOffset = i + 1;
i++;
continue;
}
if (msgExt.getStoreTimestamp() >= startTime) {
log.debug("Fresh stored. the miss offset={}, check it later, store={}", i,
new Date(msgExt.getStoreTimestamp()));
break;
}
....
List<MessageExt> opMsg = pullResult.getMsgFoundList();
//当先运行时间-消息生产的时间 超过了每次检查设置的时间 单条消息可以单独设置 没有设置默认就是整个执行超时的时间
boolean isNeedCheck = (opMsg == null && valueOfCurrentMinusBorn > checkImmunityTime)
//上面代码执行时间超过了单次运行超时时间
|| (opMsg != null && (opMsg.get(opMsg.size() - 1).getBornTimestamp() - startTime > transactionTimeout))
// 当前时间小于生产时间
|| (valueOfCurrentMinusBorn <= -1);
if (isNeedCheck) {
// 将Half消息再次写入到commitlog中
if (!putBackHalfMsgQueue(msgExt, i)) {
continue;
}
//发送检查事务消息给Producer
listener.resolveHalfMsg(msgExt);
} else {
//当不执行check的时候 再次计算 removeMap
pullResult = fillOpRemoveMap(removeMap, opQueue, pullResult.getNextBeginOffset(), halfOffset, doneOpOffset);
log.debug("The miss offset:{} in messageQueue:{} need to get more opMsg, result is:{}", i,
messageQueue, pullResult);
continue;
}
}
newOffset = i + 1;
i++;
}
关于消费进度的保存和计算 不在本篇文章的重点中 后面会整理整个Rocket中的Offset的文章说明 这里简单看下配置文件 consumerOffset的配置.
{
"offsetTable":{
....
"RMQ_SYS_TRANS_HALF_TOPIC@CID_RMQ_SYS_TRANS":{0:132
},
"RMQ_SYS_TRANS_OP_HALF_TOPIC@CID_RMQ_SYS_TRANS":{0:50
}
}
}
总结
上面我们基本可以将 生产者发送事务消息到Broker处理消息 以及事务的几个状态的关系 以及 工作的机制的源码都分析了一次,下面我们将整个事务的流程进行梳理
整个流程差不多如下图