终于有人把“分布式事务”说清楚了( 三 )


三阶段提交的三个阶段:

  • CanCommit
  • PreCommit
  • DoCommit
  •  

终于有人把“分布式事务”说清楚了

文章插图
 
 ①询问阶段:CanCommit
协调者向参与者发送 Commit 请求,参与者如果可以提交就返回 Yes 响应,否则返回 No 响应 。
②准备阶段:PreCommit
协调者根据参与者在询问阶段的响应判断是否执行事务还是中断事务:
  • 如果所有参与者都返回 Yes,则执行事务 。
  • 如果参与者有一个或多个参与者返回 No 或者超时,则中断事务 。
参与者执行完操作之后返回 ACK 响应,同时开始等待最终指令 。
③提交阶段:DoCommit
协调者根据参与者在准备阶段的响应判断是否执行事务还是中断事务:
  • 如果所有参与者都返回正确的 ACK 响应,则提交事务 。
  • 如果参与者有一个或多个参与者收到错误的 ACK 响应或者超时,则中断事务 。
  • 如果参与者无法及时接收到来自协调者的提交或者中断事务请求时,会在等待超时之后,会继续进行事务提交 。
协调者收到所有参与者的 ACK 响应,完成事务 。
解决二阶段提交时的问题:在三阶段提交中,如果在第三阶段协调者发送提交请求之后挂掉,并且唯一的接受的参与者执行提交操作之后也挂掉了,这时协调者通过选举协议产生了新的协调者 。
在二阶段提交时存在的问题就是新的协调者不确定已经执行过事务的参与者是执行的提交事务还是中断事务 。
但是在三阶段提交时,肯定得到了第二阶段的再次确认,那么第二阶段必然是已经正确的执行了事务操作,只等待提交事务了 。
所以新的协调者可以从第二阶段中分析出应该执行的操作,进行提交或者中断事务操作,这样即使挂掉的参与者恢复过来,数据也是一致的 。
所以,三阶段提交解决了二阶段提交中存在的由于协调者和参与者同时挂掉可能导致的数据一致性问题和单点故障问题,并减少阻塞 。
因为一旦参与者无法及时收到来自协调者的信息之后,他会默认执行提交事务,而不会一直持有事务资源并处于阻塞状态 。
三阶段提交的问题:在提交阶段如果发送的是中断事务请求,但是由于网络问题,导致部分参与者没有接到请求 。
那么参与者会在等待超时之后执行提交事务操作,这样这些由于网络问题导致提交事务的参与者的数据就与接受到中断事务请求的参与者存在数据不一致的问题 。
所以无论是 2PC 还是 3PC 都不能保证分布式系统中的数据 100% 一致 。
解决方案
举个栗子:在电商网站中,用户对商品进行下单,需要在订单表中创建一条订单数据,同时需要在库存表中修改当前商品的剩余库存数量 。
两步操作一个添加,一个修改,我们一定要保证这两步操作一定同时操作成功或失败,否则业务就会出现问题 。
建立时:业务量不大,用户少,系统只是一个单体架构,订单表与库存表都在一个数据库中,这时可以使用 MySQL 的本地事务保证数据一致性 。
 
终于有人把“分布式事务”说清楚了

文章插图
 
 
发展期:业务发展迅速,用户量变多,单数据已经出现了性能瓶颈,按照业务纬度进行分库,分为订单库和库存库,由于跨库跨机器,MySQL 的本地事务不能再保证订单库和库存库的数据一致性 。
 
终于有人把“分布式事务”说清楚了

文章插图
 
 
成熟期:业务拓展,单体架构已经满足不了需求,进而衍化成了分布式系统,这时的订单和库存已经拆分为了两个子系统提供服务,子系统间使用 RPC 进行通信 。
但是无论系统发展成什么样,我们都要保证业务不出问题,保证订单和库存的数据一致,这时候要思考下在服务之间我们应如何保证数据一致 。
 
终于有人把“分布式事务”说清楚了

文章插图
 
 
强一致性分布式事务
单体架构多数据源,在业务开发中,肯定是先执行对订单库的操作,但是不提交事务,再执行对库存库的操作,也不提交事务,如果两个操作都成功,在一起提交事务,如果有一个操作失败,则两个都进行回滚 。
基于 2PC/XA 协议实现的 JTA:我们已经知道了 2PC 和 XA 协议的原理,而 JTA 是 JAVA 规范,是 XA 在 Java 上的实现 。


推荐阅读