上一节讲了本地事物,我们先回顾一下,本地事物的事物是依靠底层数据库的支持实现,列如我们项目中的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将事务提交分为两个阶段提交。
两阶段提交的缺点:
为了缓解两段式提交协议的一部分缺陷,具体地说是协调者的单点问题和准备阶段的性能问题,后续又发展出了“三段式提交”(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