图解事务消息
事务消息通过消息二次提交机制,可用于解决分布式事务问题。
如何发送事务消息
/**
* 发送事务消息
*/
@Test
public void sendTrasitionMessage() throws Exception {
// 创建事务的消息提供者
TransactionMQProducer producer = new TransactionMQProducer("group");
// 设置namesrv地址
producer.setNamesrvAddr("127.0.0.1:9876");
// 设置本地事务的检查方法
producer.setTransactionListener(new TransactionListener() {
/**
* @param message 消息对象
* @param o
* @return 事务状态
*/
@Override
public LocalTransactionState executeLocalTransaction(Message message, Object o) {
if (message.getTags().equals("TAG1")) {
// 本地事务执行完成,提交消息事务
return LocalTransactionState.COMMIT_MESSAGE;
}else if (message.getTags().equals("TAG2")) {
// 本地事务执行失败,回滚消息事务
return LocalTransactionState.ROLLBACK_MESSAGE;
}else if (message.getTags().equals("TAG3")) {
// 消息状态未知
return LocalTransactionState.UNKNOW;
}
return null;
}
/**
* 消息状态为UNKNOW时的回查函数,检查本地事务的执行情况
*/
@Override
public LocalTransactionState checkLocalTransaction(MessageExt messageExt) {
System.out.println(messageExt.getBody());
return LocalTransactionState.COMMIT_MESSAGE;
}
});
// 开启生产者
producer.start();
// 创建消息体
Message mesage = new Message("Topic","TAG1","myMsg".getBytes());
// 发送事务消息
producer.sendMessageInTransaction(mesage,null);
producer.shutdown();
}
事务状态
COMMIT_MESSAGE: 本地事务已提交,允许消费者消费该消息
ROLLBACK_MESSAGE: 本地事务回滚,删除该消息
UNKONW: 事务状态未知,broker需要回查本地事务
注意事项:
1.事务消息不支持延迟特性(源码会清空延迟等级)
// ignore DelayTimeLevel parameter
if (msg.getDelayTimeLevel() != 0) {
MessageAccessor.clearProperty(msg, MessageConst.PROPERTY_DELAY_TIME_LEVEL);
}
2.消息不能保证只被检查或消费一次,因此消费方需要做幂等处理
3.不支持批量发送
事务消息二次提交机制(源码分析):
1.生产者发送消息给broker -- 事务消息存放在特定队列中,此时消费不到
// 清空延迟消息的level
if (msg.getDelayTimeLevel() != 0) {
MessageAccessor.clearProperty(msg, MessageConst.PROPERTY_DELAY_TIME_LEVEL);
}
SendResult sendResult = null;
// 设置为预消息
MessageAccessor.putProperty(msg,
MessageConst.PROPERTY_TRANSACTION_PREPARED, "true");
MessageAccessor.putProperty(msg,
MessageConst.PROPERTY_PRODUCER_GROUP, this.defaultMQProducer.getProducerGroup());
try {
// 发送half半消息(prepared消息)
sendResult = this.send(msg);
} catch (Exception e) {
throw new MQClientException("send message Exception", e);
}
2.发送半消息成功之后,执行本地事务
switch (sendResult.getSendStatus()) {
case SEND_OK: {
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;
}
}
3.将本地事务状态通过请求头传给broker
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;
// 将事务状态传给broker
endTransactionOneway(brokerAddr, requestHeader, remark,
this.defaultMQProducer.getSendMsgTimeout());
4.若本地事务提交,broker则提交该消息。 -- 将事务消息推送到真实队列中
Broker端事务提交/回滚操作(这里取endTransaction部分)
代码入口:org.apache.rocketmq.broker.processor.EndTransactionProcessor
if (MessageSysFlag.TRANSACTION_COMMIT_TYPE == requestHeader.getCommitOrRollback()) {
result = this.brokerController.getTransactionalMessageService().commitMessage(requestHeader);
// 将msg的目标队列 设置为真实的队列/主题
MessageExtBrokerInner msgInner = endMessageTransaction(result.getPrepareMessage());
MessageAccessor.clearProperty(msgInner, MessageConst.PROPERTY_TRANSACTION_PREPARED);
// 并发送最终的消息到真实队列
RemotingCommand sendResult = sendFinalMessage(msgInner);
if (sendResult.getCode() == ResponseCode.SUCCESS) {
// 删除原事务队列的消息
this.brokerController.getTransactionalMessageService().deletePrepareMessage(result.getPrepareMessage());
}
return sendResult
}
4.若本地事务回滚,broker则删除消息。 -- 从特定事务队列中删除该消息
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;
}
}
5.若因网络等原因导致事务状态为UNKNOW,broker回查未知的消息。
6.定时回查事务状态。 -- 若失败,则删除消息,若事务成功,则提交消息到真实队列。