ZBLOG

2PC

好的,这是一个非常核心的分布式系统概念。2PC,全称是两阶段提交协议

它是一种在分布式系统中用于保证所有参与事务的节点要么全部提交,要么全部回滚原子性的协议。它是实现分布式事务的经典方法。

顾名思义,2PC将一个事务的最终提交分为两个阶段来执行。


1. 角色

  • 协调者:通常就是发起事务的主节点或事务管理器。它负责驱动整个协议流程,并做出最终决定。
  • 参与者:也称为子节点或资源管理器。它们是分布式系统中各个独立的数据库或服务,负责执行本地事务,并向协调者反馈投票。

2. 两个阶段详解

第一阶段:投票阶段

  1. 准备请求:协调者向所有参与者发送“准备”请求,其中包含事务本身的所有信息。
  2. 执行本地事务:每个参与者接收到请求后,会执行本地的事务操作(例如更新数据),但不会真正提交。它会将Undo和Redo信息写入日志,以确保即使系统崩溃也能恢复。
  3. 投票响应
    • 如果参与者成功执行了事务,并做好了提交/回滚的准备,它就向协调者返回一个 “同意” 消息。
    • 如果参与者在执行本地事务时出现任何问题(如数据约束冲突、网络断开等),它就向协调者返回一个 “中止” 消息。

第二阶段:提交/回滚阶段

协调者根据第一阶段的投票结果做出决定:

  • 情况A:所有参与者都返回“同意”

    1. 提交请求:协调者向所有参与者发送 “提交” 命令。
    2. 完成提交:每个参与者在收到“提交”命令后,才会正式提交本地事务,并释放事务所占用的资源。
    3. 确认完成:参与者在完成提交后,向协调者发送“已完成”的确认消息。
  • 情况B:任何一个或多个参与者返回了“中止”,或者等待超时

    1. 回滚请求: 协调者向所有参与者发送 “回滚” 命令。
    2. 完成回滚: 每个参与者利用之前写入的Undo日志来回滚本地事务,释放资源。
    3. 确认完成: 参与者在完成回滚后,向协调者发送“已回滚”的确认消息。

3. 流程图简化表示

[协调者]                  [参与者1]                [参与者2]
   | -- (1) Prepare -----> |                       |
   | -- (1) Prepare -----------------------------> |
   | <-- (2) Yes --------- |                       |
   | <-- (2) Yes --------------------------------- |
   |                                               |
   | *** 第二阶段: 所有都同意 -> Commit ***          |
   | -- (3) Commit ------> |                       |
   | -- (3) Commit ------------------------------> |
   | <-- (4) Ack --------- |                       |
   | <-- (4) Ack --------------------------------- |

4. 优缺点

优点

  • 强一致性: 保证了所有节点数据状态的最终一致(要么全成功,要么全失败)。
  • 概念简单: 流程清晰易懂。

缺点(非常致命)

  1. 同步阻塞

    • 在参与者投票后到收到协调者决策的这段时间里,该参与者上的相关资源会被一直锁定。如果协调者宕机,所有参与者都将被无限期阻塞。
  2. 单点问题

    • 整个协议的核心是协调者。如果协调者在第二阶段(发送Commit/Rollback之前)宕机:
      • 部分已经投“同意”票的参与者将不知道最终结果,从而陷入一种不确定状态(既不能提交也不能回滚),只能被动等待协调者恢复。
      • 这是2PC最严重的问题。
  3. 数据不一致(极端情况下)

    • 虽然协议设计上追求一致性,但在某些故障场景下仍可能不一致。例如:
      • 协调者在发出部分Commit消息后宕机。
      • 导致只有一部分参与者收到了Commit并提交了事务,而另一部分没有收到,导致数据不一致。

Java代码示例

// Coordinator.java
public class Coordinator {
    private List<Participant> participants;

    public boolean performTwoPhaseCommit(Transaction transaction) {
        // ========== Phase 1: Voting Phase ==========
        List<Boolean> votes = new ArrayList<>();
        for (Participant p : participants) {
            try {
                boolean vote = p.prepare(transaction); // Send prepare request
                votes.add(vote);
            } catch (Exception e) {
                votes.add(false); // Treat exception as a "No" vote
            }
        }

        // ========== Phase 2: Decision Phase ==========
        boolean allAgreed = votes.stream().allMatch(vote -> vote == true);

        if (allAgreed) {
            // Send commit to all
            for (Participant p : participants) {
                p.commit(transaction);
            }
            return true; // Transaction committed globally
        } else {
            // Send rollback to all
            for (Participant p : participants) {
                p.rollback(transaction);
            }
            return false; // Transaction aborted globally
        }
    }
}

// Participant.java
public class Participant {

    public boolean prepare(Transaction transaction) {
        try {
            // 1. Execute transaction locally BUT DO NOT COMMIT.
            executeLocally(transaction);
            
            // 2. Write undo/redo logs to disk for recovery.
            writeToLog(transaction);
            
            // Everything is ready, vote "Yes"
            System.out.println("Participant: Voted YES for transaction.");
            return true;
        } catch (Exception e) {
            // Something went wrong, vote "No"
            System.out.println("Participant: Voted NO for transaction due to error.");
            return false;
        }
    }

    public void commit(Transaction transaction) {
        // Now it's safe to formally commit the local transaction.
        formalCommit(transaction);
        System.out.println("Participant: Committed transaction.");
    }

    public void rollback(Transaction transaction) {
        // Use the undo log to rollback the local changes.
        formalRollback(transaction);
        System.out.println("Participant: Rolled back transaction.");
    }

    // ... Other methods like executeLocally, writeToLog, etc.
}

Zeros Education关联知识点

在学习零声教育的C++后端课程时(特别是分布式架构和存储相关章节),你会深入接触到:

  • SeataDTM等开源分布式事务框架是如何实现和改进2PC的(例如通过引入TC-Transaction Coordinator)。
  • TCCSAGA等模式作为2PC的替代方案,如何解决其阻塞和单点问题。
  • 在具体的高性能网络编程和中间件开发中如何处理这类复杂的协同逻辑。

总而言之,理解清楚传统2PC的原理和缺陷是学习现代分布式事务解决方案的重要基础。


内容由零声教学AI助手提供,问题来源于学员提问

本站部分文章来源于网络,版权归原作者所有,如有侵权请联系站长删除。
转载请注明出处:https://sdn.0voice.com/?id=8512

分享:
扫描分享到社交APP
上一篇
下一篇
发表列表
游客 游客
此处应有掌声~
评论列表

还没有评论,快来说点什么吧~

联系我们

在线咨询: 点击这里给我发消息

微信号:3007537140

上班时间: 10:30-22:30

关注我们
x

注册

已经有帐号?