# 分布式事务解决方案

## 什么是分布式事务

分布式事务是指在分布式系统中，多个服务或数据库之间的操作需要作为一个整体来保证原子性。要么所有操作都成功，要么所有操作都回滚。

## ACID 特性

传统单机事务具有 ACID 特性：

* **A (Atomicity)**：原子性，事务的所有操作要么全部完成，要么全部不完成
* **C (Consistency)**：一致性，事务执行前后，数据库状态保持一致
* **I (Isolation)**：隔离性，多个事务并发执行时互不干扰
* **D (Durability)**：持久性，事务一旦提交，结果永久保存

在分布式环境中，保证 ACID 变得非常困难，因为涉及多个数据库、多个服务。

## CAP 与 BASE

在分布式系统中，根据 CAP 定理，我们需要在一致性（C）、可用性（A）和分区容错性（P）之间做出权衡。

**BASE 理论**是对 CAP 中一致性和可用性权衡的结果：

* **Basically Available（基本可用）**：分布式系统在出现故障时，允许损失部分可用性
* **Soft State（软状态）**：系统状态可以在一段时间内不同步
* **Eventually Consistent（最终一致性）**：系统最终会达到一致状态

## 分布式事务解决方案

### 方案一：2PC（Two-Phase Commit）

两阶段提交协议，包含协调者（Coordinator）和参与者（Participants）两种角色。

#### 第一阶段：准备阶段

1. 协调者向所有参与者发送 prepare 请求
2. 参与者执行事务操作，但不提交
3. 参与者向协调者返回成功或失败

#### 第二阶段：提交/回滚阶段

* **如果所有参与者都返回成功**：
  1. 协调者向所有参与者发送 commit 请求
  2. 参与者提交事务并释放锁
  3. 参与者向协调者返回提交结果
* **如果任一参与者返回失败**：
  1. 协调者向所有参与者发送 rollback 请求
  2. 参与者回滚事务并释放锁
  3. 参与者向协调者返回回滚结果

#### 优点与缺点

**优点**：

* 强一致性保证
* 实现相对简单

**缺点**：

* 同步阻塞：所有参与者在等待期间都持有资源锁
* 单点故障：协调者故障会导致整个系统阻塞
* 数据不一致：第二阶段网络故障可能导致数据不一致
* 性能差：两次网络往返，延迟较高

#### 实现示例

```java
// 使用 Atomikos 实现 2PC
@Bean
public JtaTransactionManager transactionManager() {
    UserTransactionManager userTransactionManager = new UserTransactionManager();
    UserTransactionImp userTransaction = new UserTransactionImp();
    return new JtaTransactionManager(userTransactionManager, userTransaction);
}
```

### 方案二：3PC（Three-Phase Commit）

三阶段提交是对 2PC 的改进，增加了预提交阶段来减少阻塞。

#### 三个阶段

1. **CanCommit（询问阶段）**：协调者询问参与者是否可以执行事务
2. **PreCommit（预提交阶段）**：
   * 如果所有参与者都同意，协调者发送 PreCommit 请求
   * 参与者执行事务但不提交，进入 PREPARED 状态
   * 参与者回复 Ack
3. **DoCommit（执行阶段）**：
   * 协调者收到所有 Ack 后发送 DoCommit 请求
   * 参与者提交事务

#### 与 2PC 的区别

* 增加了超时机制：参与者超时后自动提交或回滚
* 减少阻塞范围：在 PreCommit 阶段失败时，参与者可以继续执行

### 方案三：TCC（Try-Confirm-Cancel）

TCC 是业务层面的两阶段提交，需要业务实现三个操作：

* **Try**：资源的检测和预留
* **Confirm**：执行真正的业务操作
* **Cancel**：取消 Try 阶段的操作

#### 执行流程

1. **第一阶段（Try）**：
   * 调用所有参与者的 Try 方法
   * 如果所有 Try 都成功，进入 Confirm 阶段
   * 如果任一 Try 失败，进入 Cancel 阶段
2. **第二阶段**：
   * **Confirm 阶段**：调用所有参与者的 Confirm 方法
   * **Cancel 阶段**：调用所有参与者的 Cancel 方法

#### 示例

```java
// 转账业务 TCC 实现
public class TransferTCCService {
    
    // Try：检查账户余额并冻结
    @TccTry
    public boolean tryTransfer(TransferContext ctx) {
        accountService.freeze(ctx.getFromAccount(), ctx.getAmount());
        return true;
    }
    
    // Confirm：执行转账
    @TccConfirm
    public void confirmTransfer(TransferContext ctx) {
        accountService.deduct(ctx.getFromAccount(), ctx.getAmount());
        accountService.add(ctx.getToAccount(), ctx.getAmount());
    }
    
    // Cancel：解冻余额
    @TccCancel
    public void cancelTransfer(TransferContext ctx) {
        accountService.unfreeze(ctx.getFromAccount(), ctx.getAmount());
    }
}
```

#### 优点与缺点

**优点**：

* 性能优于 2PC/3PC
* 数据最终一致性
* 适用于跨服务调用

**缺点**：

* 业务侵入性强，需要实现三个方法
* 空回滚、幂等性、悬挂等问题需要处理
* 实现复杂度高

### 方案四：本地消息表

本地消息表是通过在业务数据库中增加消息表来保证最终一致性。

#### 实现原理

1. **业务操作和消息记录在同一个本地事务中完成**
2. **后台任务不断扫描消息表，发送到 MQ**
3. **下游服务消费消息并执行**
4. **完成后通知上游删除消息**

#### 示例

```sql
-- 业务表和消息表在同一数据库
BEGIN TRANSACTION;
    -- 执行业务操作
    UPDATE account SET balance = balance - 100 WHERE id = 1;
    -- 插入消息表
    INSERT INTO message_table (id, topic, data, status)
    VALUES (uuid(), 'transfer', '{"to": 2, "amount": 100}', 'pending');
COMMIT;
```

#### 优点与缺点

**优点**：

* 实现简单，不需要复杂的框架
* 数据一致性好
* 性能较好

**缺点**：

* 消息表和业务数据在同一数据库
* 消息发送失败需要重试机制
* 下游服务需要实现幂等性

### 方案五：MQ 事务消息

一些消息队列（如 RocketMQ）支持事务消息。

#### 执行流程

1. **生产者发送 half 消息（半消息，对消费者不可见）**
2. **执行本地事务**
3. **根据本地事务结果提交或回滚消息**：
   * 成功：Commit，消息对消费者可见
   * 失败：Rollback，消息被丢弃
4. **如果生产者故障**：MQ 会回调生产者检查本地事务状态

#### 示例

```java
// RocketMQ 事务消息
TransactionMQProducer producer = new TransactionMQProducer("producer_group");
producer.setTransactionListener(new TransactionListener() {
    @Override
    public LocalTransactionState executeLocalTransaction(Message msg, Object arg) {
        // 执行本地事务
        try {
            localTransaction.execute();
            return LocalTransactionState.COMMIT_MESSAGE;
        } catch (Exception e) {
            return LocalTransactionState.ROLLBACK_MESSAGE;
        }
    }
    
    @Override
    public LocalTransactionState checkLocalTransaction(MessageExt msg) {
        // 检查本地事务状态
        return LocalTransactionState.COMMIT_MESSAGE;
    }
});
```

## 方案选择指南

| 场景     | 推荐方案         | 理由        |
| ------ | ------------ | --------- |
| 跨银行转账  | 2PC/3PC      | 强一致性要求    |
| 电商订单   | TCC          | 高性能，最终一致性 |
| 内部服务调用 | 本地消息表/MQ事务消息 | 实现简单，性能好  |
| 日志记录   | 最大努力通知       | 异步，不阻塞主流程 |

## 总结

分布式事务没有银弹解决方案。选择方案时需要综合考虑：

* 一致性要求（强一致 vs 最终一致）
* 性能要求
* 实现复杂度
* 对业务代码的侵入性
* 基础设施支持

在实践中，TCC 和本地消息表是最常用的两种方案。


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://qiangrens-organization.gitbook.io/qkd90/8-fen-bu-shi-li-lun/fen-bu-shi-shi-wu-jie-jue-fang-an.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
