How RocketMQ Transactional Messages Achieve High‑Performance Final Consistency
This article explains RocketMQ's transactional message mechanism, detailing its three‑phase workflow, half‑message handling, transaction checks, key implementation tips, and production‑grade best practices for achieving reliable eventual consistency in distributed systems.
RocketMQ transactional messages aim for eventual consistency rather than strong consistency by combining a two‑phase commit with a transaction status check mechanism, splitting a distributed transaction into two independent, locally ordered operations.
Core Concepts and Participants
Producer : sends transactional messages.
Broker : stores and forwards messages, handling half messages and transaction checks.
Consumer : ultimately receives the committed message.
Three‑Phase Process
1. Send Half Message (Prepare Phase)
Producer sends a half message to RMQ_SYS_TRANS_HALF_TOPIC, which is not visible to consumers.
Broker persists the half message and acknowledges success.
Purpose: ensure message durability even if the producer crashes, allowing later check‑based decisions.
2. Execute Local Transaction and Commit/Rollback
If the local transaction succeeds, the producer commits the message, and the broker makes the half message deliverable.
If the local transaction fails, the producer rolls back, and the broker deletes the half message.
The local transaction must run before committing or rolling back to maintain data consistency.
3. Transaction Status Check (Check Phase)
Addresses uncertainty when a producer crashes or network issues occur.
Broker periodically scans the half‑message queue and initiates a check request.
Producer implements TransactionListener.checkLocalTransaction to query the database using the message key and return COMMIT, ROLLBACK, or UNKNOW.
Broker acts on the returned status to commit or roll back the message.
Key Implementation Details and Production Practices
1. Importance of Message Key
Use a unique key (e.g., order ID) so the check method can locate the corresponding local transaction.
Message msg = new Message("OrderTopic", "TagA", orderId, body.getBytes());2. Transaction Listener Example
public class TransactionListenerImpl implements TransactionListener {
@Override
public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
try {
orderService.createOrder(msg.getKeys());
return LocalTransactionState.COMMIT_MESSAGE;
} catch (Exception e) {
return LocalTransactionState.ROLLBACK_MESSAGE;
}
}
@Override
public LocalTransactionState checkLocalTransaction(MessageExt msg) {
String orderId = msg.getKeys();
Order order = orderService.getOrder(orderId);
if (order == null) return LocalTransactionState.ROLLBACK_MESSAGE;
if (order.getStatus() == OrderStatus.SUCCESS) return LocalTransactionState.COMMIT_MESSAGE;
return LocalTransactionState.UNKNOW;
}
}3. Dual‑Queue Mechanism
Half‑message queue: RMQ_SYS_TRANS_HALF_TOPIC Operation queue: RMQ_SYS_TRANS_OP_HALF_TOPIC Broker compares both queues to decide which messages need checking.
4. Check Mechanism Tuning
Check interval: transactionCheckInterval (default 1 minute).
Maximum checks: transactionCheckMax (default 15).
Thread pool sizes: transactionCheckThreadPoolMinSize / MaxSize.
5. High Availability and Idempotency
Producer clustering ensures at least one instance can respond to checks.
Both local transaction and message operations must be idempotent to avoid duplicate processing.
6. Consumer Considerations
Design consumers to be idempotent, handling possible duplicate messages.
Accept that transactional messages become visible only after the check cycle, introducing latency.
7. Monitoring and Operations
Monitor half‑message queue buildup to detect abnormal checks or transaction execution.
Monitor operation queue latency to ensure timely commit/rollback.
Set alerts for half messages that remain unprocessed for too long.
Advantages
Eventual consistency : achieves cross‑system data consistency.
Asynchronous processing : avoids synchronous blocking, boosting performance.
High availability : check mechanism mitigates producer crashes.
Limitations
Business intrusion : producers must implement TransactionListener, coupling business logic with MQ.
Latency : final visibility time ≥ check interval + local transaction time.
One‑way dependency : suitable only for "local transaction success → message send success" scenarios.
Conclusion
RocketMQ transactional messages encapsulate a "message + local transaction" pattern using half messages, a second‑phase confirmation, and status checks, balancing high performance with eventual consistency. Mastering the three‑phase flow, especially the check mechanism, is essential for correct and reliable usage.
Ray's Galactic Tech
Practice together, never alone. We cover programming languages, development tools, learning methods, and pitfall notes. We simplify complex topics, guiding you from beginner to advanced. Weekly practical content—let's grow together!
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.
