Principles and Implementation of Distributed Transactions
In a traditional monolithic architecture, ensuring data integrity usually relies on the ACID properties provided by relational databases. However, with the widespread adoption of microservices, a single business workflow often spans across multiple independent services and databases. Under such circumstances, conventional local transactions fall short, giving rise to the critical need for distributed transactions.
A distributed transaction refers to a logical unit of work coordinated across two or more network nodes in a distributed system. The core challenge lies in guaranteeing that—despite unreliable environments plagued by network partitions and node crashes—all participating nodes either commit the transaction entirely or roll it back completely, thereby preserving system-wide data consistency.
Governed by the CAP theorem (Consistency, Availability, and Partition tolerance), distributed systems cannot simultaneously achieve all three guarantees. Consequently, implementing distributed transactions typically involves making a strategic trade-off between strict consistency and eventual consistency.
Strong consistency solutions aim to emulate the atomicity of local transactions, ensuring that all nodes reach an agreement at the exact same moment.
Two-Phase Commit (2PC)
The 2PC protocol introduces a coordinator to manage all participants. Its workflow is divided into two distinct phases:
- Prepare Phase: The coordinator sends a pre-write log (WAL) request to all participants, asking whether they can commit. Participants execute the local transaction operations, write the results to logs, lock the necessary resources, and respond with either a "agree" or "abort" vote.
- Commit Phase: If every participant votes "agree", the coordinator broadcasts a
commitcommand. If even a single participant votes "abort" or times out, the coordinator issues arollbackcommand.
Drawbacks:
- Synchronous Blocking: Participants hold resource locks while waiting for instructions from the coordinator, which severely degrades system throughput.
- Single Point of Failure: If the coordinator crashes, the entire system can be left in an indeterminate state.
Three-Phase Commit (3PC)
3PC is an evolution of 2PC designed to reduce blocking risks by introducing a timeout mechanism and a Pre-Commit phase. It splits the commit phase of 2PC into two steps and embeds timeout handling into each stage. If a participant fails to receive instructions from the coordinator within a specified window, it automatically proceeds with a commit or rollback. Although this mitigates blocking, network partitions can still prevent it from fully guaranteeing strict consistency.
Eventual Consistency Approaches: TCC and Saga
In high-performance internet-scale scenarios, the performance penalties associated with strong consistency are often unacceptable. Therefore, the industry predominantly relies on eventual consistency models, which allow temporary inconsistencies across data nodes while guaranteeing convergence over time.
Try-Confirm-Cancel (TCC)
TCC is a business-level compensatory transaction mechanism that breaks a specific operation down into three explicit phases:
- Try: Attempts to execute the operation. It performs business checks and reserves necessary resources (e.g., freezing a specific amount in a user account).
- Confirm: Confirms the execution. It performs the actual business logic using the resources reserved during the Try phase (e.g., deducting the frozen funds).
- Cancel: Cancels the execution. It releases the resources reserved during the Try phase (e.g., unfreezing the funds).
Example Scenario: E-Commerce Order Placement
- Try: The order service creates an order (marked as pending payment), and the payment service freezes $100 in the user's account.
- Confirm: The payment service officially deducts the frozen $100.
- Cancel: If order creation fails subsequently, the payment service unfreezes and returns the $100 to the user.
Saga Pattern
The Saga pattern decomposes a long-running transaction into a sequence of local short transactions. Each local transaction updates data and triggers the next step in the chain. If any step fails, Saga executes a series of compensating transactions in reverse order to undo the impact of preceding actions.
- Forward Flow: $T_1 \rightarrow T_2 \rightarrow T_3 \dots \rightarrow T_n$
- Compensating Flow: If $T_3$ fails $\rightarrow$ execute $C_2 \rightarrow C_1$
Unlike TCC, Saga does not reserve resources beforehand; instead, it commits local transactions immediately. This means intermediate states may be exposed to other concurrent operations before compensation occurs (lacking isolation), requiring careful business design to manage potential side effects.
Message-Based Asynchronous Transactions
In asynchronous and decoupled architectures, leveraging a message queue (MQ) to achieve eventual consistency is a prevailing practice.
Reliable Message Eventual Consistency
The core philosophy revolves around utilizing the persistence and retry mechanisms of message queues. A typical workflow (such as RocketMQ transactional messaging) proceeds as follows:
- Send Half Message: The producer sends a preliminary, invisible message to the MQ broker.
- Execute Local Transaction: The producer performs its local database operations.
- Commit or Rollback Message: Depending on the outcome of the local transaction, the producer sends either a
commitorrollbackinstruction to the MQ. - Consumer Processing: The MQ delivers the message to the downstream consumer, which ensures safe execution via idempotency design.
If the producer crashes immediately after executing its local transaction, the MQ broker queries the producer via a status-check mechanism to determine the final state of the transaction, ensuring the message is either delivered or discarded safely.
Summary Comparison of Distributed Transaction Models
| Scheme | Consistency Level | Performance | Implementation Complexity | Typical Use Cases |
|---|---|---|---|---|
| 2PC | Strict Consistency | Low | Low | Internal systems with extremely high consistency requirements and low concurrency |
| TCC | Eventual Consistency | High | High | Core financial transactions and scenarios requiring resource reservation |
| Saga | Eventual Consistency | High | Medium | Long-running business processes and long service call chains |
| Reliable Messaging | Eventual Consistency | Very High | Medium | Asynchronous decoupling and notification-driven services with low real-time constraints |
Engineering Recommendations
When designing modern systems, choosing a distributed transaction strategy should follow these core principles:
- Avoid When Possible: Prioritize domain-driven design and architectural adjustments (such as service consolidation or bounded context refinement) to eliminate the need for distributed transactions altogether.
- Opt for Eventual Consistency: Unless faced with rare scenarios demanding absolute strict consistency, favor reliable messaging or Saga patterns to preserve high availability and horizontal scalability.
- Ensure Idempotency: Regardless of the chosen pattern, network retries and duplicate deliveries are inevitable. Downstream consumers must always implement idempotent processing to prevent data corruption caused by repeated execution.