01. 什么是分布式事务?
在单体系统中,一次业务操作通常只涉及一个数据库节点,本地事务即可通过 ACID(原子性、一致性、隔离性、持久性)确保数据正确性。但随着系统拆分为微服务、数据分片、读写分离、多数据库架构等分布式模式,同一个业务操作往往涉及多个节点的资源操作。这时,本地事务不再能够保证全局一致性,就需要“分布式事务”。
分布式事务(Distributed Transaction)是指事务的参与者、资源、资源管理器与事务协调器分布在不同节点上,但整体仍需保证事务的原子性与一致性。
典型例子:跨银行转账
假设账户 A 在银行 X,账户 B 在银行 Y,需要完成转账:
银行 X 扣款 A – 100
银行 Y 加款 B + 100
两个操作不在同一数据库中,不能使用单节点本地事务保证一致性。一旦任意一方成功而另一方失败,数据就不一致。
为解决这种问题,就需要分布式事务协议。
02. 分布式事务的主要模型
常见的分布式模型有二阶段提交(2PC/XA)、三阶段提交(3PC)和SAGA。
2PC(二阶段提交,XA)
- 传统、强一致性
- 有协调器
- 涉及 Prepare → Commit 两个阶段
- 常用于数据库层面的强一致事务
3PC(三阶段提交)
- 2PC 的改进版
- 加入超时机制与预提交
- 理论价值大于工程价值(几乎没有实际系统采用)
SAGA(长事务 / 补偿事务)
- 无锁、最终一致
- 常用于微服务
- 每一步强调“可补偿操作”
- 分为 编排式 和 协同式 两种模式
03. 二阶段提交(2PC/XA)
2PC 是最典型的强一致性分布式事务协议,它由协调器(Coordinator)统一控制所有参与者(Participants)的提交过程。2PC的执行过程被分为投票和提交两个阶段:
- 投票阶段:协调者向所有参与者发送事务请求,询问他们是否可以提交事务。每个参与者预先执行事务,并根据本地情况决定是否投票。
- 提交/回滚阶段:如果所有参与者在前一阶段都回复 “是”,协调者就会向所有参与者发送 “提交 “命令。如果任何参与方回复 “否”,或在规定时间内没有回复,协调者将向所有参与者发送 “回滚 “命令。
2PC 的优缺点
| 优点 | 强一致性、模型简单、数据库支持广泛 |
| 缺点 | 协调器单点故障、参与者可能“悬挂事务”、锁资源时间长、性能下降 |
2PC 仍然是传统企业领域(金融、电信)常见的强一致性方案,也是 XA 规范的底层基础。
04. 使用2PC实现一个简单的分布式事务
下面是一个基于2PC模型使用 PostgreSQL 原生 PREPARE TRANSACTION 模拟上面的转账场景的Java代码示例:
import java.sql.*;
import java.util.UUID;
public class Postgres2PCDemo {
public static void main(String[] args) throws Exception {
String url1 = "jdbc:postgresql://localhost:5432/db1";
String url2 = "jdbc:postgresql://localhost:5432/db2";
Connection c1 = DriverManager.getConnection(url1, "user1", "pass1");
Connection c2 = DriverManager.getConnection(url2, "user2", "pass2");
c1.setAutoCommit(false);
c2.setAutoCommit(false);
String xid = UUID.randomUUID().toString();
try {
// 参与者1:扣款
c1.createStatement().executeUpdate(
"UPDATE account SET balance = balance - 100 WHERE id = 1"
);
// 参与者2:加款
c2.createStatement().executeUpdate(
"UPDATE account SET balance = balance + 100 WHERE id = 2"
);
// 阶段1:Prepare
c1.createStatement().execute("PREPARE TRANSACTION '" + xid + "-p1'");
c2.createStatement().execute("PREPARE TRANSACTION '" + xid + "-p2'");
// 阶段2:Commit(协调器决策成功)
c1.createStatement().execute("COMMIT PREPARED '" + xid + "-p1'");
c2.createStatement().execute("COMMIT PREPARED '" + xid + "-p2'");
System.out.println("Global 2PC commit success!");
} catch (Exception e) {
System.out.println("Error, global rollback");
// 回滚已 prepare 的事务
try {
c1.createStatement().execute("ROLLBACK PREPARED '" + xid + "-p1'");
} catch (Exception ignore) {}
try {
c2.createStatement().execute("ROLLBACK PREPARED '" + xid + "-p2'");
} catch (Exception ignore) {}
throw e;
}
}
}