Ensuring Distributed Transaction Consistency in High‑Concurrency Flash‑Sale Systems with Transactional Messages and RocketMQ
This article explains how to achieve strong consistency for high‑traffic flash‑sale (seckill) scenarios by using distributed transaction principles, flexible transaction models, and Alibaba Cloud RocketMQ transactional messages, providing detailed architectural guidance, implementation steps, code examples, and operational best practices.
Preface
Flash‑sale and seckill are common e‑commerce patterns that generate massive concurrent traffic, challenging system performance and stability. Similar high‑concurrency scenarios exist in ticketing, online education enrollment, and large‑scale events.
To address these challenges, the industry adopts techniques such as static‑dynamic separation, timed release, asynchronous processing, token queues, multi‑level caching, cheat detection, traffic protection, and full‑link stress testing.
This article focuses on guaranteeing consistency for flash‑sale business by building a high‑performance, highly‑available distributed consistency mechanism based on transactional messages.
Transaction Consistency Principles Review
A transaction is an indivisible unit of work that must either fully succeed or fully fail, characterized by the ACID properties: Atomicity, Consistency, Isolation, Durability.
Dirty read: Transaction A reads uncommitted data from Transaction B.
Non‑repeatable read: Two reads of the same data within a transaction return different results.
Phantom read: Two range queries within a transaction return different row counts.
Higher isolation levels reduce these anomalies but increase performance cost. Common isolation levels:
READ_UNCOMMITTED – allows all anomalies.
READ_COMMITTED – prevents dirty reads.
REPEATABLE_READ – prevents dirty and non‑repeatable reads.
SERIALIZABLE – prevents all three anomalies but has the lowest throughput.
Relational databases support ACID only for single‑node transactions; distributed transactions require additional mechanisms.
Distributed Transactions in Flash‑Sale Business
Three typical scenarios that generate distributed transactions:
One transaction accesses two different databases.
One transaction spans multiple data shards managed by sharding middleware.
One transaction invokes multiple micro‑services, each with its own data store.
In a flash‑sale flow, the order‑creation stage may involve three micro‑services (inventory, order, cart), and the payment‑success stage may involve four sub‑transactions (order status, inventory deduction, shipping notification, points addition). Ensuring consistency across these services is critical.
Implementation of Distributed Transactions
Traditional Distributed Transactions
Traditional XA two‑phase commit provides strong consistency but suffers from performance bottlenecks, single‑point‑of‑failure risk, and data‑inconsistency when the coordinator crashes after committing.
Flexible (BASE) Transactions
Flexible transactions relax strict isolation, allowing intermediate inconsistent states while guaranteeing eventual consistency. They achieve higher concurrency and better fault tolerance, making them suitable for flash‑sale workloads.
Traditional Distributed Transaction
Flexible Transaction
Business Refactor
No
Yes
Consistency
Strong
Eventual
Rollback Support
Yes
Implement fallback interface
Isolation
Supported
Relaxed or lock interface
Concurrent Performance
Low
High
Suitable Scenario
Low‑concurrency, short‑lived transactions
High‑concurrency, long‑lived transactions
Among flexible transaction models (TCC, Saga, transactional messages, max‑effort notification), this article concentrates on transactional messages.
Transactional Message Principle Analysis
Flash‑Sale Scenario Decomposition
The critical stages are order creation (inventory lock → order record) and payment success (order status update, inventory deduction, shipping notification, points addition). Each stage must be atomic across services.
Using an asynchronous message queue, the first participant sends a "half‑message" before executing its local transaction. After the local transaction succeeds, it sends a confirmation message; otherwise, the half‑message is discarded. The message queue stores the half‑message and delivers it only after receiving the confirmation.
If the participant crashes after the local commit, the queue performs a back‑check: it queries the participant for the transaction status and decides whether to deliver or discard the half‑message.
Ensuring Remote Transaction Success
Message delivery may fail due to network issues or remote consumer crashes. A retry mechanism is required: the queue repeatedly attempts delivery until the remote consumer acknowledges successful processing.
Full Process Diagram
(Diagram omitted – original shows half‑message → local commit → confirm → delivery → remote execution → ack.)
Transactional Message Practice
RocketMQ Message Queue
RocketMQ is a low‑latency, high‑throughput, highly‑available distributed messaging middleware provided by Alibaba Cloud. It fully supports transactional messages, including half‑message, confirm message, back‑check, and retry mechanisms.
Creating Resources
Steps include creating a RocketMQ instance, a Topic (set to "transactional" type), and two Group IDs—one for the local transaction participant and one for the remote participant.
Local Transaction Participant Code
Java example shows how to initialize a TransactionProducer , configure Group ID, AccessKey, SecretKey, and NameServer address, and start the producer.
Properties properties = new Properties();
properties.put(PropertyKeyConst.GROUP_ID, "XXX");
properties.put(PropertyKeyConst.AccessKey, "XXX");
properties.put(PropertyKeyConst.SecretKey, "XXX");
properties.put(PropertyKeyConst.NAMESRV_ADDR, "XXX");
TransactionProducer producer = ONSFactory.createTransactionProducer(properties, new LocalTransactionCheckerImpl());
producer.start();A LocalTransactionCheckerImpl implements LocalTransactionChecker to answer the queue’s back‑check by inspecting business state.
public class LocalTransactionCheckerImpl implements LocalTransactionChecker {
private static final Logger LOGGER = LoggerFactory.getLogger(LocalTransactionCheckerImpl.class);
private static final BusinessService businessService = new BusinessService();
@Override
public TransactionStatus check(Message msg) {
String transactionKey = msg.getKey();
try {
boolean isCommit = businessService.checkBusinessService(transactionKey);
return isCommit ? TransactionStatus.CommitTransaction : TransactionStatus.RollbackTransaction;
} catch (Exception e) {
LOGGER.error("Transaction Key:{}", transactionKey, e);
return TransactionStatus.Unknow;
}
}
}The producer sends a half‑message together with a lambda that executes the local transaction and returns the appropriate TransactionStatus :
Message message = new Message(TOPIC, null, transactionKey, body.getBytes());
SendResult result = producer.send(message, (msg, arg) -> {
try {
boolean ok = businessService.execBusinessService(transactionKey);
return ok ? TransactionStatus.CommitTransaction : TransactionStatus.RollbackTransaction;
} catch (Exception e) {
LOGGER.error("Transaction Key:{}", transactionKey, e);
return TransactionStatus.RollbackTransaction;
}
}, null);Remote Transaction Participant Code
The remote consumer subscribes to the same Topic with a different Group ID, processes the message, and returns Action.CommitMessage to acknowledge successful handling.
Consumer consumer = ONSFactory.createConsumer(props);
consumer.subscribe(TOPIC, "*", (msg, context) -> {
LOGGER.info("Receive: {}", msg);
businessService.doBusiness(msg);
return Action.CommitMessage;
});
consumer.start();Transaction Rollback Considerations
When a remote transaction fails due to business errors (e.g., insufficient inventory, invalid address), the system must trigger a compensating transaction to revert the local changes and possibly initiate refunds. This compensating flow is itself a distributed transaction and can be implemented with a separate Topic.
Other Considerations
Message Idempotency
RocketMQ guarantees no loss but may deliver duplicates. Consumers must implement idempotent handling using a unique business key (the global transaction ID) to avoid double processing.
Daily Reconciliation
Because flexible transactions allow an intermediate state, periodic reconciliation is required to detect and resolve stuck or failed transactions (e.g., messages that exceeded retry limits, business exceptions, idempotency failures, or infrastructure outages).
Conclusion
Transactional messages provide an elegant way to achieve distributed consistency for high‑concurrency flash‑sale scenarios. Leveraging Alibaba Cloud RocketMQ’s high performance and reliability, developers can build robust, eventually consistent systems. However, adopting this model requires careful business analysis, code changes, and operational safeguards such as daily reconciliation.
High Availability Architecture
Official account for High Availability Architecture.
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.