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.

Ray's Galactic Tech
Ray's Galactic Tech
Ray's Galactic Tech
How RocketMQ Transactional Messages Achieve High‑Performance Final Consistency

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.

JavaRocketMQTransactional MessagingEventual Consistency
Ray's Galactic Tech
Written by

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!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.