大部分关系型数据库通过两阶段提交协议来完成分布式事务,比如在Oracle中通过dblink方式进行事务处理。两阶段提交协议最早由分布式事务专家Jim Gray在1978年的一篇文章“Notes on Database Operating Systems”中提及。两阶段提交协议可以保证数据的强一致性,即保证分布式事务的原子性 。它是协调所有分布式原子事务参与者,并决定提交或取消(回滚)的分布式算法,同时也是解决一致性问题的算法。2PC算法能够解决很多临时性系统故障(包括进程、网络节点、通信等故障),因此被广泛使用,但它并不能通过配置来解决所有的故障,在某些情况下它还需要人为参与才能解决问题。
顾名思义,两阶段提交分为以下两个阶段:准备阶段(Prepare Phase)和提交阶段(Commit Phase)。
在两阶段提交协议中,系统一般包含两类角色。一是协调者(Coordinator),通常一个系统中只有一个。二是参与者(Participant),一般包含多个,在数据存储系统中可以理解为数据副本的个数。
1.准备阶段
在准备阶段,协调者将通知事务参与者准备提交或取消事务,写本地的redo和undo日志,但不提交,然后进入表决过程。在表决过程中,参与者将告知协调者自己的决策:同意(事务参与者本地作业执行成功)或取消(本地作业执行故障),流程如下。
(1)写本地日志“BEGIN_COMMIT”,并进入WAIT状态。
(2)向所有参与者发送“VOTE_REQUEST”消息。
(3)等待并接收参与者发送的对“VOTE_REQUEST”的响应,参与者响应“VOTE_ABORT”或“VOTE_COMMIT”消息给协调者。
2.提交阶段
在该阶段,协调者将基于第一个阶段的投票结果进行决策:提交或取消。当且仅当所有参与者都同意提交事务,协调者才通知所有的参与者提交事务,否则协调者将通知所有的参与者取消事务。参与者在接收到协调者发来的消息后将执行相应的操作,流程如下。
(1)若收到任何一个参与者发送的“VOTE_ABORT”消息,写本地“GLOBAL_ABORT”日志,进入ABORT状态,向所有的参与者发送“GLOBAL_ABORT”消息。
(2)若收到所有参与者发送的“VOTE_COMMIT”消息,写本地“GLOBAL_COMMIT”日志,进入COMMIT状态,向所有的参与者发送“GLOBAL_COMMIT”消息。
(3)等待并接收参与者发送的对“GLOBAL_ABORT”消息或“GLOBAL_COMMIT”消息的确认响应消息,一旦收到所有参与者的确认消息,写本地“END_TRANSACTION”日志,流程结束。
一般情况下,两阶段提交机制都能较好地运行,但当在事务进行过程中,有参与者死机时,重启以后,可以通过询问其他参与者或协调者,从而知道这个事务是否提交了。当然,这一切的前提是各个参与者在进行每一步操作时都会事先写入日志。
两阶段提交不能解决的困境如下:
● 同步阻塞问题:执行过程中,所有参与节点都是事务阻塞的。当参与者占有公共资源,其他第三方节点访问公共资源时就不得不处于阻塞状态。
● 单点故障:由于协调者的重要性,一旦协调者发生故障,参与者会一直阻塞下去。尤其是在第二阶段,如果协调者发生故障,那么所有的参与者将会处于锁定事务资源的状态中,无法继续完成事务操作;如果是协调者挂掉,可以重新选举一个协调者,但是无法解决因为协调者死机导致的参与者处于阻塞状态的问题。
● 数据不一致:在两阶段提交的第二阶段中,当协调者向参与者发送提交请求之后,发生了局部网络异常,或者在发送提交请求的过程中协调者发生了故障,这会导致只有一部分参与者接到提交请求,而这部分参与者在接到提交请求之后就会执行提交操作,但是其他未接到提交请求的机器则无法执行事务提交,于是整个分布式系统便出现了数据不一致的现象。