The background,

In microservices architecture, we often use asynchronous means to improve system throughput and decouple upstream and downstream, and the most common way to build asynchronous architecture is to use message queue (MQ). How can asynchronous architecture achieve data consistency? This article focuses on using RocketMQ transaction messages to address consistency issues.

RocketMQ is alibaba’s open source distributed messaging middleware, which has become a top project of Apache. After repeated trials of massive messages on Tmall Double 11, it has the characteristics of high performance, low latency and high reliability

PS: How to ensure consistency in synchronization scenarios? See the article “How to do distributed Transactions in Spring Cloud Synchronization Scenarios? Try Seata.”

 

Second, MQ selection

You can see that RocketMQ is superior to its competitors in terms of business processing and supports transactional messaging natively

PS: What if the business system uses other MQ products but needs transaction messages? Learning principles to develop their own implementation!

 

What is a transaction message

For example, the following scenario: Generate order record -> MQ -> Add credits

Should we create the order record first or send the MQ message first?

  1. Send the MQ message first: this is obviously a no-no, because there is no way to retrieve the message if the message is sent successfully and the order creation fails

  2. Create the order record first: throw an exception if the MQ message fails to be sent after the order is successfully created, because the order data can be rolled back because both operations are in a local transaction

The above way two seems to be no problem, but the network is unreliable! If the MQ response is not received for network reasons, a rollback is performed in the face of uncertain results; However, the MQ server did receive the message, but the response back to the client was lost! So transaction messages are used to ensure atomicity between local transactions and MQ message sending!

 

RocketMQ Transaction message principle

The main logic is divided into two flows:

  • Transaction message sending and submission:
    1. sendHalf the message
    2. MQ serverThe response message writes the result
    3. Execute according to the sent resultLocal transactionsIf the write fails, then the half message to the businessinvisible, local logic is not executed)
    4. Execute based on local transaction stateCommitorRollbackThe Commit operation generates the message index, the message to consumervisible)
  • To check the process:
    1. For a long time noCommit/RollbackTransaction message (pendingStatus message), sent once from the serverBack to check
    2. ProducerAfter receiving the check back message, check corresponding to the check back messageLocal transaction status
    3. According to the local transaction status, reCommitorRollback

Logical sequence diagram

 

Five, asynchronous architecture consistency implementation ideas

As you can see from the above principle, transaction messages only guarantee the atomicity of the local transaction and MQ message delivery as a whole. Once delivered to the MQ server, there is no guarantee that the consumer will be able to consume successfully! If the consumer fails to consume, it is recommended to record the exception information and handle it manually. It is not recommended to roll back the data of the upstream service (because they are decoupled and too complex). We can use two features of MQ, retry and dead-letter queue, to assist the consumer:

  1. A certain number of times after a consumption failureretry
  2. If the retry fails, the message is thrown inDead-letter queue
  3. Another thread listens for consumptionDead-letter queueMessages, log and alert!

The consumer needs to implement idempotency because of retry

 

Sample distributed transaction scenario

Use the scenario just mentioned: Generate order records -> MQ -> increase credits; Let’s talk briefly about how to do this in Spring Cloud. Please download the demo for details. Apache RocketMQ Message Queue Deployment and Visual Interface Installation

6.1. Introduce dependencies

Use the Spring-Cloud-Stream framework to access RocketMQ

Spring Cloud Stream is a message-driven framework for decoupling applications from MQ message queues through abstract definitions. It currently supports RabbitMQ, Kafka, and RocketMQ

 

6.2. Enable transaction messages

Message producers need to add transactional: True to enable transaction messages

 

6.3. The order service sends the half message

The half message sent here is invisible to the consumer because transaction messaging is enabled

 

6.4. The order service listens for the half message

Using the @ RocketMQTransactionListener annotations to monitor messages, and implement RocketMQLocalTransactionListener interface, this interface has two methods

  • ExecuteLocalTransaction: Used to commit local transactions
  • CheckLocalTransaction: Used for transaction backcheck

If the transaction message fails to commit, it takes about 1 minute for the transaction callback method to be invoked

 

6.5. Points service consumption message

Note: Since there are retries, the real business here needs to implement idempotency itself

 

6.6. Consumption dead letter queue warning

Monitors and consumes messages in the dead-letter queue, logs errors, and notifies o&M personnel

 

6.7. Test cases

Demo provides three interfaces to test different scenarios:

  • Transaction is successful

    http://localhost:11002/success

    The process is as follows:

    1. Order created successfully
    2. The transaction message was committed successfully
    3. Consumption message added credits successfully
  • The order was created successfully but failed to commit the transaction message

    http://localhost:11002/produceError

    The process is as follows:

    1. Order created successfully
    2. Failed to commit transaction message
    3. The transaction rollback succeeds (wait about 1 minute)
    4. The transaction message was committed successfully
    5. Consumption message added credits successfully
  • Consumption message failure

    http://localhost:11002/consumeError

    The process is as follows:

    1. Order created successfully
    2. The transaction message was committed successfully
    3. Failed to add credits to consume message
    4. Failed to retry consumption message
    5. Succeeded in entering the dead letter queue
    6. The message consuming the dead-letter queue succeeded
    7. Logs are logged and alarms are generated successfully

 

7. Demo download address

Gitee.com/zlt2000/mic…

 

Recommended reading

  • Log troubleshooting difficulty? Distributed log link tracing to help you
  • Zuul integrates Sentinel’s latest gateway flow control components
  • Ali Registry Nacos production deployment solution
  • Spring Boot custom configuration items implement automatic prompts in the IDE
  • How to do Spring Cloud Zuul dynamic routing? Integrating the Nacos implementation is simple
  • How can Spring Cloud developers resolve service conflicts and instance hopping?
  • How to do distributed transactions in Spring Cloud synchronization scenarios? Try Seata

 

Scan code attention has surprise!