xiaoxiangyu 2019-12-26
https://www.cnblogs.com/lay2017/p/12078232.html
在讲1pc事务提交协议的时候我们说过,1pc适合单个数据源,如果在分布式场景下需要面对多个数据源的时候它就心有余而力不足了。
1pc的根本问题在于无法协调多个数据源,为此就需要创造出一个能够协调多个数据源事务的管理者。第二个问题1pc中,每个事务如果执行了commit事务操作,那么就无法再回滚了。这对第二个问题,可以将commit操作拆分成两步,先预提交一次事务,并不真正的提交,再统一commit一次真正的提交。这样,如果业务代码出现异常,那么预提交的事务完全可以做回滚操作。解决了这两个逻辑,也就是2pc的核心要点。
如图所示,两阶段提交协议分为两个阶段:
1)prepare阶段,询问各个加入事务管理的数据源是否预提交事务,数据源返回yes或者no
2)commit阶段,事务管理器检查参与的数据源是否都返回yes,如果是通知所有数据源commit事务,如果不是通知所有数据源rollback事务。
这里,我们注意到,每次询问参与者prepare,commit,rollback操作都会有一个yes、no、ok之类的响应。比如,事务管理器询问数据库1、数据库2是否prepare事务,数据库1返回了yes,而数据库2因为某些原因迟迟未回应,那么事务管理器就会阻塞等待它的响应,也因此数据库1的资源也会阻塞等待响应。这个问题值得注意一下。
第二个值得注意的点是上图中没有表现出来的事务日志持久化方面的机制,持久化本身的最大目的是维持宕机高可用,也就是可恢复。
事务管理器接收到客户端请求的时候会先本地持久化一次事务日志,这样事务管理器如果出现宕机,那么重启的时候可以根据事务日志进行恢复。同理,事务参与者也需要进行事务日志持久化。
事务管理器和事务参与者应该把相互之间的请求响应消息也写进日志中,举个例子:
数据库1参与了分布式事务,而也不幸的发生了宕机问题。在数据库1重启以后读取事务日志,如果接收到了事务管理器commit指令,那么就commit事务。如果接受到rollback指令,那么就rollback事务。如果还是prepare阶段的yes,这时候可以主动向事务管理器询问一下指令,如果事务管理器并没有需要向你发送的指令,那么应该还未进入第二阶段就宕机了。
那如果是事务管理器发生宕机重启,并读取事务日志。如果连prepare阶段的yes都没有,可以先进行一次询问,看看参与者是否有需要返回的事务状态。如果没有全部yes,那么rollback事务,否则commit事务。
2pc比起1pc来看主要是增加了一个事务管理器,用来协调多个事务资源。以及,将commit拆分成了prepare和commit两个阶段,prepare存在的意义就是可以做回滚,不会像1pc一样无法挽回。不过说白了2pc本质上就是让管理者来控制参与者的投票,如果全票通过,那么事务全部commit,然后程序继续运行。
2pc的交互、持久化都非常比1pc多了很多,这样就注定2pc的效率会下降很多。同时,2pc事务管理器和事务资源参与者之间存在很多可能地阻塞等待地地方,这将会导致资源的不可用问题,从而程序不可用的问题。
另外,虽说2pc利用阻塞做到了一定程度的强一致性,但是在遇到宕机问题、网络隔离问题,并无法马上恢复的时候数据的强一致性并不能够真正的保证。
总的来说,2pc提供了分布式场景下协调多个事务参与者的协议规范。但是,在高并发的使用场景下还是不太适用,毕竟增加了不少通信上的成本,以及阻塞问题带来的严重后果。