当前位置: 首页 > article >正文

从零开始学2PC:分布式事务的原子性保障

一、什么是2PC?—— 分布式世界的“交通灯”

2PC(Two-Phase Commit) 是分布式系统中保证原子性的核心协议,就像交通信号灯协调多辆车同时通过路口。其核心目标是:让多个服务在同一个事务中,要么全部成功,要么全部失败

核心角色
协调者(Coordinator):指挥官,负责统筹全局事务(如订单系统)。
参与者(Participant):士兵,执行具体业务操作的服务(如库存系统、支付系统)。

通俗比喻
想象你和朋友约好一起做饭,你负责买菜(参与者A),朋友负责炒菜(参与者B)。协调者就是你们的“总指挥”,确保两人要么都完成,要么都不做。


二、2PC原理:两步走战略
1. 第一阶段:准备阶段(Prepare Phase)

协调者发令
“所有人准备好!我要检查你们的任务是否能完成。”
参与者响应
• 执行本地操作(如扣减库存、冻结支付金额),记录** undo 日志**(回滚脚本)。
• 回答“是”(Yes)或“否”(No):
Yes:表示“我能完成,但还没提交”。
No:表示“我做不到”(如库存不足)。

关键点
• 参与者不提交事务,只“预提交”。
• 通过**事务ID(XID)**唯一标识全局事务。


2. 第二阶段:提交阶段(Commit Phase)

协调者根据反馈决策
所有参与者都说“Yes” → 发送“全体提交!”命令。
至少一个说“No” → 发送“全体回滚!”命令。
参与者执行最终操作
• 提交:正式提交本地事务,释放资源。
• 回滚:根据undo日志恢复数据(如库存加回来)。

流程图解

协调者 → [Prepare] → 参与者A  
协调者 → [Prepare] → 参与者B  
...  
← [Yes/No] ← 参与者B  
← [Yes/No] ← 参与者A  

协调者 → [Commit/Rollback] → 所有参与者  

三、2PC适用场景:强一致性的刚需

以下场景必须用2PC(或类似协议):

  1. 金融转账
    • 用户A扣款100元,用户B加款100元。
    必须保证两者原子性,否则会资损。
  2. 电商订单
    • 扣减库存 → 生成订单 → 扣款 → 发送物流。
    • 任一环节失败,需全部回滚。
  3. 分布式数据库
    • 跨库事务(如MySQL + PostgreSQL)。

反例
日志记录:允许异步写入,最终一致即可。
统计报表:数据允许稍晚更新。


四、实战:手把手实现2PC
4.1 最简代码示例
// 协调者类
public class Coordinator {
    private List<Participant> participants = new ArrayList<>();

    public void addParticipant(Participant p) {
        participants.add(p);
    }

    // 第一阶段:准备
    public boolean prepare() {
        boolean allReady = true;
        for (Participant p : participants) {
            if (!p.prepare()) {
                allReady = false;
                // 可以立即通知参与者回滚(优化)
            }
        }
        return allReady;
    }

    // 第二阶段:提交/回滚
    public void commit() {
        participants.forEach(Participant::commit);
    }

    public void rollback() {
        participants.forEach(Participant::rollback);
    }
}

// 参与者类
public class Participant {
    private String name;
    private boolean prepared = false;

    public Participant(String name) {
        this.name = name;
    }

    // 准备阶段:执行本地操作
    public boolean prepare() {
        System.out.println(name + " 执行本地操作...");
        // 模拟成功率为80%
        prepared = Math.random() > 0.2;
        return prepared;
    }

    // 提交阶段
    public void commit() {
        if (prepared) {
            System.out.println(name + " 提交成功!");
        } else {
            System.out.println(name + " 被强制回滚");
        }
    }

    // 回滚阶段
    public void rollback() {
        if (prepared) {
            System.out.println(name + " 执行回滚...");
            // 这里恢复数据(如库存加回)
        }
    }
}

// 测试类
public class Main {
    public static void main(String[] args) {
        Coordinator coord = new Coordinator();
        coord.addParticipant(new Participant("库存服务"));
        coord.addParticipant(new Participant("支付服务"));

        try {
            if (coord.prepare()) {
                coord.commit();
                System.out.println("全局事务提交成功!");
            } else {
                coord.rollback();
                System.out.println("全局事务回滚!");
            }
        } catch (Exception e) {
            coord.rollback();
            System.out.println("发生异常,强制回滚!");
        }
    }
}

输出示例

库存服务 执行本地操作...
支付服务 执行本地操作...
库存服务 提交成功!
支付服务 提交成功!
全局事务提交成功!
4.2 使用Seata框架(进阶实战)

Seata是国产分布式事务框架,简化了2PC的实现。核心组件:
TC(Transaction Coordinator):协调者。
TM(Transaction Manager):定义全局事务边界(@GlobalTransactional)。
RM(Resource Manager):管理数据库连接等资源。

// 业务代码(只需加注解)
@Service
public class OrderService {
    @GlobalTransactional(timeout=30000, name="createOrder")
    public void createOrder(Order order) {
        inventoryService.deduct(order.getSkuId()); // 扣库存
        paymentService.charge(order.getUserId(), order.getAmount()); // 扣款
        orderDAO.insert(order); // 生成订单
    }
}

流程说明

  1. TM启动全局事务,请求TC注册。
  2. 各个RM(库存、支付、订单服务)向TC汇报状态。
  3. TC根据所有RM的反馈决定提交或回滚。

五、2PC的坑与解决方案
1. 单点故障:协调者宕机

问题:协调者宕机后,参与者无法收到提交/回滚指令,导致死锁。
解决方案
选举机制:集群部署协调者,自动选举主节点(如Raft协议)。
持久化日志:协调者将事务状态写入磁盘,重启后恢复。

2. 阻塞问题:参与者长时间等待

问题:网络延迟导致参与者一直等待协调者指令,占用资源。
解决方案
超时机制:参与者设置最大等待时间(如30秒),超时后自动回滚。
异步通信:使用消息队列代替同步调用(需结合Saga模式)。

3. 数据不一致:脑裂(Split-Brain)

问题:协调者集群分裂,部分节点认为已提交,部分认为未提交。
解决方案
基于版本号或时间戳:参与者记录事务版本,协调者只接受最新指令。
多阶段提交(3PC):引入预提交阶段,减少不确定状态。

4. 重复提交:幂等性缺失

问题:协调者重启后,可能重复发送提交指令。
解决方案
全局事务ID(XID):每个事务唯一标识,参与者拒绝重复处理。
状态机:记录事务状态(PREPARED/COMMITTED/ROLLED_BACK)。


六、2PC vs 其他协议
对比维度2PCTCCSaga
一致性强一致最终一致最终一致
阻塞高(同步)低(异步)低(异步)
开发成本中(需实现协调者/参与者)高(需编写补偿逻辑)中(需定义事件和补偿)
适用场景金融、订单支付、退款日志、通知

七、总结与行动建议
  1. 掌握基础:先通过简单代码理解2PC的两阶段流程。
  2. 使用框架:生产环境推荐Seata或RocketMQ事务消息,避免重复造轮子。
  3. 避坑指南
    • 协调者集群化部署。
    • 设置合理的超时时间(如10秒)。
    • 业务代码需幂等(如通过订单ID防重)。
  4. 进阶学习
    • 书籍:《分布式系统模式》(Unmesh Joshi)。
    • 文档:Seata官方文档、RocketMQ事务消息指南。

最后思考
2PC是分布式事务的基石,但并非银弹。在实际项目中,需结合业务场景选择方案(如Seata AT模式=2PC+自动补偿)。对于90%的互联网场景,最终一致性已足够,只有在核心金融系统中才需强一致。


http://www.kler.cn/a/595268.html

相关文章:

  • C++编译流程
  • UNIX网络编程笔记:一些网络协议的相关知识
  • 【Android】基础架构(详细介绍)
  • WordPress 性能优化技术指南:打造快速加载的网站
  • 【python】OpenCV—Hand Landmarks Detection
  • 能源监控软件UI界面设计:平衡功能性与审美性的艺术
  • 针对耳鸣患者推荐的一些菜谱和食材
  • 透析Vue的nextTick原理
  • uniapp小程序,输入框限制输入(正整数、小数后几位)
  • Umi-OCR 实践教程:离线、免费、高效的图像文字识别工具
  • 家庭网络安全:智能设备与IoT防护——当“智能家居”变成“僵尸网络”
  • Java 记忆链表,LinkedList 的升级版
  • PostgreSQL_数据表结构设计并创建
  • 使用 Ansys Fluent 评估金属管道腐蚀
  • 1204. 【高精度练习】密码
  • 《Python实战进阶》No42: 多线程与多进程编程详解(上)
  • 【漫话机器学习系列】153.残差平方和(Residual Sum of Squares, RSS)
  • LeetCode 2680.最大或值:位运算
  • 如何在IPhone 16Pro上运行python文件?
  • 【UI设计】一些好用的免费图标素材网站