细品事物机制

Posted jeff-y

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了细品事物机制相关的知识,希望对你有一定的参考价值。

细品事物机制(二)

上一节讲了本地事物,我们先回顾一下,本地事物的事物是依靠底层数据库的支持实现,列如我们项目中的jdbc中统一封装的rollBack()方法以及结合AOP切面和事务的传播特性实现整个项目的事物机制。 今天我们主要聊的是全局事务。

什么是全局事务?

与本地事务相对的是全局事务,在上一节中,事务的区分可以根据数据源和是否单机进行区分。全局事务被限定为一种适用于单个服务使用多个数据源的事务解决方案。但是理论上的全局事务是没有单个服务的限制的,它本来就是 DTP(Distributed Transaction Processing)模型中的概念。 其实也就可以理解为全局事务就是分布式事务。

分布式事务

分布式事务一致性问题的解决

  • XA的解决方案,由Xopen组织提出一套名为X/open XA的处理事务的架构,其核心内容是定义了全局事务管理器(Transaction manager,用于协调全局事务)和局部的资源管理器(Resourse Manager,用于驱动本地事物)之间的通信接口。XA接口是双向的能在一个事物管理器 和多个资源管理器之间形成通信桥梁,通过协调多个数据源的一致动作,实现全局事物的统一提交或者统一回滚/

  • 但是对于XA来说是一种通用规范,在java中实现的技术规范就是我们常讲的JTA

  • JTA 最主要的两个接口是:

    • 事务管理器的接口:javax.transaction.TransactionManager。这套接口是给 Java EE 服务器提供容器事务(由容器自动负责事务管理)使用的,还提供了另外一套javax.transaction.UserTransaction接口,用于通过程序代码手动开启、提交和回滚事务。

    • 满足 XA 规范的资源定义接口:javax.transaction.xa.XAResource,任何资源(JDBC、JMS 等等)如果想要支持 JTA,只要实现 XAResource 接口中的方法即可。

    • example:在java中实现事务的方式有两种,1.声明式事物,2.编程式事物。我们使用声明式事物写一段为代码:

      public void buyBook(PaymentBill bill) 
          userTransaction.begin();
          warehouseTransaction.begin();
          businessTransaction.begin();
      	try 
              userAccountService.pay(bill.getMoney());
              warehouseService.deliver(bill.getItems());
              businessAccountService.receipt(bill.getMoney());
              userTransaction.commit();
              warehouseTransaction.commit();
              businessTransaction.commit();
      	 catch(Exception e) 
              userTransaction.rollback();
              warehouseTransaction.rollback();
              businessTransaction.rollback();
      	
      
      

    从上面代码中我们可以看到在进行 businessTransaction.commit()的时候异常时而前两个commit已经提交,catch中调用rollBack时已经提交无法回滚。为了解决这个问题,XA将事务提交分为两个阶段提交。

两阶段提交(2 Phase Commit,2PC)

  • 准备阶段:又叫做投票阶段,协调者询问事务的所有参与着是否准备好提交,参与者如果已经准备好提交恢复Prepared,否则恢复 Non-Prepared。这里所说的准备操作跟人类语言中通常理解的准备并不相同,对于数据库来说,准备操作是在重做日志中记录全部事务提交操作所要做的内容,它与本地事务中真正提交的区别只是暂不写入最后一条 Commit Record 而已,这意味着在做完数据持久化后并不立即释放隔离性,即仍继续持有锁,维持数据对其他非事务内观察者的隔离状态,

  • 提交阶段:又叫作执行阶段,协调者如果在上一阶段收到所有事务参与者回复的 Prepared 消息,则先自己在本地持久化事务状态为 Commit,在此操作完成后向所有参与者发送 Commit 指令,所有参与者立即执行提交操作;否则,任意一个参与者回复了 Non-Prepared 消息,或任意一个参与者超时未回复,协调者将将自己的事务状态持久化为 Abort 之后,向所有参与者发送 Abort 指令,参与者立即执行回滚操作。对于数据库来说,这个阶段的提交操作应是很轻量的,仅仅是持久化一条 Commit Record 而已,通常能够快速完成,只有收到 Abort 指令时,才需要根据回滚日志清理已提交的数据,这可能是相对重负载操作。

  • 还需要其他的条件保证一致性:

    • 必须假设网络在提交阶段的短时间内是可靠的,即提交阶段不会丢失消息。同时也假设网络通信在全过程都不会出现误差,即可以丢失消息,但不会传递错误的消息,XA 的设计目标并不是解决诸如拜占庭将军一类的问题。两段式提交中投票阶段失败了可以补救(回滚),而提交阶段失败了无法补救(不再改变提交或回滚的结果,只能等崩溃的节点重新恢复),因而此阶段耗时应尽可能短,这也是为了尽量控制网络风险的考虑。

    • 必须假设因为网络分区、机器崩溃或者其他原因而导致失联的节点最终能够恢复,不会永久性地处于失联状态。由于在准备阶段已经写入了完整的重做日志,所以当失联机器一旦恢复,就能够从日志中找出已准备妥当但并未提交的事务数据,再而向协调者查询该事务的状态,确定下一步应该进行提交还是回滚操作 。

    两阶段提交的缺点:

    • 单点问题:协调者在两段提交中具有举足轻重的作用,协调者等待参与者回复时可以有超时机制,允许参与者宕机,但参与者等待协调者指令时无法做超时处理。一旦宕机的不是其中某个参与者,而是协调者的话,所有参与者都会受到影响。如果协调者一直没有恢复,没有正常发送 Commit 或者 Rollback 的指令,那所有参与者都必须一直等待。可以根据上图可知,是协调者主动发起的准备,随意这一过程没有一个超时的过程

    • 性能问题:两段提交过程中,所有参与者相当于被绑定成为一个统一调度的整体,期间要经过两次远程服务调用,三次数据持久化(准备阶段写重做日志,协调者做状态持久化,提交阶段在日志写入 Commit Record),整个过程将持续到参与者集群中最慢的那一个处理操作结束为止,这决定了两段式提交的性能通常都较差。

    • 一致性风险:前面已经提到,两段式提交的成立是有前提条件的,当网络稳定性和宕机恢复能力的假设不成立时,仍可能出现一致性问题。宕机恢复能力这一点不必多谈,1985 年 Fischer、Lynch、Paterson 提出了“FLP 不可能原理”,证明了如果宕机最后不能恢复,那就不存在任何一种分布式协议可以正确地达成一致性结果。该原理在分布式中是与“CAP 不可兼得原理“齐名的理论。而网络稳定性带来的一致性风险是指:尽管提交阶段时间很短,但这仍是一段明确存在的危险期,如果协调者在发出准备指令后,根据收到各个参与者发回的信息确定事务状态是可以提交的,协调者会先持久化事务状态,并提交自己的事务,如果这时候网络忽然被断开,无法再通过网络向所有参与者发出 Commit 指令的话,就会导致部分数据(协调者的)已提交,但部分数据(参与者的)既未提交,也没有办法回滚,产生了数据不一致的问题。

三阶段提交

  • 为了缓解两段式提交协议的一部分缺陷,具体地说是协调者的单点问题和准备阶段的性能问题,后续又发展出了“三段式提交”(3 Phase Commit,3PC)协议。三段式提交把原本的两段式提交的准备阶段再细分为两个阶段,分别称为 CanCommit、PreCommit,把提交阶段改称为 DoCommit 阶段。其中,新增的 CanCommit 是一个询问阶段,协调者让每个参与的数据库根据自身状态,评估该事务是否有可能顺利完成。将准备阶段一分为二的理由是这个阶段是重负载的操作,一旦协调者发出开始准备的消息,每个参与者都将马上开始写重做日志,它们所涉及的数据资源即被锁住,如果此时某一个参与者宣告无法完成提交,相当于大家都白做了一轮无用功。所以,增加一轮询问阶段,如果都得到了正面的响应,那事务能够成功提交的把握就比较大了,这也意味着因某个参与者提交时发生崩溃而导致大家全部回滚的风险相对变小。因此,在事务需要回滚的场景中,三段式的性能通常是要比两段式好很多的,但在事务能够正常提交的场景中,两者的性能都依然很差,甚至三段式因为多了一次询问,还要稍微更差一些。

  • 同样也是由于事务失败回滚概率变小的原因,在三段式提交中,如果在 PreCommit 阶段之后发生了协调者宕机,即参与者没有能等到 DoCommit 的消息的话,默认的操作策略将是提交事务而不是回滚事务或者持续等待,这就相当于避免了协调者单点问题的风险。三段式提交的操作时序如下。

    从以上过程可以看出,三段式提交对单点问题和回滚时的性能问题有所改善,但是它对一致性风险问题并未有任何改进,在这方面它面临的风险甚至反而是略有增加了的。譬如,进入 PreCommit 阶段之后,协调者发出的指令不是 Ack 而是 Abort,而此时因网络问题,有部分参与者直至超时都未能收到协调者的 Abort 指令的话,这些参与者将会错误地提交事务,这就产生了不同参与者之间数据不一致的问题。
    

总结

  • 简单回顾上一届的本地事物的内容

  • 全局事物我们也就认为他是分布式事务

  • XA规范地提出,以及java 使用XA规范实现的JTA

  • 使用Java的编程式事物遇到的不一致问题,当有事物有异常的时候,只能回滚部分,导致不一致

  • XA事物的两阶段提交规范,多个数据源本身有多个参与者和一个协调者,协调者通过准备和提交阶段达到上面编程式事务所遇到的问题。
    - 两阶段提交一致性的前提条件是达到网络一直可用,以及两阶段提交的问题:单点,性能,一致性风险。
    - 为了解决二阶段提交的风险提出三阶段提交。但是两阶段和三阶段都在性能上存在问题。

    ## 学习资料(凤凰架构)
    
    强烈推荐给大家(电子版):
    
    - http://icyfenix.cn/architect-perspective/general-architecture/transaction/global.html
    

以上是关于细品事物机制的主要内容,如果未能解决你的问题,请参考以下文章

细品事务机制

细品mysql的事务隔离机制

细品事务机制-分布式事务

细品事务机制-分布式事务

mysql的事务和引擎,注意细品

分布式事务介绍