为了实现分布式事务,人们提出了分两个阶段提交事务,以及改进版,分三个阶段提交事务。
为了实现不同事务参与者之间事务的一致性,需要有一个统一的事务管理者,来对这些参与者进行统一的事务管理。
两阶段提交过程大致如下:
准备阶段
- 管理者向参与者发起事务请求,询问是否可以执行事务。
- 参与者执行事务,写undo日志和redo日志,并返回事务执行成功或失败。
提交阶段
- 管理者接到参与者返回的信息,如果有一个失败,发送rowback消息,所有参与者回滚事务,返回ack响应;如果全部成功,发送commit消息,所有参与者提交事务,返回ack响应。
- 最终释放参与者在事务中占用的锁资源。
缺点是:
- 同步阻塞: 参与者在整个阶段都是事务阻塞型,当参与者占用公共资源时,其他第三方节点访问公共资源的时候不得不处于阻塞状态。
- 单点故障: 如果管理者挂掉了,参与者就会一直处于阻塞状态,尤其是第二阶段,参与者处于锁定事务占用资源的状态。新的管理者也不知道哪些节点执行成功,哪些失败了。
- 数据不一致: 管理者发送commit消息后,由于网络等原因,一部分节点收到了消息,执行了commit命令,而一部分没有收到该消息,此时就会出现数据不一致的现象。
为了应对这些缺点,提出了三阶段提交方案,过程如下:
1. CanCommit
- 管理者发送CanCommit请求,询问能否提交事务。
- 参与者接收CanCommit请求,如果认为自己能提交事务,返回yes,并进入预提交状态,否则返回no。
2. PreCommit
- 管理者发送PreCommit请求。
- 参与者接收PreCommit请求,执行事务,并将undo,redo信息记录到日志文件中。
- 如果执行成功就返回ack响应,等待最终的指令。
如果有一个节点返回no响应,或者超时后仍然没有收到ack响应,则开始执行任务中断。
- 管理者向所有参与者发送absorb请求。
- 参与者收到absorb请求(或者超时后,仍未收到管理者的请求),执行事务中断。
3. DoCommit
- 管理者接收到所有参与者的ack响应,从预提交阶段进入到提交阶段,发送DoCommit请求。
- 参与者接收到DoCommit请求,开始提交事务,释放所有的资源。
- 成功提交的参与者向管理者发送ack响应。
- 管理者收到所有参与者的ack响应,完成事务。
如果管理者收到的不是ack响应,或者响应超时,开始执行事务中断。
- 管理者项目所有参与者发送absorb请求。
- 参与者收到absorb请求,回滚事务,释放资源。
- 所有的参与者完成事务回滚,返回ack响应。
- 管理者接收到所有的参与者ack请求,执行事务中断。
三阶段提交主要解决了单点故障问题,并减少阻塞。如果在第三阶段参与者长时间不能得到管理者发送的消息,默认执行commit操作。不过这样会导致数据一致性问题,如:网络不好,管理者的absorb请求及时发送过来,而这个节点却执行了commit操作。
三阶段提交为管理者和参与者都提供了超时机制。