分布式事务在电商交易系统中经常会遇到,其中一个实现方案两阶段提交(简称“2pc”)比较经典,今天心血来潮利用DeepSeek辅助手写了一把
什么是分布式事务?
分布式事务是指涉及多个独立资源(如数据库、消息队列、缓存等)的事务操作,需要保证所有资源要么全部提交成功,要么全部回滚,以确保数据的一致性。
什么是 2PC(两阶段提交)
2PC 是一种分布式事务协议,分为两个阶段:
- 准备阶段(Prepare Phase):
● 协调者询问所有参与者是否可以提交事务。
● 参与者执行事务操作,并返回准备结果(成功或失败)。 - 提交阶段(Commit Phase):
● 如果所有参与者都准备成功,协调者发送提交请求。
● 如果任一参与者准备失败,协调者发送回滚请求。
2PC 的核心组件
● 协调者(Coordinator):负责协调事务的提交或回滚。
● 参与者(Participant):负责执行具体的事务操作。
2PC 的优缺点
● 优点:
保证分布式事务的原子性。
实现简单,易于理解。
● 缺点:
存在单点故障(协调者故障可能导致阻塞)。
性能开销较大(需要多次通信)。
2PC 的 Java 实现
5.1 参与者接口与实现
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
// 参与者接口
interface Participant {
boolean prepare() throws SQLException;
void commit() throws SQLException;
void rollback() throws SQLException;
}
// 具体的数据库参与者
class DatabaseParticipant implements Participant {
private String url;
private String user;
private String password;
private String sql;
private Object[] params;
public DatabaseParticipant(String url, String user, String password, String sql, Object[] params) {
this.url = url;
this.user = user;
this.password = password;
this.sql = sql;
this.params = params;
}
@Override
public boolean prepare() throws SQLException {
try (Connection conn = DriverManager.getConnection(url, user, password)) {
conn.setAutoCommit(false); // 开启事务
try (var stmt = conn.prepareStatement(sql)) {
for (int i = 0; i < params.length; i++) {
stmt.setObject(i + 1, params[i]);
}
stmt.executeUpdate();
}
return true; // 准备成功
}
}
@Override
public void commit() throws SQLException {
try (Connection conn = DriverManager.getConnection(url, user, password)) {
conn.commit(); // 提交事务
System.out.println("Committed: " + url);
}
}
@Override
public void rollback() throws SQLException {
try (Connection conn = DriverManager.getConnection(url, user, password)) {
conn.rollback(); // 回滚事务
System.out.println("Rolled back: " + url);
}
}
}
5.2 协调者实现
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
// 协调者
class Coordinator {
private final List<Participant> participants = new ArrayList<>();
private final List<Participant> preparedParticipants = new ArrayList<>();
public void addParticipant(Participant participant) {
participants.add(participant);
}
// 第一阶段:准备阶段
public boolean prepare() {
for (Participant p : participants) {
try {
if (p.prepare()) {
preparedParticipants.add(p); // 记录已成功准备的参与者
} else {
return false; // 如果有参与者准备失败,返回 false
}
} catch (SQLException e) {
e.printStackTrace();
return false;
}
}
return true; // 所有参与者准备成功
}
// 第二阶段:提交或回滚
public void commit(boolean success) {
for (Participant p : preparedParticipants) { // 只对已成功准备的参与者操作
try {
if (success) {
p.commit(); // 提交事务
} else {
p.rollback(); // 回滚事务
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
5.3 测试主程序
public class TwoPhaseCommit {
public static void main(String[] args) {
// 初始化参与者
Participant participant1 = new DatabaseParticipant(
"jdbc:mysql://localhost:3306/db1", "root", "password",
"UPDATE accounts SET balance = balance - ? WHERE id = ?",
new Object[]{100.00, 1}
);
Participant participant2 = new DatabaseParticipant(
"jdbc:mysql://localhost:3306/db2", "root", "password",
"UPDATE accounts SET balance = balance + ? WHERE id = ?",
new Object[]{100.00, 1}
);
// 初始化协调者
Coordinator coordinator = new Coordinator();
coordinator.addParticipant(participant1);
coordinator.addParticipant(participant2);
// 第一阶段:准备阶段
boolean prepared = coordinator.prepare();
if (prepared) {
// 第二阶段:提交
coordinator.commit(true);
} else {
// 回滚
coordinator.commit(false);
}
}
}
6. 运行说明
- 准备阶段:
● 参与者 1 从账户 1 扣除 100。
● 参与者 2 向账户 2 增加 100。 - 提交阶段:
● 如果所有参与者都准备成功,提交事务。
● 如果任一参与者准备失败,回滚事务。
7. 总结
2PC 是分布式事务的基础协议,通过准备和提交两个阶段保证事务的原子性。
本示例通过 Java 实现了 2PC 的核心逻辑,适用于分布式数据库场景。
实际应用中,2PC 可以与其他技术(如 TCC、Saga)结合,解决单点故障和性能问题。