欢迎您访问 最编程 本站为您分享编程语言代码,编程技术文章!
您现在的位置是: 首页

深入理解分布式事务:JTA的核心机制与实践解析(第三部分)

最编程 2024-07-23 12:27:04
...

著作权归作者所有,任何形式的转载都请联系作者获得授权并注明出处。

什么是JTA

Java 事务编程接口(JTA:Java Transaction API)和 Java 事务服务 (JTS;Java Transaction Service) 为 J2EE 平台提供了分布式事务服务。分布式事务(Distributed Transaction)包括事务管理器(Transaction Manager)和一个或多个支持 XA 协议的资源管理器 ( Resource Manager )。

资源管理器( Resource Manager ) 可以将看做任意类型的持久化数据存储。

事务管理器(Transaction Manager) 承担着所有事务所参与单元的协调与控制。

JTA 事务有效的屏蔽了底层事务资源,使应用可以以透明的方式参入到事务处理中;但是与本地事务相比,XA 协议的系统开销大,在系统开发过程中应慎重考虑是否确实需要分布式事务。若确实需要分布式事务以协调多个事务资源,则应实现和配置所支持 XA 协议的事务管理器,如 JMS、JDBC 数据库连接池等。

JTA事务管理的弊端

  • 两阶段提交
  • 事务时间太长、锁数据的时间太长
  • 低性能、低吞吐量

不使用JTA事务管理的优点

  • Spring 事务同步机制
  • 多个数据源上实现近似事务一致性(非强一致)
  • 高性能、高吞吐量

使用 JTA 处理事务的示例如下(注意:connA 和 connB 是来自不同数据库的连接)

public void transferAccount() { 
        UserTransaction userTx = null; 
        Connection connA = null; 
        Statement stmtA = null; 
                
        Connection connB = null; 
        Statement stmtB = null; 
    
        try{ 
            ## 获得 Transaction 管理对象
            userTx = (UserTransaction)getContext().lookup("\java:comp/UserTransaction"); 
            ## 从数据库 A 中取得数据库连接
            connA = getDataSourceA().getConnection(); 
            
            ## 从数据库 B 中取得数据库连接
            connB = getDataSourceB().getConnection(); 
      
            ## 启动事务
            userTx.begin();
            
            ## 将 A 账户中的金额减少 500 
            stmtA = connA.createStatement(); 
            stmtA.execute("update t_account set amount = amount - 500 where account_id = 'A'");
            
            ## 将 B 账户中的金额增加 500 
            stmtB = connB.createStatement(); 
            stmtB.execute("\update t_account set amount = amount + 500 where account_id = 'B'");
            
            ## 提交事务
            userTx.commit();
            ## 事务提交:转账的两步操作同时成功(数据库 A 和数据库 B 中的数据被同时更新)
        } catch(SQLException sqle){ 
            try{ 
                 ## 发生异常,回滚在本事务中的操纵
                 userTx.rollback();
                ## 事务回滚:转账的两步操作完全撤销 
                ##( 数据库 A 和数据库 B 中的数据更新被同时撤销)
                stmt.close(); 
                conn.close(); 
                ## 剩余业务代码 
            }catch(Exception ignore){ } 
            sqle.printStackTrace(); 
            
        } catch(Exception ne){ 
            e.printStackTrace(); 
        } 
    }

外部(全局)事务-JTA

  • 外部事务管理器提供JTA事务管理
  • JTA事务管理器可以管理多个数据资源
  • 通过2阶段提交实现多数据源的事务

事务机制过程图

外部(全局)事务(调用应用程序的JTA管理器)

1657ac608f6389ff_tplv-t2oaga2asx-zoom-in-crop-mark_4536_0_0_0.png

外部(全局)事务(调用Spring的JTA管理器)

1657ac65c2f77952_tplv-t2oaga2asx-zoom-in-crop-mark_4536_0_0_0.png

XA

1657aca496b37327_tplv-t2oaga2asx-zoom-in-crop-mark_4536_0_0_0.png

JTA

1657aca7f0817a83_tplv-t2oaga2asx-zoom-in-crop-mark_4536_0_0_0.png

JTA 实现原理

很多开发人员都会对 JTA 的内部工作机制感兴趣:我们大多时候编写的代码没有任何与事务资源(如数据库连接)互动的代码,但是我的操作(数据库更新)却实实在在的被包含在了事务中,那 JTA 究竟是通过何种方式来实现这种透明性的呢?

JTA的事务管理器(Transaction Manager)和资源管理器(Resource Manager)

要理解 JTA 的实现原理首先需要了解其架构:

  • 事务管理器(Transaction Manager)
  • 一个或多个支持 XA 协议的资源管理器 ( Resource Manager )

我们可以将资源管理器看做任意类型的持久化数据存储介质;

事务管理器则承担着所有事务参与单元的协调与控制。

根据所面向对象的不同,我们可以将 JTA的事务管理器和资源管理器理解为两个方面:

  • 面向开发人员的使用接口(事务管理器)
  • 面向服务提供商的实现接口(资源管理器)

其中开发接口的主要部分即为上述示例中引用的 UserTransaction 对象,开发人员通过此接口在信息系统中实现分布式事务;而实现接口则用来规范提供商(如数据库连接提供商)所提供的事务服务,它约定了事务的资源管理功能,使得 JTA 可以在异构事务资源之间执行协同沟通。以数据库为例,IBM 公司提供了实现分布式事务的数据库驱动程序,Oracle 也提供了实现分布式事务的数据库驱动程序,在同时使用 DB2 和 Oracle 两种数据库连接时, JTA 即可以根据约定的接口协调这两种事务资源从而实现分布式事务。正是基于统一规范的不同实现使得 JTA 可以协调与控制不同数据库或者 JMS 厂商的事务资源,其JTA 体系架构如下图所示:

1657fa676b38e449_tplv-t2oaga2asx-zoom-in-crop-mark_4536_0_0_0.png

开发人员使用开发人员接口,实现应用程序对全局事务的支持;各提供商(数据库,JMS 等)依据提供商接口的规范提供事务资源管理功能;事务管理器( TransactionManager )将应用对分布式事务的使用映射到实际的事务资源并在事务资源间进行协调与控制。 下面,本文将对包括 UserTransactionTransactionTransactionManager 在内的三个主要接口以及其定义的方法进行介绍。

UserTransaction 和 Transaction

面向开发人员的接口为 UserTransaction (使用方法如上例所示),开发人员通常只使用此接口实现 JTA 事务管理,其定义了如下的方法:

  • begin()- 开始一个分布式事务,(在后台 TransactionManager 会创建一个 Transaction 事务对象并把此对象通过 * ThreadLocale 关联到当前线程上 )
  • commit()- 提交事务(在后台 TransactionManager 会从当前线程下取出事务对象并把此对象所代表的事务提交)
  • rollback()- 回滚事务(在后台 TransactionManager 会从当前线程下取出事务对象并把此对象所代表的事务回滚)
  • getStatus()- 返回关联到当前线程的分布式事务的状态 (Status 对象里边定义了所有的事务状态,感兴趣的读者可以参考 API 文档 )
  • setRollbackOnly()- 标识关联到当前线程的分布式事务将被回滚

面向提供商的实现接口主要涉及到 TransactionManager 和 Transaction 两个对象;

Transaction 代表了一个物理意义上的事务,在开发人员调用 UserTransaction.begin() 方法时 TransactionManager 会创建一个 Transaction 事务对象(标志着事务的开始)并把此对象通过 ThreadLocale 关联到当前线程。   UserTransaction 接口中的 commit()、rollback(),getStatus() 等方法都将最终委托给 Transaction 类的对应方法执行。

Transaction 接口定义了如下的方法:

  • commit()- 协调不同的事务资源共同完成事务的提交
  • rollback()- 协调不同的事务资源共同完成事务的回滚
  • setRollbackOnly()- 标识关联到当前线程的分布式事务将被回滚
  • getStatus()- 返回关联到当前线程的分布式事务的状态
  • enListResource(XAResource xaRes, int flag)- 将事务资源加入到当前的事务中(在上述示例中,在对数据库 A 操作时 其所代表的事务资源将被关联到当前事务中,同样,在对数据库 B 操作时其所代表的事务资源也将被关联到当前事务中)
  • delistResourc(XAResource xaRes, int flag)- 将事务资源从当前事务中删除
  • registerSynchronization(Synchronization sync)- 回调接口

Hibernate 等 ORM 工具都有自己的事务控制机制来保证事务,但同时它们还需要一种回调机制以便在事务完成时得到通知从而触发一些处理工作,如清除缓存等。这就涉及到了 Transaction 的回调接口 registerSynchronization。工具可以通过此接口将回调程序注入到事务中,当事务成功提交后,回调程序将被激活。 TransactionManager 本身并不承担实际的事务处理功能,它更多的是充当用户接口和实现接口之间的桥梁。下面列出了 TransactionManager 中定义的方法,可以看到此接口中的大部分事务方法与 UserTransaction 和 Transaction 相同。

在开发人员调用 UserTransaction.begin() 方法时 TransactionManager 会创建一个 Transaction 事务对象(标志着事务的开始)并把此对象通过 ThreadLocale 关联到当前线程上;同样 UserTransaction.commit() 会调用 TransactionManager.commit(), 方法将从当前线程下取出事务对象 Transaction 并把此对象所代表的事务提交, 即调用 Transaction.commit()TransactionManager定义了如下的方法:

  • begin()- 开始事务
  • commit()- 提交事务
  • rollback()- 回滚事务
  • getStatus()- 返回当前事务状态
  • setRollbackOnly()
  • getTransaction()- 返回关联到当前线程的事务
  • setTransactionTimeout(int seconds)- 设置事务超时时间
  • resume(Transaction tobj)- 继续当前线程关联的事务
  • suspend()- 挂起当前线程关联的事务

在系统开发过程中会遇到需要将事务资源暂时排除的操作,此时就需要调用 suspend() 方法将当前的事务挂起:在此方法后面所做的任何操作将不会被包括在事务中,在非事务性操作完成后调用 resume() 以继续事务(注: 要进行此操作需要获得 TransactionManager 对象, 其获得方式在不同的 J2EE 应用服务器上是不一样的)

下面将通过具体的代码向读者介绍 JTA 实现原理。下图列出了示例实现中涉及到的 Java 类,其中 UserTransactionImpl 实现了 UserTransaction 接口,TransactionManagerImpl 实现了 TransactionManager 接口,TransactionImpl 实现了 Transaction 接口

JTA 实现类图:

5c75005c4fcf98605d84328b363c003.png

## 开始事务
public class UserTransactionImpl implenments UserTransaction{
    public void begin() throws NotSupportedException, SystemException { 
     ## 将开始事务的操作委托给 TransactionManagerImpl 
         TransactionManagerImpl.singleton().begin(); 
     }
}
## 开始事务
public class TransactionManagerImpl implements TransactionManager{
## 此处 transactionHolder 用于将 Transaction 所代表的事务对象关联到线程上
    private static ThreadLocal<TransactionImpl> transactionHolder = new ThreadLocal<TransactionImpl>(); 
    ## TransacationMananger 必须维护一个全局对象,因此使用单实例模式实现
     private static TransactionManagerImpl singleton = new TransactionManagerImpl(); 
     
     private TransactionManagerImpl(){ } 
     
     public static TransactionManagerImpl singleton(){ 
         return singleton; 
     } 
 
     public void begin() throws NotSupportedException, SystemException { 
         ## XidImpl 实现了 Xid 接口,其作用是唯一标识一个事务
         XidImpl xid = new XidImpl(); 
         ## 创建事务对象,并将对象关联到线程
         TransactionImpl tx = new TransactionImpl(xid); 
         transactionHolder.set(tx); 
     }
}

现在我们就可以理解 Transaction 接口上没有定义 begin 方法的原因了:Transaction 对象本身就代表了一个事务,在它被创建的时候就表明事务已经开始,因此也就不需要额外定义 begin() 方法了。

##提交事务
public class UserTransactionImpl implenments UserTransaction{
    public void commit() throws RollbackException, HeuristicMixedException, 
        HeuristicRollbackException, SecurityException, 
        IllegalStateException, SystemException { 
        
        // 检查是否是 Roll back only 事务,如果是回滚事务
           if(rollBackOnly){ 
            rollback(); 
            return; 
          } else { 
           // 将提交事务的操作委托给 TransactionManagerImpl 
           TransactionManagerImpl.singleton().commit(); 
          } 
    }
}
## 提交事务
public class TransactionManagerImpl implenments TransactionManager{
public void commit() throws RollbackException, HeuristicMixedException, 
    HeuristicRollbackException, SecurityException, 
    IllegalStateException, SystemException { 
       ## 取得当前事务所关联的事务并通过其 commit 方法提交
       TransactionImpl tx = transactionHolder.get(); 
       tx.commit(); 
    }
}

同理, rollback、getStatus、setRollbackOnly 等方法也采用了与 commit() 相同的方式实现。 UserTransaction 对象不会对事务进行任何控制, 所有的事务方法都是通过 TransactionManager 传递到实际的事务资源即 Transaction 对象上。

上述示例演示了 JTA 事务的处理过程,下面将为您展示事务资源(数据库连接,JMS)是如何以透明的方式加入到 JTA 事务中的。首先需要明确的一点是,在 JTA 事务代码中获得的数据库源 ( DataSource ) 必须是支持分布式事务的。在如下的代码示例中,尽管所有的数据库操作都被包含在了 JTA 事务中,但是因为 MySql 的数据库连接是通过本地方式获得的,对 MySql 的任何更新将不会被自动包含在全局事务中。

上一篇: 开发与Openfire开源聊天服务器集成的聊天记录插件指南

下一篇: jive论坛2

推荐阅读