Ensuring Data Consistency: From Local Transactions to Distributed 2PC and Message Queues
This article explores how to maintain data consistency across services by using local transactions, two‑phase commit distributed transactions, and reliable message‑queue patterns, illustrating each method with an Alipay‑to‑YuEBao transfer example and discussing their trade‑offs.
1 Local Transaction
Using an Alipay to YuEBao transfer example, two tables are involved: update A set amount=amount-10000 where userId=1; and update B set amount=amount+10000 where userId=1;. A local transaction can wrap these statements with BEGIN TRANSACTION ... COMMIT to guarantee atomicity when both tables reside in the same database instance.
In Spring, the @Transactional(rollbackFor=Exception.class) annotation provides the same guarantee.
When the tables are on different physical nodes, local transactions no longer work, prompting the need for distributed transactions.
2 Distributed Transaction – Two‑Phase Commit (2PC)
2PC involves a coordinator (TC) and multiple participants (Si). The coordinator first logs a prepare record, then asks each participant to prepare. Participants execute local work but do not commit, responding with yes or no. If all reply yes, the coordinator sends commit; otherwise it sends abort. Logging on both sides enables recovery after failures.
Implementation can use Java libraries such as Atomikos, but 2PC suffers from high latency and long lock times, making it unsuitable for high‑concurrency systems.
3 Using Message Queues to Avoid Distributed Transactions
Instead of a single atomic transaction, the system can issue a reliable message (a “ticket”) after deducting money from Alipay. The message instructs YuEBao to credit the amount, achieving eventual consistency.
3.1 Reliable Message Persistence
Two approaches:
3.1.1 Coupled Business and Message
The same transaction that updates the account also inserts a row into a message table. If the transaction commits, both the debit and the message are guaranteed to be stored.
BEGIN TRANSACTION; UPDATE A SET amount=amount-10000 WHERE userId=1; INSERT INTO message(userId, amount, status) VALUES (1, 10000, 1); COMMIT;
After commit, a real‑time messaging service delivers the message to YuEBao, which replies upon successful processing; the sender then deletes the message.
3.1.2 Decoupled Business and Message
The business logic requests the messaging service to reserve a message before committing. Only after the transaction succeeds does it confirm the send; if the transaction rolls back, the message is cancelled. A periodic reconciliation checks for unconfirmed messages.
Pros: reduced coupling; Cons: extra round‑trips and need for status‑check APIs.
3.2 Handling Duplicate Message Delivery
To prevent double credit, the consumer maintains a message_apply table recording processed message IDs. Before applying a message, it checks this table; if the ID exists, the message is ignored.
FOR EACH msg IN queue BEGIN TRANSACTION; SELECT COUNT(*) AS cnt FROM message_apply WHERE msg_id=msg.msg_id; IF cnt = 0 THEN UPDATE B SET amount=amount+10000 WHERE userId=1; INSERT INTO message_apply(msg_id) VALUES (msg.msg_id); END IF; COMMIT;
This technique was first proposed by eBay engineers in 2008.
References
Dan Pritchett, “Base: An ACID Alternative”
程立, “大规模SOA系统中的分布式事务处理”
《MySQL两阶段提交》
Signed-in readers can open the original source through BestHub's protected redirect.
This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactand we will review it promptly.
21CTO
21CTO (21CTO.com) offers developers community, training, and services, making it your go‑to learning and service platform.
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.
