在分布式系统中,每一个机器节点虽然都能明确的知道自己在事务操作中的结果是成功或失败,但无法直接获取其他节点的操作结果。因此在分布式环境中,为了保持事务的ACID特性,就需要增加一个“协调者”来管理其他节点(“参与者”)事务的提交和回滚。基于这个思想,衍生出二阶段提交和三阶段提交两种协议。
二阶段提交(Two-Phase Commit),是一个非常经典的强一致、中心化的原子提交协议。目前,绝大多数关系型数据库都采用二阶段提交协议来完成分布式事务处理(例如mysql的XA协议)。因此二阶段提交协议也被广泛运用到分布式系统中。
顾名思义,算法流程就是分为两个阶段提交某一操作,其分为准备阶段、提交阶段。为了更好描述算法过程,为此定义了两种角色:协调者(Coordinator)、参与者(Participant)。
准备阶段,又被称为投票阶段(Vote Request),由协调者向参与者发送请求,询问当前事务能否处理成功。参与者则开启本地数据库事务,开始执行数据库操作,但是并不会提交。根据操作结果,返回给协调者“yes/no”,表示事务是否可以提交。
在准备阶段,由于参与者可以返回yes/no,则在提交阶段也会出现两种可能,即全局提交事务、全局回滚事务。
当准备阶段所有参与者都返回yes的响应后,协调者将发起全局提交事务请求。
当准备阶段有一个参与者都返回no的响应后,或者在协调者等待响应超时后,则协调者将发起全局回滚事务请求,中断事务。
2PC协议明显的优点就是:原理简单、容易实现。但是它的缺点更加明显:
上面小节讲解了二阶段提交协议的原理,也指出了它所存在的问题,因此有必要在2PC基础上进一步改进,提出三阶段提交协议。
3PC(Three-Phase Commit),在2PC的基础上,将准备阶段一分为二,形成由 CanCommit、PreCommit、DoCommit三个阶段组成的事务提交协议。并且在3PC参与者也增加超时机制(2PC只有协调者拥有超时机制),避免了参与者长时间无法与协调者节点通讯(协调者宕机)的情况下,无法释放资源的问题。在参与者超时后,会自动进行本地commit/rollback从而进行释放资源。这种机制也侧面降低了整个事务的阻塞时间和范围。
在阶段一结果中,如果所有参与者都返回yes,则执行事务预提交,协调者发起PreCommit请求。如果有任何一个参与者节点返回的结果是no,或者协调者在等待参与者节点反馈的过程中超时,整个分布式事务就会中断,协调者就会向所有的参与者发送“abort”请求。
注意:上面所说的参与者增加超时时间,在该阶段就可以派上用场了。在该阶段开始之前,如果协调者宕机,参与者在等待超时时间后,各自选择中断事务,最终达成一致。但是协调者在发出一部分preCommit请求后宕机,有一部分没有收到preCommit请求,那情况则是:一部分参与者执行了preCommit操作,一部分参与者没有执行preCommit操作,最终导致各节点之间数据不一致。
该阶段中断事务相比阶段三中断事务要简单,只需要由协调者向所有参与者发送abort请求,通知中断事务。期间参与者在等待协调者的preCommit请求超时后也会主动中断本地事务。
同样,根据上一阶段的结果,该阶段也会存在两种情况。阶段二所有参与者成功反馈,则执行提交事务请求,否则中断事务。
注意:参与者增加的超时时间,在该阶段也有所体现。在该阶段,无论是协调者宕机了,还是网络原因导致参与者没有收到该阶段的(提交/中断)请求,参与者都会执行本地事务提交。倘若在该阶段,协调者需要发送的是提交事务请求,参与者自动提交本地事务,最后能达到一致性。倘若在该阶段,协调者需要发送的是中断事务请求,如果参与者没有收到该请求,则参与者也会自动提交本地事务,最终导致各节点之间数据不一致。
相比于2PC,3PC最大的优点就是减少了参与者的阻塞范围,并且能在协调者故障之后某些情况下继续达成一致。
3PC的缺点,也就是我在每个阶段描述中,注意的那一部分,在某些情况下,3PC必然会造成数据的不一致性。