1. 分布式事务概述

1.1 问题背景

在分布式系统中,业务操作可能跨越多个服务或数据库(如订单服务、库存服务、支付服务),传统单机事务(ACID)无法满足跨网络节点的数据一致性需求。

  • 网络不可靠:服务间调用可能失败或超时。
  • 数据一致性:不同节点间的状态需最终一致。
  • 性能与可用性:避免长时间锁资源导致系统阻塞。

分布式事务的核心目标是确保 跨服务/数据库的操作要么全部成功,要么全部回滚

2. 分布式事务解决方案

2.1 两阶段提交(2PC)

原理

  • 阶段一(Prepare):协调者询问所有参与者是否可提交,参与者锁定资源并返回“同意”或“拒绝”。
  • 阶段二(Commit/Rollback):若所有参与者同意,协调者发送提交命令;否则发送回滚命令。

以下是一个简化的 Java 两阶段提交(2PC) 具体实现示例,包含协调者(Coordinator)和参与者(Participant)的核心逻辑。代码通过模拟数据库操作展示2PC的关键流程:


1. 参与者(Participant)实现

每个参与者代表一个独立的数据库或服务,需支持准备(Prepare)、提交(Commit)、回滚(Rollback)操作。

import java.util.concurrent.atomic.AtomicBoolean;

/**
* 参与者(如数据库或服务)
*/
public class Participant {
private String name; // 参与者名称(如"DB1")
private AtomicBoolean prepared = new AtomicBoolean(false); // 准备状态
private AtomicBoolean committed = new AtomicBoolean(false); // 提交状态 public Participant(String name) {
this.name = name;
} /**
* 阶段一:准备操作(锁定资源)
* @return true表示准备成功,false表示失败
*/
public boolean prepare() {
try {
// 模拟资源锁定,实际可能为操作数据库
System.out.println(name + ": Trying to prepare...");
Thread.sleep(100); // 模拟网络延迟
boolean success = Math.random() > 0.2; // 80%概率成功
if (success) {
prepared.set(true);
System.out.println(name + ": Prepared successfully.");
return true;
} else {
System.out.println(name + ": Prepare failed.");
return false;
}
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
return false;
}
} /**
* 阶段二:提交操作
*/
public void commit() {
if (prepared.get()) {
// 实际提交事务(如更新数据库)
committed.set(true);
System.out.println(name + ": Committed.");
} else {
System.out.println(name + ": Cannot commit without preparation.");
}
} /**
* 阶段二:回滚操作
*/
public void rollback() {
if (prepared.get()) {
// 实际回滚事务(如恢复数据)
prepared.set(false);
System.out.println(name + ": Rolled back.");
} else {
System.out.println(name + ": No need to rollback.");
}
} // 检查是否已提交
public boolean isCommitted() {
return committed.get();
}
}

2. 协调者(Coordinator)实现

协调者负责管理所有参与者,驱动两阶段提交流程。

import java.util.List;

/**
* 协调者(事务管理器)
*/
public class Coordinator {
private List<Participant> participants; public Coordinator(List<Participant> participants) {
this.participants = participants;
} /**
* 执行两阶段提交事务
* @return true表示事务成功提交,false表示失败
*/
public boolean executeTransaction() {
System.out.println("===== Phase 1: Prepare =====");
boolean allPrepared = participants.stream()
.allMatch(Participant::prepare); System.out.println("===== Phase 2: Commit/Rollback =====");
if (allPrepared) {
participants.forEach(Participant::commit);
System.out.println("Transaction committed successfully.");
return true;
} else {
participants.forEach(Participant::rollback);
System.out.println("Transaction rolled back due to failures.");
return false;
}
}
}

3. 客户端测试代码

模拟包含两个参与者的分布式事务场景。

import java.util.Arrays;

public class TwoPhaseCommitDemo {
public static void main(String[] args) {
// 创建两个参与者(如数据库DB1和DB2)
Participant db1 = new Participant("DB1");
Participant db2 = new Participant("DB2"); // 创建协调者并关联参与者
Coordinator coordinator = new Coordinator(Arrays.asList(db1, db2)); // 执行两阶段提交事务
boolean success = coordinator.executeTransaction(); // 输出最终状态
System.out.println("\nFinal Status:");
System.out.println("DB1 Committed: " + db1.isCommitted());
System.out.println("DB2 Committed: " + db2.isCommitted());
System.out.println("Transaction Result: " + (success ? "SUCCESS" : "FAILURE"));
}
}

4. 运行结果示例

成功场景(所有参与者准备成功)

===== Phase 1: Prepare =====
DB1: Trying to prepare...
DB1: Prepared successfully.
DB2: Trying to prepare...
DB2: Prepared successfully.
===== Phase 2: Commit/Rollback =====
DB1: Committed.
DB2: Committed.
Transaction committed successfully. Final Status:
DB1 Committed: true
DB2 Committed: true
Transaction Result: SUCCESS

失败场景(某一参与者准备失败)

===== Phase 1: Prepare =====
DB1: Trying to prepare...
DB1: Prepared successfully.
DB2: Trying to prepare...
DB2: Prepare failed.
===== Phase 2: Commit/Rollback =====
DB1: Rolled back.
DB2: No need to rollback.
Transaction rolled back due to failures. Final Status:
DB1 Committed: false
DB2 Committed: false
Transaction Result: FAILURE

5. 关键点说明

  1. 阶段一(Prepare)

    • 协调者询问所有参与者是否可以提交。
    • 参与者锁定资源并记录操作日志。
    • 任一参与者失败则整个事务回滚。
  2. 阶段二(Commit/Rollback)

    • 若所有参与者准备成功,协调者发送提交命令。
    • 若任一参与者失败,协调者发送回滚命令。
  3. 代码简化说明

    • 实际应用中需处理网络超时、重试和持久化日志。
    • 分布式场景下需使用RPC或HTTP替代本地方法调用。
    • 生产环境建议使用成熟的XA协议实现(如Atomikos、Narayana)。

6. 2PC的局限性

  • 同步阻塞:参与者在Prepare阶段后需阻塞等待协调者指令。
  • 单点故障:协调者宕机可能导致事务悬挂。
  • 数据不一致:协调者与参与者在Commit阶段同时宕机时,可能部分提交。

关注微信公众号,查看更多技术文章。

分布式事务之2PC两阶段提交的更多相关文章

  1. Atitit ACID解决方案2PC(两阶段提交)  跨越多个数据库实例的ACID保证

    Atitit ACID解决方案2PC(两阶段提交)  跨越多个数据库实例的ACID保证 1.1. ACID解决方案1 1.2. 数据库厂商在很久以前就认识到数据库分区的必要性,并引入了一种称为2PC( ...

  2. 关于分布式事务、两阶段提交、一阶段提交、Best Efforts 1PC模式和事务补偿机制的研究 转载

    1.XA XA是由X/Open组织提出的分布式事务的规范.XA规范主要定义了(全局)事务管理器(Transaction Manager)和(局部)资源管理器(Resource Manager)之间的接 ...

  3. 分布式事务、XA、两阶段提交、一阶段提交

    本文原文连接:http://blog.csdn.net/bluishglc/article/details/7612811 ,转载请注明出处! 1.XA XA是由X/Open组织提出的分布式事务的规范 ...

  4. 分布式事务 & 两阶段提交 & 三阶段提交

    可以参考这篇文章: http://blog.csdn.net/whycold/article/details/47702133 两阶段提交保证了分布式事务的原子性,这些子事务要么都做,要么都不做. 而 ...

  5. OceanBase分布式事务以及两阶段提交实现具体设计

    眼下OceanBase中还存在updaeserver单点,下一步的开发任务是使得OB支持多点写入,支持多个UPS(及updateserver). 当中难点是怎样设计两阶段提交的失败恢复以及多机的快照读 ...

  6. XA: 事务和两阶段提交

    本文原文连接:http://blog.csdn.net/bluishglc/article/details/7612811 ,转载请注明出处! 1.XA XA是由X/Open组织提出的两阶段提交协议, ...

  7. 分布式事务一2PC

    分布式事务解决方案之2PC(两阶段提交) 前面已经学习了分布式事务的基础理论,以理论为基础,针对不同的分布式场景业界常见的解决方案有2PC.TCC.可靠消息最终一致性.最大努力通知这几种. 3.1.什 ...

  8. 使用golang理解mysql的两阶段提交

    使用golang理解mysql的两阶段提交 文章源于一个问题:如果我们现在有两个mysql实例,在我们要尽量简单地完成分布式事务,怎么处理? 场景重现 比如我们现在有两个数据库,mysql3306和m ...

  9. MySQL源码之两阶段提交

    在双1的情况下,两阶段提交的过程 环境准备:mysql 5.5.18, innodb 1.1 version配置: sync_binlog=1 innodb_flush_log_at_trx_comm ...

  10. 分布式事务专题笔记(二)分布式事务解决方案之 2PC(两阶段提交)

    个人博客网:https://wushaopei.github.io/    (你想要这里多有) 前面已经了解了分布式事务的基础理论,以理论为基础,针对不同的分布式场景业界常见的解决方案有2PC.TCC ...

随机推荐

  1. gearman实现任务分发

    偶然发现了这个gearman,觉得这玩意儿挺好用,非常适合PHP运行一部分业务. 话不多说,安装一下. 使用apt查找 sudo apt search gearman 找到了这个 gearman/bi ...

  2. Http状态码502常见原因及排错思路

    Http状态码502常见原因及排错思路 502表示Bad Gateway.当Nginx返回502错误时,通常表示Nginx作为代理服务器无法从上游服务器(如:我们的后端服务器地址)获取有效的响应.导致 ...

  3. zynq 按照传统的Linux开发模式制作系统

    参考资料 xilinx wiki: https://xilinx-wiki.atlassian.net/wiki/spaces/A/pages/460653138/Xilinx+Open+Source ...

  4. Windows安装redis并将redis设置成服务开机自启

    Redis 作为一种缓存工具,主要用于解决高并发的问题,在分布式系统中有着极其广泛的应用,Redis 本身是应用于 Linux/Unix 平台的(部署在服务器上边),官方并没有提供 Windows 平 ...

  5. ecognition server注意事项

    1.4002端口是节点管理界面,默认密码admin. 2.8184端口是任务管理界面. 3.节点在线状态下,查看提交的影像矢量路径是否正确. 4.看服务器读取各个文件是否有误. 5.用develope ...

  6. Flutter 长列表优化

    Flutter 长列表优化 固定列表和长列表的区别 固定列表中的组件,每次都会被重新构建 ListView.builder会按需构建列表元素,也就是只有那些可见得元素才会调用itemBuilder 构 ...

  7. Visual Studio C++ 汇编 混合编程

    Visual Studio C++ 汇编 混合编程 实验要求 请用汇编语言编写实现GCD递推公式的子程序,对入口和出口参数形式不做要求,但需要用 C 语言函数来获取输入.调用汇编递推子程序,并且用 C ...

  8. Postgres中的Common Table Expression

    Common Table Expression 是 pg 里极为重要的特性.这个特性简单的说就是 INSERT/UPDATE/DELTE 三项操作可以返回结果集.如: update item set ...

  9. 【前端】白天/黑夜主题切换:JS读取XML预设主题实现黑白主题切换

    上扩展实现多主题选择,切换主题) 先看一下XML文档 id值不能重复,一套主题的name要相同,亮色type1,暗色type0 <?xml version="1.0" enc ...

  10. 性能测试 -- docker部署grafana

    一.前提 1.安装好了docker 2.docker内 和 jmeter脚本 全都已经部署好了influxdb,并且数据采集等都正常 二.docker 部署 grafana 的操作步骤 1.下载 do ...