Keeping Money Transfers Consistent: Local Transactions, 2PC, and Message Queues
To prevent data inconsistencies when transferring funds—like moving money from Alipay to Yu'ebao—the article explores local transactions, the limitations of two-phase commit in distributed systems, and how reliable message queues can achieve eventual consistency without sacrificing performance.
1 Local Transaction
When a transfer of 10,000 CNY from Alipay to Yu'ebao is performed, two SQL statements are needed: one to deduct the amount from the Alipay account table and another to add it to the Yu'ebao account table. Using a database transaction ensures both statements succeed or both roll back.
Begin transaction update A set amount = amount - 10000 where userId = 1; update B set amount = amount + 10000 where userId = 1; End transaction commit;
In Spring this can be achieved with a single @Transactional annotation.
2 Distributed Transaction – Two-Phase Commit (2PC)
When the involved tables reside on different database instances, a local transaction no longer works. A two‑phase commit protocol introduces a coordinator (TC) and multiple participants (Si). The protocol consists of a prepare phase where each participant writes a prepare record to its log and replies yes or no , followed by a commit or abort phase based on the collected votes.
TC: send <prepare> to all participants Participant: execute local transaction, write log, reply <yes>/<no> TC: if all <yes> then send <commit> else send <abort>
Although libraries such as Atomikos implement 2PC, the protocol incurs multiple network round‑trips and holds locks for a long time, making it unsuitable for high‑concurrency systems.
3 Using Message Queues to Avoid Distributed Transactions
Instead of a distributed transaction, the system can rely on a reliable message as a “ticket”. After Alipay deducts the amount, it records a message indicating that Yu'ebao should credit the same amount. The message is persisted before the business transaction commits.
3.1 Persisting the Ticket
Two approaches:
Coupled approach : The business update and the message insert share the same database and are wrapped in a single local transaction.
Decoupled approach : The business transaction requests the message service to store the message first; the transaction commits only after the service acknowledges successful storage.
Begin transaction update A set amount = amount - 10000 where userId = 1; insert into message(userId, amount, status) values (1, 10000, 1); End transaction commit;
After the transaction commits, the message is sent to the real‑time messaging system, which notifies Yu'ebao. Upon successful processing, Yu'ebao replies, and Alipay deletes the message.
3.2 Handling Duplicate Message Delivery
To avoid processing the same message twice, a message‑application status table (message_apply) records each consumed message ID. Before processing, the service checks this table; if the ID exists, the message is discarded.
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 End transaction commit;
This pattern, first described by eBay engineers in 2008, provides reliable eventual consistency without the performance penalties of 2PC.
References
Dan Pritchett, "Base: An Acid Alternative"
程立, "大规模SOA系统中的分布式事务处理"
"MySQL Two-Phase Commit"
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.
