Make sure you read RocketMQ transaction message source analysis (dry)

Foreword

Thanks MQ load shifting, decoupling, asynchronous operation and other features in the Internet industry can be said to have a place to distributed services, MQ are often not absent. Ali from the Institute of RocketMQ more experienced double eleven years of high concurrency challenges, which launched the 4.3.0 version of the new features transaction message, the paper RocketMQ 4.5.0 version of the source code related to transaction message tracking reports, read by the reader Know:

  • Transaction message to solve any kind of problem
  • The principle of its design highlights transaction message

Solve the problem

I assume that the system where there is now such a scenario:

Local open-debit transaction database, MQ message sent after a successful delivery to the Stock Center.

Some people may think of open mybatis transaction implementation, the local transaction and MQ messaging together not on line yet? If MQ sent successfully, it commits the transaction, sending failed to roll back the transaction, the entire operation at one go.

transaction{
  扣款();
  boolean success = 发送MQ();
	if(success){
    commit();
  }else{
    rollBack();
  }
}
复制代码

Seemingly no problem, but the network is not reliable in.

MQ return assumptions over the response because the network has yet to receive a reason, so I had to be rolled back in the face of an uncertain return MQ results. But MQ server has indeed received this message, but to respond to the client is lost, so is the result of a failed charge, successful delivery.

scene

Since sending MQ messages and local affairs can not write together, how to ensure that overall demand has atomicity of it? The answer is that today we introduce the protagonist: the transaction message .

Overview

Overview)

Overall RocketMQ transaction message is divided into two main lines

  1. Sending process timer task : sending half message (message half), performs a local transaction, transmits transaction execution result
  2. Check back flow of regular tasks : MQ server back to check local affairs, sent the results of the transaction

Therefore, this paper were also analyzed by two main lines of the source

Source code analysis

Semi message sending process

Local application (client)

Core classes in the local application transaction message is sent TransactionMQProducer, through inheritance class DefaultMQProducer multiplexed message sent by the majority logic associated with the class code amount is very small only 100 rows, the following methods of this class are sendMessageTransaction

@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);
}
复制代码

This method does two things,

  1. Check whether there is transactionListener
  2. Call the parent to perform a transaction message

TransactionListener play a crucial role in the transaction message flow, take a look at this interface

public interface TransactionListener {
    /**
     * When send transactional prepare(half) message succeed, this method will be invoked to execute local transaction.
     *
     * @param msg Half(prepare) message
     * @param arg Custom business parameter
     * @return Transaction state
     */
    LocalTransactionState executeLocalTransaction(final Message msg, final Object arg);

    /**
     * When no response to prepare(half) message. broker will send check message to check the transaction status, and this
     * method will be invoked to get local transaction status.
     *
     * @param msg Check message
     * @return Transaction state
     */
    LocalTransactionState checkLocalTransaction(final MessageExt msg);
}
复制代码

Interface comment made it very clear, with the above overview of view is, executeLocalTransaction corresponding method is to perform local affairs operations, checkLocalTransaction correspondence is back to check local affairs operation.

The following is a method of source DefaultMQProducer class sendMessageInTransaction

public TransactionSendResult sendMessageInTransaction(final Message msg,
                                                      final LocalTransactionExecuter localTransactionExecuter, final Object arg)
    throws MQClientException {
    ...
    SendResult sendResult = null;
    MessageAccessor.putProperty(msg, MessageConst.PROPERTY_TRANSACTION_PREPARED, "true");
    MessageAccessor.putProperty(msg, MessageConst.PROPERTY_PRODUCER_GROUP, this.defaultMQProducer.getProducerGroup());
    						...
        sendResult = this.send(msg);
    						...
    switch (sendResult.getSendStatus()) {
        case SEND_OK: {
            		...
        localTransactionState = transactionListener.executeLocalTransaction(msg, arg);
                ...
        break;
        case FLUSH_DISK_TIMEOUT:
        case FLUSH_SLAVE_TIMEOUT:
        case SLAVE_NOT_AVAILABLE:
            localTransactionState = LocalTransactionState.ROLLBACK_MESSAGE;
            break;
        default:
            break;
    }

    						...
        this.endTransaction(sendResult, localTransactionState, localException);
								...
}
复制代码

In order to make the source code more intuitive logic, the author streamline the core code. sendMessageInTransaction main method to do the following things

  1. Transaction message to a message marked with the relevant marker to distinguish between normal MQ message server and transaction messages
  2. Send message half (half message)
  3. Send a successful implementation of local affairs by transactionListener
  4. EndTransaction execution method, if the semi-failure messages sent or local transaction fails to tell the server to delete a message and a half, half the message is sent successfully and performed locally successful Affairs , told news server into force half.

Send half the message flow, Client-side code here is almost over, let's look at how to deal with the end RocketMQ Server

RocketMQ Server

Server After receiving the message will be converted and whether to support the transaction message permissions check SomeDomainObject of little use for understanding the transaction message, here omitted the introduction of an offshoot of the distal. The following is TransactionalMessageBridge half message processing source category of

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));
    msgInner.setTopic(TransactionalMessageUtil.buildHalfTopic());
    msgInner.setQueueId(0);
    msgInner.setPropertiesString(MessageDecoder.messageProperties2String(msgInner.getProperties()));
    return msgInner;
}
复制代码

These two methods are mainly to do the following things:

public class Message implements Serializable {
    private static final long serialVersionUID = 8445773977080406428L;

    private String topic;
    private int flag;
    private Map<String, String> properties;
    private byte[] body;
    private String transactionId;
}
复制代码
  1. The topic of the message, queueId into the body of the message in its own map caches
  2. The topic of the message set to "RMQ_SYS_TRANS_OP_HALF_TOPIC", queueId is set to 0
  3. The message is written to disk persistence

You can see all the affairs of half the message will be placed in the same queue with a topic of which, by distinction topic, thus avoiding the semi message is to the consumer to consumer

Server semi-persistent message and then sends the results to our local application. To here Server-side processing of the message is over half, followed by a debut timed tasks.

Check back flow of regular tasks

RocketMQ Server

Timing task is called a thread TransactionalMessageService class, the following method is check the class

@Override
public void check(long transactionTimeout, int transactionCheckMax,
    AbstractTransactionalMessageCheckListener listener) {
                  ...
     if (!putBackHalfMsgQueue(msgExt, i)) {
        continue;
     }
       listener.resolveHalfMsg(msgExt);
   } 
									...
}
复制代码

check method is very long, omitted code is substantially half message (transaction messages such as exceeding 72 hours, it is counted as overdue) was filtered, leaving only half of eligible message back to its investigation.

One very interesting is putBackHalfMsgQueue method, because every time the semi-processed messages will change its properties from disk pulled memory (eg TRANSACTION_CHECK_TIMES, which is the key message of the transaction information is discarded), so check before sending back a message You need the message again put on a half disk. RocketMQ method adopted is based on the latest physical offset rewritten , rather than half the original message modification , in which the goal is RocketMQ storage design uses sequential write, if you go to modify the message, can not do high performance.

Here is resolveHalfMsg method, mainly open a thread and then send check messages.

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);
            }
        }
    });
}
复制代码

Local application (client)

The following is a method DefaultMQProducerImpl checkTransactionState is local application processing logic Recall message

@Override
public void checkTransactionState(final String addr, final MessageExt msg,
    final CheckTransactionStateRequestHeader header) {
    Runnable request = new Runnable() {
        ...
        @Override
        public void run() {
            ...
     TransactionListener transactionListener = getCheckListener();
            ...
     localTransactionState = transactionListener.checkLocalTransaction(message);
               ...
                 
      this.processTransactionState(
                    localTransactionState,
                    group,
                    exception);        
        }
      
        private void processTransactionState(
           ...
 DefaultMQProducerImpl.this.mQClientFactory.getMQClientAPIImpl().endTransactionOneway(brokerAddr, thisHeader, remark,
                    3000);
           ...
        }
    };
    this.checkExecutor.submit(request);
}
复制代码

After streamlining the code logic can clearly see

  • Open a thread to perform logic back investigation
  • checkLocalTransaction method to obtain the results of the implementation of transactionListener local transaction execution

RocketMQ Server

RocketMQ server after receiving the Client sent me Commit message

Information> Restore topic such as the original message body - - read half the message> and again written to disk as a common message -> semi messages before deletion

If it is before the Rollback message is a message delete half

This, calling the entire chain of RocketMQ transaction message is over

Think

1. Distributed Transaction equal Affairs news?

Both did not matter, transactional messaging only guarantee local affairs and MQ messaging atomic form a whole, and after delivery to the MQ server, whether the consumer can consume a certain success is not guaranteed.

What highlights 2. source design?

Through the discovery of the entire link-source learning to understand there are a lot of bright spots

  • Check back end server to send a message, the message processing client-side logic Recall, client-side submission commit / rollback messages are used for asynchronous, it can be said to have a place asynchronously asynchronous, asynchronous manner and retry ensures that even short network conditions are not good, it will not affect the overall logic in a distributed environment.
  • Introducing TransactionListener, truly on-off principle and Dependency Inversion Principle, oriented programming interface. Overall scalability done very well, users only need to write your own affairs Listener can do to send a message, very convenient
  • TransactionMQProducer through inheritance DefaultMQProducer greatly multiplexed message transmitted on logic associated

3. What deficiencies do source design?

RocketMQ as a highly successful messaging middleware, to find deficiencies is not so easy, I make a few remarks

  • sendMessageIntransaction other transaction related methods are divided DefaultMQProducer the inside, from the perspective of cohesive, this is sent with the message transaction related methods should be divided TransactionMQProducer.

  • All semi-topic messages are written on the topic for the message queue RMQ_SYS_TRANS_OP_HALF_TOPIC half, and each half message, there will be a link in the entire written many times, if a concurrent large and most of the message, then the message is transactional, reliability there will be a problem.

Guess you like

Origin juejin.im/post/5d2a6960f265da1bae39295e