协调者

在分布式系统中,每一个机器节点虽然都能明确的知道自己执行的事务是成功还是失败,但是却无法知道其他分布式节点的事务执行情况。因此,当一个事务要跨越多个分布式节点的时候(比如,淘宝下单流程,下单系统和库存系统可能就是分别部署在不同的分布式节点中),为了保证该事务可以满足ACID,就要引入一个协调者(Cooradinator)。其他的节点被称为参与者(Participant)。协调者负责调度参与者的行为,并最终决定这些参与者是否要把事务进行提交。

二阶段提交协议(2PC)

二阶段提交协议主要分为两个阶段:准备阶段和提交阶段。

准备阶段

事务协调者(事务管理器)给每个参与者发送事务内容,每个参与者要么直接返回失败(如权限验证失败),要么在本地执行事务,写本地的redo和undo日志,但不提交,到达一种“万事俱备,只欠东风”的状态。由于这一阶段近似各参与者投票表明是否可以执行接下来的事务提交操作,所以也被称为“投票阶段”。

提交阶段

如果协调者收到了参与者的失败消息或者超时,直接给每个参与者发送回滚(Rollback)消息;否则,发送提交(Commit)消息;参与者根据协调者的指令执行提交或者回滚操作,释放所有事务处理过程中使用的锁资源。

优缺点

优点:原理简单、实现方便。
缺点:

  • 同步阻塞:各个参与者在等待其他参与者响应的过程中,将无法进行其他任何操作。
  • 单点问题:协调者至关重要。尤其在第二阶段,协调者发生故障,那么所有的参与者还都处于锁定事务资源的状态中,而无法继续完成事务操作。
  • 数据不一致:由于参与者可能宕机,此时只有部分参与者收到了commit请求。

三阶段提交协议(3PC)

3PC在协调者和参与者中都引入了超时机制,并将2PC的准备阶段(投票阶段)一分为二,这样三阶段提交就有CanCommit、PreCommit、DoCommit三个阶段。

CanCommit阶段

协调者向所有的参与者发送一个包含事务内容的CanCommit请求,参与者在收到该请求后,如果认为自己可以顺利执行事务,就反馈Yes响应,并进入预备状态,否则反馈No响应。

PreCommit阶段

协调者根据参与者的反馈情况来决定是否可以继续事务的PreCommit操作。根据响应情况,有以下两种可能。

  • 假如协调者从所有的参与者获得的反馈都是Yes响应,那么就会执行事务的预执行。即执行事务操作,并将undo和redo信息记录到事务日志中。如果参与者成功的执行了事务操作,则返回Ack响应,同时开始等待最终指令。
  • 假如有任何一个参与者向协调者发送了No响应,或者等待超时之后,协调者都没有接到参与者的响应,那么就执行事务的中断。

DoCommit阶段

该阶段进行真正的事务提交,也可以分为以下两种情况。

  • 执行提交:协调者接收到参与者发送的ACK响应,那么他将从预提交状态进入到提交状态,并向所有参与者发送doCommit请求。参与者接收到doCommit请求之后,执行正式的事务提交,并在完成事务提交之后释放所有事务资源,并向协调者发送Ack响应。协调者接收到所有参与者的ack响应之后,完成事务。
  • 中断事务:协调者没有接收到参与者发送的Ack响应(可能是接受者发送的不是Ack响应,也可能响应超时),那么就会执行中断事务,即回滚操作。

在doCommit阶段,如果参与者无法及时接收到来自协调者的doCommit或者rebort请求时,会在等待超时之后,会继续进行事务的提交。(其实这个应该是基于概率来决定的,当进入第三阶段时,说明参与者在第二阶段已经收到了PreCommit请求,那么协调者产生PreCommit请求的前提条件是他在第二阶段开始之前,收到所有参与者的CanCommit响应都是Yes。(一旦参与者收到了PreCommit,意味他知道大家其实都同意修改了)所以,一句话概括就是,当进入第三阶段时,由于网络超时等原因,虽然参与者没有收到commit或者abort响应,但是他有理由相信:成功提交的几率很大。 )

优缺点

优点:降低了参与者的阻塞范围,比如在执行事务之前先询问是否可以顺利执行,避免一些本来就无法执行的参与者导致其他正常的参与者无意义的回滚。并且由于一旦参与者无法及时收到来自协调者的信息之后,他会默认执行commit,因此也就解决了单点故障问题。
缺点:超时机制虽然解决了单点问题,但也引入了数据不一致的问题,比如协调者发送的abort响应没有及时被参与者接收到,那么参与者在等待超时之后执行了commit操作,这样就和其他接到abort命令并执行回滚的参与者之间存在数据不一致的情况。

参考资料