mobile wallpaper 1mobile wallpaper 2mobile wallpaper 3mobile wallpaper 4
3660 字
10 分钟
分布式事务:2PC、Saga 与最终一致性
2024-10-10

单机事务靠锁和 WAL 就能保证 ACID——但当一个业务操作跨越多个数据库、多个微服务时,谁来保证”要么全成功,要么全回滚”?这就是分布式事务要解决的核心问题。在 事务与并发控制 中,讨论了单机事务的 ACID 保证与并发控制机制;当数据分布到多个节点上,这些机制就不再直接适用了。

分布式事务的本质困难在于:没有全局锁、没有共享内存、没有原子时钟——每个节点都可能独立失败,网络可能分区,消息可能延迟或重复。本章从 2PC 出发,逐步分析 3PC、Saga、TCC 和最终一致性方案,帮助你在强一致性与可用性之间做出合理的工程取舍。

前置知识#

Note

2PC 由 Jim Gray 在 1978 年提出,成为分布式事务标准方案,但阻塞问题困扰了工程师数十年。1987 年 Hector Garcia-Molina 提出的 Saga 模式在 2010 年代微服务浪潮中重新被发掘,成为主流方案。

一、分布式事务挑战#

1.1 从单机到分布式#

在单机数据库中,事务的原子性由 Undo Log 和 WAL 共同保证,隔离性由锁或 MVCC 实现——所有操作共享同一块内存和磁盘,协调者就是数据库引擎本身。但当业务操作跨越多个独立数据库时,情况发生了根本变化:

维度单机事务分布式事务
协调机制进程内调用,共享内存网络通信,消息传递
失败模型进程崩溃,磁盘故障节点崩溃、网络分区、消息丢失
原子性保证Undo Log + WAL需要分布式提交协议
隔离性保证锁 / MVCC全局锁代价极高,通常降级为最终一致性
性能开销微秒级锁获取毫秒到秒级网络往返

1.2 CAP 定理的约束#

数据复制 中讨论了 CAP 定理:在网络分区(P)发生时,系统只能在一致性(C)和可用性(A)之间二选一。分布式事务方案同样受此约束:

graph TB CAP["CAP 定理"] --> CP["CP 方案<br/>强一致性<br/>2PC / 3PC"] CAP --> AP["AP 方案<br/>高可用性<br/>Saga / 最终一致性"] CP --> |"阻塞风险高"| CP_RISK["协调者故障→全部阻塞"] AP --> |"一致性延迟"| AP_RISK["中间状态对外可见"] style CAP fill:#fff9c4,stroke:#f9a825 style CP fill:#ffcdd2,stroke:#c62828 style AP fill:#c8e6c9,stroke:#2e7d32
Important

没有任何分布式事务方案能同时做到强一致性、高可用性和高性能。2PC 追求强一致性但牺牲可用性,Saga 追求可用性但牺牲强一致性——选择方案的本质是在这三者之间做取舍。

二、2PC — 两阶段提交#

2.1 基本流程#

两阶段提交(Two-Phase Commit, 2PC)是最经典的分布式提交协议。它引入一个协调者(Coordinator)角色,所有参与事务的节点称为参与者(Participant)。协议分为两个阶段:

sequenceDiagram participant C as 协调者 Coordinator participant P1 as 参与者 1 participant P2 as 参与者 2 participant P3 as 参与者 3 Note over C,P3: 阶段一:Prepare(准备阶段) C->>P1: PREPARE C->>P2: PREPARE C->>P3: PREPARE P1-->>C: YES(已锁定资源) P2-->>C: YES(已锁定资源) P3-->>C: NO(无法提交) Note over C,P3: 阶段二:Commit/Abort(提交阶段) C->>P1: ABORT C->>P2: ABORT C->>P3: ABORT P1-->>C: ACK P2-->>C: ACK P3-->>C: ACK

阶段一:Prepare(准备阶段)

协调者向所有参与者发送 PREPARE 请求。每个参与者检查自身是否能够提交:

  • 如果可以提交,将操作写入本地日志并锁定资源,回复 YES
  • 如果无法提交,回复 NO

阶段二:Commit/Abort(提交阶段)

  • 如果所有参与者都回复 YES,协调者发送 COMMIT
  • 如果任何一个参与者回复 NO,或等待超时,协调者发送 ABORT

2.2 阻塞问题#

2PC 最大的问题是阻塞:协调者在发送 COMMIT 前崩溃时,已回复 YES 的参与者将永远等待,资源无法释放。

# 2PC 参与者的阻塞状态(简化示意)
class Participant:
def handle_prepare(self, txn_id):
"""阶段一:准备"""
if self.can_commit(txn_id):
self.lock_resources(txn_id) # 锁定资源!
self.write_prepare_log(txn_id) # 写入 Prepare 日志
return "YES"
return "NO"
def handle_commit(self, txn_id):
"""阶段二:提交"""
self.commit(txn_id)
self.release_locks(txn_id) # 只有提交/回滚后才能释放锁
self.write_commit_log(txn_id)
# 问题:如果协调者崩溃,参与者将永远停在"已锁定"状态
# 直到协调者恢复并重新发送决定
Warning

2PC 的阻塞问题是结构性的——参与者一旦回复 YES 就必须等待协调者的最终决定,在此期间它不能单方面提交或回滚,否则会违反原子性。这意味着协调者成为了单点故障:它崩溃时,所有已 Prepare 的事务都被阻塞。

2.3 XA 规范#

XA(eXtended Architecture)是 X/Open 组织定义的 2PC 工业标准,被主流数据库和中间件广泛支持:

// Java 中使用 XA 事务
XADataSource xaDs1 = createXADataSource("jdbc:mysql://db1:3306/order");
XADataSource xaDs2 = createXADataSource("jdbc:mysql://db2:3306/inventory");
XAResource xaRes1 = xaDs1.getXAConnection().getXAResource();
XAResource xaRes2 = xaDs2.getXAConnection().getXAResource();
TransactionManager tm = getTransactionManager();
tm.begin();
tm.getTransaction().enlistResource(xaRes1);
tm.getTransaction().enlistResource(xaRes2);
// 在两个数据源上执行操作... 事务管理器自动执行 2PC
tm.commit(); // 内部执行 Prepare → Commit

XA 规范定义了三个核心组件:

组件角色职责
AP(Application Program)应用程序定义事务边界,发起全局事务
TM(Transaction Manager)事务管理器协调者,管理 2PC 流程
RM(Resource Manager)资源管理器参与者,管理本地资源(数据库等)

XA 事务的状态转换如下:

stateDiagram-v2 [*] --> 活跃: TM.begin() 活跃 --> 已准备: 所有RM Prepare成功 已准备 --> 已提交: TM.commit() 已准备 --> 已回滚: TM.rollback() / RM Prepare失败 活跃 --> 已回滚: TM.rollback() 已提交 --> [*] 已回滚 --> [*] note right of 已准备: 阻塞点:等待TM决定

2.4 2PC 的优缺点#

优点缺点
实现原理简单,容易理解同步阻塞,资源锁定时间长
强一致性保证协调者单点故障
工业标准(XA)支持广泛性能随参与者数量线性下降
多数数据库原生支持网络分区时可能导致数据不一致

三、3PC — 三阶段提交#

3.1 基本流程#

三阶段提交(Three-Phase Commit, 3PC)是 2PC 的改进版本,通过增加一个**预提交(PreCommit)**阶段来减少阻塞:

sequenceDiagram participant C as 协调者 participant P1 as 参与者 1 participant P2 as 参与者 2 Note over C,P2: 阶段一:CanCommit(询问阶段) C->>P1: CanCommit? C->>P2: CanCommit? P1-->>C: Yes P2-->>C: Yes Note over C,P2: 阶段二:PreCommit(预提交阶段) C->>P1: PreCommit C->>P2: PreCommit P1-->>C: ACK P2-->>C: ACK Note over C,P2: 阶段三:DoCommit(提交阶段) C->>P1: DoCommit C->>P2: DoCommit P1-->>C: ACK P2-->>C: ACK

三个阶段的职责:

阶段名称职责是否锁定资源
阶段一CanCommit询问参与者是否”可能”提交不锁定
阶段二PreCommit通知参与者准备提交,锁定资源锁定
阶段三DoCommit执行实际提交释放锁

3.2 超时机制#

3PC 的关键改进是引入了超时机制:参与者在 PreCommit 阶段之后,如果等待协调者的 DoCommit 超时,可以自行决定提交。这是因为:

  • 如果参与者到达了 PreCommit 阶段,说明所有参与者在 CanCommit 阶段都回复了 Yes
  • 此时提交是”大概率正确”的选择
# 3PC 参与者的超时处理
class ThreePhaseParticipant:
def handle_can_commit(self, txn_id):
if self.can_commit(txn_id): return "YES" # 不锁定资源
return "NO"
def handle_pre_commit(self, txn_id):
self.lock_resources(txn_id)
self.write_precommit_log(txn_id)
return "ACK"
def handle_do_commit(self, txn_id):
self.commit(txn_id)
self.release_locks(txn_id)
def on_timeout(self, txn_id):
if self.state == "PRE_COMMITTED":
self.handle_do_commit(txn_id) # 超时自行提交
elif self.state == "INIT":
self.abort(txn_id) # 未到 PreCommit,自行回滚

3.3 3PC 的局限性#

Caution

3PC 并不能完美解决 2PC 的问题。在网络分区(而非单纯超时)的场景下,3PC 仍可能导致数据不一致:部分参与者因超时自行提交,另一部分因未收到 PreCommit 而回滚。3PC 假设了”网络分区不会同时与节点故障发生”——但这在现实中并不成立。因此,3PC 在实际系统中很少被采用。

3PC 与 2PC 的对比:

维度2PC3PC
阶段数23
阻塞风险高(协调者故障→全部阻塞)低(超时可自行决定)
网络分区安全性阻塞但不一致可能不一致
实际采用广泛(XA 规范)极少
网络往返次数2 轮3 轮

四、Saga 模式#

4.1 核心思想#

Saga 模式由 Hector Garcia-Molina 和 Kenneth Salem 在 1987 年提出,其核心思想是:将长事务拆分为多个本地短事务,每个本地事务都有对应的补偿操作。如果某个步骤失败,则逆序执行已完成步骤的补偿操作。

flowchart LR T1["T1: 创建订单"] --> T2["T2: 扣减库存"] T2 --> T3["T3: 扣减余额"] T3 --> T4["T4: 增加积分"] T4 --> DONE["完成"] T2 -.->|"补偿"| C1["C1: 取消订单"] T3 -.->|"补偿"| C2["C2: 恢复库存"] T4 -.->|"补偿"| C3["C3: 恢复余额"] style T1 fill:#c8e6c9,stroke:#2e7d32 style T2 fill:#c8e6c9,stroke:#2e7d32 style T3 fill:#c8e6c9,stroke:#2e7d32 style T4 fill:#c8e6c9,stroke:#2e7d32 style C1 fill:#ffcdd2,stroke:#c62828 style C2 fill:#ffcdd2,stroke:#c62828 style C3 fill:#ffcdd2,stroke:#c62828 style DONE fill:#a5d6a7,stroke:#1b5e20

Saga 的关键区别在于:它不保证事务的隔离性——中间状态对外可见。这是它与 2PC 的根本取舍:用隔离性换可用性。

4.2 编排式 vs 协同式#

Saga 有两种实现方式:

编排式(Orchestration):由一个中央协调器(Orchestrator)控制整个流程。

sequenceDiagram participant O as 编排器 participant OS as 订单服务 participant IS as 库存服务 participant PS as 支付服务 O->>OS: T1: 创建订单 OS-->>O: 成功 O->>IS: T2: 扣减库存 IS-->>O: 成功 O->>PS: T3: 扣减余额 PS-->>O: 失败! Note over O,PS: 开始补偿 O->>IS: C2: 恢复库存 IS-->>O: 补偿成功 O->>OS: C1: 取消订单 OS-->>O: 补偿成功
// 编排式 Saga 示例
public class OrderSagaOrchestrator {
public SagaDefinition<OrderState> saga() {
return step()
.invokeParticipant(this::createOrder)
.withCompensation(this::cancelOrder)
.step()
.invokeParticipant(this::deductInventory)
.withCompensation(this::restoreInventory)
.step()
.invokeParticipant(this::deductBalance)
.withCompensation(this::restoreBalance)
.build();
}
}

协同式(Choreography):没有中央协调器,各服务通过事件驱动协作。

// 协同式 Saga — 订单服务
@Transactional
public Order createOrder(CreateOrderRequest req) {
Order order = new Order(req);
order.setStatus("PENDING");
orderRepository.save(order);
eventBus.publish(new OrderCreatedEvent(order)); // 触发下游
return order;
}
@Transactional
@EventHandler
public void handlePaymentFailed(PaymentFailedEvent event) {
Order order = orderRepository.findById(event.getOrderId());
order.setStatus("CANCELLED"); // 补偿:取消订单
orderRepository.save(order);
eventBus.publish(new OrderCancelledEvent(order));
}
// 协同式 Saga — 库存服务
@Transactional
@EventHandler
public void handleOrderCreated(OrderCreatedEvent event) {
inventoryRepository.deduct(event.getProductId(), event.getQuantity());
eventBus.publish(new InventoryDeductedEvent(event));
}
@Transactional
@EventHandler
public void handleOrderCancelled(OrderCancelledEvent event) {
inventoryRepository.restore(event.getProductId(), event.getQuantity());
}

两种方式的对比:

维度编排式协同式
协调方式中央编排器控制流程事件驱动,服务间松耦合
可见性流程集中可见,易于监控流程分散,难以追踪全貌
耦合度编排器依赖所有服务服务间无直接依赖
扩展性新增步骤需修改编排器新增步骤只需订阅事件
适用场景复杂流程、需要集中监控简单流程、服务自治
典型框架Axon Framework、Seata自定义事件总线

4.3 补偿操作的设计原则#

补偿操作不是”回滚”——它是在业务层面撤销之前操作的效果。设计补偿操作时需要遵循以下原则:

原则 1:补偿操作必须是幂等的(网络重试可能导致多次执行)
原则 2:补偿操作本身不能失败(否则需人工介入)
原则 3:补偿是"语义撤销"而非"物理回滚"(反向转账 vs 删除记录)
原则 4:必须逆序补偿(先补偿最后完成的步骤)

五、TCC 模式#

5.1 Try-Confirm-Cancel#

TCC(Try-Confirm-Cancel)是业务层面的分布式事务方案,将每个操作分为三个阶段:

flowchart TB subgraph TCC["TCC 模式"] TRY["Try 阶段<br/>预留资源"] CONFIRM["Confirm 阶段<br/>确认提交"] CANCEL["Cancel 阶段<br/>释放预留"] TRY --> |"所有Try成功"| CONFIRM TRY --> |"任一Try失败"| CANCEL end subgraph Example["电商示例"] T_TRY["冻结库存 10 件<br/>冻结余额 ¥1000"] T_CONFIRM["扣减冻结库存<br/>扣减冻结余额"] T_CANCEL["释放冻结库存<br/>释放冻结余额"] T_TRY --> T_CONFIRM T_TRY --> T_CANCEL end style TRY fill:#bbdefb,stroke:#1565c0 style CONFIRM fill:#c8e6c9,stroke:#2e7d32 style CANCEL fill:#ffcdd2,stroke:#c62828

5.2 TCC 实现示例#

// TCC 模式实现示例(以库存服务为例)
public class InventoryTccService {
// Try 阶段:冻结库存
@Transactional
public boolean tryDeduct(String xid, String productId, int quantity) {
Inventory inv = inventoryRepository.findByProductId(productId);
if (inv.getTotal() - inv.getFrozen() < quantity) return false;
inv.setFrozen(inv.getFrozen() + quantity); // 冻结,而非扣减
tccBranchRepository.save(new TccBranch(xid, productId, quantity, "TRY"));
return true;
}
// Confirm 阶段:确认扣减
@Transactional
public boolean confirm(String xid) {
TccBranch branch = tccBranchRepository.findByXid(xid);
Inventory inv = inventoryRepository.findByProductId(branch.getProductId());
inv.setTotal(inv.getTotal() - branch.getQuantity());
inv.setFrozen(inv.getFrozen() - branch.getQuantity());
branch.setStatus("CONFIRMED");
return true;
}
// Cancel 阶段:释放冻结
@Transactional
public boolean cancel(String xid) {
TccBranch branch = tccBranchRepository.findByXid(xid);
if (branch.getStatus().equals("CANCELLED")) return true; // 幂等
Inventory inv = inventoryRepository.findByProductId(branch.getProductId());
inv.setFrozen(inv.getFrozen() - branch.getQuantity());
branch.setStatus("CANCELLED");
return true;
}
}

5.3 TCC 与 Saga 的对比#

维度TCCSaga
资源隔离Try 阶段冻结资源,隔离性好无资源冻结,隔离性差
一致性准实时一致性最终一致性
业务侵入高(需实现 Try/Confirm/Cancel)中(需实现补偿操作)
回滚方式Cancel 释放预留资源补偿操作语义撤销
性能Try 阶段锁定资源,有性能开销无锁定,性能较好
适用场景资金类、库存类(隔离性要求高)流程类、长事务(可用性要求高)
Note

TCC 的 Try 阶段”冻结资源”本质上是一种业务层面的锁——它不是数据库行锁,而是在业务数据中增加一个”冻结”字段。这使得 TCC 比 2PC 的数据库级锁更灵活,但也意味着每个服务都需要额外实现冻结/确认/取消的逻辑。

六、最终一致性#

6.1 核心思想#

最终一致性(Eventual Consistency)放弃了强一致性的要求,允许系统在短时间内处于不一致状态,但保证如果没有新的更新操作,系统最终会达到一致状态。这与 一致性与共识 中讨论的线性化形成鲜明对比——线性化要求所有操作看起来像发生在单一时间点上,而最终一致性只要求”最终”一致。

graph LR subgraph 强一致性["强一致性(2PC)"] S1["T1 写入"] --> S2["T2 读取<br/>立即看到T1"] end subgraph 最终一致性["最终一致性"] E1["T1 写入"] --> E2["T2 读取<br/>可能看到旧值"] E2 --> E3["T3 读取<br/>看到T1的值"] E3 --> E4["最终一致"] end style 强一致性 fill:#ffcdd2,stroke:#c62828 style 最终一致性 fill:#c8e6c9,stroke:#2e7d32

6.2 本地消息表#

本地消息表是最常用的最终一致性方案,其核心思想是:将消息与业务操作放在同一个本地事务中,保证业务操作和消息发送的原子性。

sequenceDiagram participant App as 应用服务 participant DB as 本地数据库 participant MQ as 消息队列 participant Svc as 下游服务 Note over App,Svc: 步骤 1:业务操作 + 写入消息表(本地事务) App->>DB: BEGIN App->>DB: INSERT orders (业务数据) App->>DB: INSERT outbox (消息数据, status=NEW) App->>DB: COMMIT Note over App,Svc: 步骤 2:异步发送消息 App->>DB: SELECT * FROM outbox WHERE status='NEW' App->>MQ: 发送消息 App->>DB: UPDATE outbox SET status='SENT' Note over App,Svc: 步骤 3:下游消费 MQ->>Svc: 投递消息 Svc->>Svc: 执行本地业务逻辑 Svc-->>MQ: ACK
-- 本地消息表(Outbox Pattern)实现
-- 1. 创建消息表
CREATE TABLE outbox (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
topic VARCHAR(100) NOT NULL, -- 消息主题
payload JSON NOT NULL, -- 消息内容
status ENUM('NEW', 'SENT', 'FAILED') DEFAULT 'NEW',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
retry_count INT DEFAULT 0,
INDEX idx_status_created (status, created_at)
);
-- 2. 业务操作 + 写入消息表(原子性保证)
BEGIN;
INSERT INTO orders (user_id, product_id, amount, status)
VALUES (1001, 2001, 99.00, 'CREATED');
INSERT INTO outbox (topic, payload)
VALUES ('order-created', '{"orderId": 10001, "userId": 1001, "amount": 99.00}');
COMMIT;
-- 3. 消息发送器(定时轮询)
-- 由后台线程定期执行
UPDATE outbox SET status = 'SENT'
WHERE id = ? AND status = 'NEW';
-- 4. 失败重试
UPDATE outbox
SET retry_count = retry_count + 1,
status = CASE WHEN retry_count >= 3 THEN 'FAILED' ELSE 'NEW' END
WHERE id = ? AND status = 'FAILED';

6.3 可靠消息最终一致性#

基于消息队列的可靠消息方案,通过半消息和事务回查保证消息不丢失:

// RocketMQ 事务消息示例
public class OrderService {
@Autowired private RocketMQTemplate rocketMQTemplate;
public void createOrder(CreateOrderRequest req) {
// 发送半消息:对消费者不可见,等待本地事务确认
rocketMQTemplate.sendMessageInTransaction(
"order-topic", MessageBuilder.withPayload(req).build(), req);
}
@Transactional
public void executeLocalTransaction(Message msg) {
orderRepository.save(new Order(parseMessage(msg)));
// 本地事务提交成功 → 消息对消费者可见
}
public RocketMQLocalTransactionState checkLocalTransaction(Message msg) {
// 事务回查:不确定本地事务状态时调用
Order order = orderRepository.findById(parseOrderId(msg));
return order != null ? COMMIT : ROLLBACK;
}
}

6.4 消费者的幂等性#

最终一致性方案中,消息可能被重复投递,消费者必须保证幂等性

// 幂等消费者:唯一消息 ID 去重
@Transactional
public void handleOrderCreated(OrderCreatedEvent event) {
String msgId = event.getMessageId();
if (idempotentRepo.existsByMessageId(msgId)) return; // 重复消息跳过
inventoryService.deduct(event.getProductId(), event.getQuantity());
idempotentRepo.save(new IdempotentRecord(msgId, "PROCESSED"));
}

幂等性实现的常见方式:

方式原理优点缺点
唯一消息 ID数据库唯一约束去重简单可靠需要额外表
状态机检查业务状态是否已变更无需额外存储状态设计需谨慎
乐观锁版本号控制并发更新天然幂等需业务字段支持
Redis 去重SET NX 原子操作高性能需处理 Redis 故障

七、方案对比#

7.1 核心维度对比#

维度2PC3PCSagaTCC最终一致性
一致性强一致强一致最终一致准实时一致最终一致
可用性低(阻塞)中(超时提交)
性能
业务侵入低(数据库层面)中(补偿操作)高(Try/Confirm/Cancel)中(幂等处理)
隔离性完全隔离完全隔离无隔离资源冻结隔离无隔离
实现复杂度
典型框架XA、Seata AT几乎不用Seata Saga、AxonSeata TCCRocketMQ、本地消息表

7.2 场景选型指南#

flowchart TB START["分布式事务选型"] --> Q1{"是否需要强一致性?"} Q1 --> |"是"| Q2{"参与者是否支持 XA?"} Q1 --> |"否"| Q3{"是否需要资源隔离?"} Q2 --> |"是"| XA["2PC / XA"] Q2 --> |"否"| Q4{"能否接受阻塞风险?"} Q4 --> |"能"| XA Q4 --> |"不能"| Q5{"能否改造业务接口?"} Q5 --> |"能"| TCC["TCC"] Q5 --> |"不能"| XA Q3 --> |"是"| TCC Q3 --> |"否"| Q6{"流程是否复杂?"} Q6 --> |"是(多步骤)"| SAGA_O["Saga(编排式)"] Q6 --> |"否(简单链路)"| Q7{"是否需要集中管控?"} Q7 --> |"是"| SAGA_O Q7 --> |"否"| SAGA_C["Saga(协同式)"] Q6 -.->|"也可选"| EC["最终一致性<br/>本地消息表/可靠消息"] style XA fill:#ffcdd2,stroke:#c62828 style TCC fill:#fff9c4,stroke:#f9a825 style SAGA_O fill:#c8e6c9,stroke:#2e7d32 style SAGA_C fill:#c8e6c9,stroke:#2e7d32 style EC fill:#bbdefb,stroke:#1565c0

7.3 性能对比#

以一个涉及 3 个服务的分布式事务为例,各方案的性能特征:

指标2PCSagaTCC最终一致性
同步等待时间2 × RTT × N1 × RTT × N2 × RTT × N1 × RTT
资源锁定时间整个事务期间无锁定Try→Confirm 期间无锁定
吞吐量最高
延迟
Important

性能对比中的 RTT(Round-Trip Time)是网络往返延迟。在跨机房部署时,RTT 可能从数据中心内的 0.5ms 增加到跨地域的 30-100ms,这使得 2PC 的多轮同步通信代价极高。这也是为什么跨地域场景几乎不使用 2PC 的原因。

八、总结#

8.1 核心观点回顾#

分布式事务的本质是在一致性、可用性、性能之间做取舍。没有万能方案,只有适合场景的选择:

  1. 2PC/XA 适合强一致性要求高、参与者少、网络稳定的场景(如同一数据中心的跨库事务)
  2. 3PC 理论上减少了阻塞,但网络分区下的不一致风险使其在实践中几乎不被采用
  3. Saga 适合长流程、多步骤的业务场景(如订单流程、旅行预订),用补偿操作替代回滚
  4. TCC 适合需要资源隔离的场景(如资金、库存),但业务侵入性高
  5. 最终一致性 适合高吞吐、可容忍短暂不一致的场景(如通知、日志),是微服务架构的主流选择

8.2 与前后章节的联系#

分布式事务不是孤立的话题,它与系列中多个章节紧密关联:

  • 事务与并发控制:分布式事务的原子性建立在单机事务的 ACID 保证之上——每个参与者首先是一个单机事务
  • 数据复制:复制引入的一致性问题(读写一致性、单调读)是分布式事务的”轻量版”——它们都在处理数据分布后的一致性挑战
  • 一致性与共识:2PC 的协调者故障问题可以通过共识算法(如 Raft)解决——让协调者本身成为一个容错的复制组

8.3 工程实践建议#

1. 优先避免分布式事务 — 合理划分服务,将强关联数据放同一服务
2. 选择合适的方案 — 不要为"强一致性"强行用 2PC,多数场景可接受最终一致性
3. 做好补偿和重试 — 所有操作必须幂等,补偿操作必须有重试机制
4. 可观测性 — 记录事务步骤状态,实现事务看板排查悬挂事务
5. 测试故障场景 — 模拟网络分区、节点崩溃,验证补偿与幂等正确性

分布式事务没有银弹。理解每种方案的原理、取舍和适用场景,才能在面对真实问题时做出正确的选择。

支持与分享

如果这篇文章对你有帮助,欢迎支持作者或分享给更多人

分布式事务:2PC、Saga 与最终一致性
https://blog.souloss.com/posts/database/distributed-transactions/
作者
Souloss
发布于
2024-10-10
许可协议
CC BY-NC-SA 4.0

部分信息可能已经过时