经典的分布式数据库模型中,同一个数据库的各个副本运行在不同的节点上,每个副本的数 据要求完全一致。数据库中的操作都是事务 (transaction),一个事务是一系列读、写操作,事务满足 ACID。每个事务的最终状态要么是提交 (commit),要么是失败 (abort)。一旦一个事务成功提交,那么这个事务中所有的写操作中成功,否则所有的写操作都失败。

为了适应这种 ACID 模型,2PC 提供的解决方案是:首先由一个节点(提议者)询问每个其他节点(参与者)是否同意提交某个事务,如果全部同意,则实际提交这个事务,否则拒绝提交这个事务。

工作流程

成功提交

在任何一个新的事务到来时,master会先将事务开始的相关信息写入日志。

注意要 commit 的内容在第一次询问时会被 cache 在各个 node 上,如果决定了 commit,直接做这个 cache 里的内容就好了,这部分 cache 也被用于决定后续的事务是否可以被提交。

2pc-step1


2pc-step2-all-agree


2pc-step3-do-commit


注意最后的 commit 请求完成后node还需要通知一声master,让master记录此次事务完成。

失败

失败提交的可能场景是某些节点上其他事务(可能是上一步留下来还没有完成提交的事务)和待提交的事务有冲突。

2pc-step1


2pc-step2-has-disagree


2pc-step3-do-reject

异常情况处理

宕机

宕机恢复后可以通过日志确定自己处于何种状态。

Master

Master宕机后恢复,如果发现自己处于:

  • 事务开始了,但还没有作出决定是否commit最后一个事务的状态

    那么重新询问全部节点是否可以commit,然后继续一般流程即可。

  • 已经决定是否commit最后一个事务,但还没有实际commit/reject的状态

    简单地从通知所有节点实际进行commit/reject这步开始继续一般流程即可。

Node

Node宕机后恢复,如果发现自己处于:

  • 已经接到对某个事务的投票请求,但还未投票

    检查并投票,然后继续流程。

  • 已经投票,但还没有实际提交

    等待Master发送/重发commit/reject消息即可

超时

  • Node为某次事务是否commit的投票超时

    Master直接认为它选择了reject这个事务。

  • Node某次事务实际提交/拒绝后的确认超时

    不断重试要求其提交,直到Node再次上线并返回确认。

  • Node迟迟没有收到Master对某次事务的最终决定

    不断重发自己的投票结果,直到收到决定。

这种不断重试的机制决定了2PC如果不加修改的话,A和P都是很糟糕的,不过相应地也换来了完全的C。