2PC,全称是two phase commit,两段式提交,用来解决分布式环境下事务的原子性。
单机的原子性是通过redo和undo日志来实现的,但是分布式环境下,原子性不仅仅要让每一台机器的事务原子性,还要让多台机器的事务整体原子性。
每一台机器自己无法知道其他机器的事务执行情况,所以只有程序的调用方或者事务的发起方可以掌控每一个事务的执行情况。我们把事务的发起方叫做协调者,把事务的执行机器叫做参与者。2PC的大致思想是,协调者先让每一个参与者执行事务的内容,但是不提交,每一个参与者执行完以后给协调者一个反馈,如果协调者收到的全部是ok,那么就让每一个参与者执行commit,否则就让每一个参与者rollback。
详细过程如下:
1.请求阶段:
协调者向每一个参与者发送事务内容,并且询问是否可以做;
参与者记录redo和undo日志并且执行事务的内容,一旦出错,就会返回错误信息给协调者,否则告知协调者ok;
2.提交阶段:
协调者如果收到的全部是ok,就会让参与者commit,参与者收到以后就会执行commit。否则如果收到了一个或者一个以上的错误,或者等待超时了(认为不ok),就会发送rollback请求,参与者全部rollback。
仔细一看这个协议本身还是有很多的问题:
(1)单点问题:协调者一旦挂了,那么事务就无法执行了,参与者可能会一直等待下去而无法处理其他的请求;
(2)同步阻塞:协调者必须等待所有的参与者返回结果才能执行,等待期间无法做其他事情,这就降低了系统的响应时间;
(3)数据不一致:假设commit只有一部分发送到了参与者,其他的参与者可能由于网络原因之类的没有收到commit,那么最终就只有部分参与者执行了提交,其余的没有提交,造成了数据的不一致。
(4)太过保守:只有全部参与者都返回了ok才能commit,加入其中一些ok信息由于网络没有到达,最终协调者会等待超时rollback事务,但是其实是可以commit的。所以只要有一点出问题,就会导致事务失败。
为了解决上述中的一些问题,出现了3PC,分为了三个阶段。
1.canCommit:协调者询问参与者是否可以执行事务,但没有发送具体的事务内容,参与者返回信息。
2.preCommit:如果全部返回了ok,那么协调者就会发送事务内容,参与者记录日志并且执行,如果执行顺利,就返回ok,否则返回错误。
3.doCommit:如果全部返回了ok,那么参与者发送commit命令,参与者执行commit,否则有一个必败或者等待参与者超时,就rollback。如果在该阶段,参与者没有等到协调者的命令,它会默认执行commit。
3PC的改进是多了一个canCommit阶段,这样会缩小同步等待的时间,如果有些参与者本身除了问题,就可以在第一阶段反馈,那么就可以及时的中断事务,但是在2PC中,是直接执行事务操作的,这样同步的时间会变久。另外,在doCommit阶段如果协调者的消息没收到或者协调者挂了,参与者会默认执行,解决了2PC单点问题,但是这样会有新的数据不一致问题,就是假设协调者收到的是不ok的信息,这时它需要发送rollback命令,但是这个命令由于网络或者协调者挂了,没有发送到参与者,参与者就等待超时直接执行了commit操作,也造成了数据一致性的问题。