Ensuring Distributed Transaction Consistency with RocketMQ Transactional Messages
This article explains how to achieve eventual consistency for cross‑service transactions in a distributed environment by using RocketMQ's transactional messages, detailing the underlying principles, message flow, implementation steps for producers and consumers, and important usage limitations.
In distributed systems, cross‑service transactions such as deducting money in an account service (Service A) and extending a subscription in a billing service (Service B) require data consistency; this article shows how RocketMQ's transactional messages can guarantee eventual consistency.
RocketMQ, an open‑source distributed message queue originally developed by Alibaba and now under Apache, supports transactional messages that ensure atomicity between a local transaction and message sending. The key concepts include Half (Prepare) Message and Message Status Check.
Half (Prepare) Message: After the producer sends a message, the broker marks it as Half Message until it receives a second ACK from the producer.
Message Status Check: If the broker does not receive the second ACK within the default 60 seconds, it actively queries the producer for the transaction status (commit or rollback).
The detailed flow is:
Producer sends a message to the broker.
Broker persists the message and returns the first ACK; the message remains a Half Message and is not delivered to consumers.
Producer executes the local transaction.
Based on the transaction result, the producer sends a second ACK (commit or rollback). The broker either marks the half‑message as deliverable or deletes it.
If network issues or application restarts prevent the second ACK, the broker initiates a Message Status Check.
Producer responds to the check with the appropriate transaction result.
The broker then decides to deliver or discard the message accordingly.
Implementation example:
Producer: Implement the TransactionListener interface, execute local transaction logic in executeLocalTransaction , and return the transaction state in checkLocalTransaction . Use TransactionMQProducer to create and send transactional messages.
Consumer: After receiving a message, process the local transaction and return CONSUME_SUCCESS on success or RECONSUME_LATER on failure; the broker will retry later. Consumers must ensure idempotency because messages may be delivered multiple times.
Usage limitations include:
Transactional messages do not support delay or batch operations.
By default, each half‑message is checked up to 15 times; this can be changed via the transactionCheckMax broker parameter or by overriding AbstractTransactionCheckListener .
The transactionMsgTimeout parameter controls the interval between status checks.
Consumers must handle possible multiple deliveries with proper idempotent logic.
RocketMQ defines three transaction states: LocalTransactionState.UNKNOW (intermediate, requires later confirmation), LocalTransactionState.COMMIT_MESSAGE (message can be delivered), and LocalTransactionState.ROLLBACK_MESSAGE (message is discarded).
For further details, refer to the official RocketMQ transaction design page ( http://rocketmq.apache.org/rocketmq/the-design-of-transactional-message ) and the official transaction example ( https://rocketmq.apache.org/docs/transaction-example ).
Architect's Tech Stack
Java backend, microservices, distributed systems, containerized programming, and more.
How this landed with the community
Was this worth your time?
0 Comments
Thoughtful readers leave field notes, pushback, and hard-won operational detail here.