到目前为止,对于事务的讨论基本上都聚焦在本地事务上,本地事务只会涉及到一个单一的数据源。本章开始介绍分布式事务,分布式事务会在单个事务内涉及多个数据源。以下内容主要包括:

  • 分布识事务基础设施
  • 事务管理器和资源管理器
  • XADataSourceXAConnectionXAResource 接口
  • 两阶段提交

JDBC 的事务管理 API 与 JTA 规范是兼容的。

基础设施

分布式事务的基础设施包括以下几个部分:

  • 事务管理器,用来控制事务的边界和管理两阶段提交协议。它也应该是 JTA 的一个经典实现。
  • 实现了XADataSourceXAConnectionXAResource 接口的 JDBC 驱动。
  • 一个对于应用层完全可见的 DataSource 实现,利用它来操作 XADataSource ,并与事务管理器交互。通常这个实现由应用服务器提供。
  • 用来管理底层数据的资源管理器。在 JDBC 的语境中,资源管理器指的是数据库服务器。“资源管理器” 这个术语实际上来自于 JTA,在这里使用这个术语是为了强调 JDBC 中的分布式事务是遵循 JTA 规范的架构来处理的。

通常会以“三层架构”来实现这个基础设施,包括:

  1. 客户端
  2. 一个包含应用程序、EJB 服务器、JDBC 驱动集合的中间层
  3. 多个资源管理器

分布式事务也可以实现为“两层架构”。在两层架构中,应用层本身就会扮演事务管理器的角色,并且直接操作 XADataSource API。下图阐述了分布式事务的基础设施:

image-20210613150204476

后续的内容将会对基础设施的各个部分进行详细的说明

XADataSourceXAConnection

XADataSourceXAConnection 接口,定义在 javax.sql 包中。支持分布式事务的数据库驱动需要实现这两个接口。XAConnection 继承了 PooledConnection 接口,添加了一个 getXAResource 方法,这个方法会生成一个 XAResource 对象,事务管理器可以利用这个对象来完成分布式事务。以下是 XAConnection 接口的定义:

public interface XAConnection extends PooledConnection {
javax.transaction.xa.XAResource getXAResource() 
throws SQLException;
}

因为继承了 PooledConnection 接口,所以所有的 XAConnection 对象也支持 PooledConnection 中定义的方法。这些对象代表着与底层数据源的一条可重用的物理连接,应用层也可以通过这个对象操作这条连接。

XAConnection 对象由 XADataSource 生成。ConnectionPoolDataSourceXADataSource 有一些相似的地方,他们都实现了 DataSource 接口。这样就允许分布式事务的底层实现对于应用层来说是透明的。XADataSource 接口定义如下:

public interface XADataSource {
XAConnection getXAConnection() throws SQLException;
XAConnection getXAConnection(String user, 
String password) throws SQLException;
//...
}

通常,一个基于 XADataSource 之上的 DataSource 实现,也会包含一个连接池化的模块。

部署 XADataSource 对象

部署一个 XADataSource 与先前所提到的 ConnectionPoolDataSource 是一样的。流程总共分两步,如下代码所示:

// com.acme.jdbc.XADataSource implements the 
// XADataSource interface.
// Create an instance and set properties.
com.acme.jdbc.XADataSource xads = new com.acme.jdbc.XADataSource();
xads.setServerName(“bookstore”);
xads.setDatabaseName(“bookinventory”);
xads.setPortNumber(9040);
xads.setDescription(“XADataSource for inventory”);
// First register xads with a JNDI naming service, using the
// logical name “jdbc/xa/inventory_xa”
Context ctx = new InitialContext();
ctx.bind(“jdbc/xa/inventory_xa”, xads);
// Next register the overlying DataSource object for application
// access. com.acme.appserver.DataSource is an implementation of
// the DataSource interface.
// Create an instance and set properties. 
com.acme.appserver.DataSource ds = 
new com.acme.appserver.DataSource();
ds.setDescription(“Datasource supporting distributed transactions”);
// Reference the previously registered XADataSource
ds.setDataSourceName(“jdbc/xa/inventory_xa”);

// Register the DataSource implementation with a JNDI naming service,
// using the logical name “jdbc/inventory”.
ctx.bind(“jdbc/inventory”, ds);

获取连接

调用 DataSource.getConnection 方法返回一个底层实现为 XAConnection 的逻辑 Connection 对象:

Context ctx = new InitialContext();
DataSource ds = (DataSource)ctx.lookup(“jdbc/inventory”);
Connection con = ds.getConnection(“myID”,“mypasswd”);

DataSource.getConnection 方法的底层实现如下所示:

// Assume xads is a driver’s implementation of XADataSource
XADataSource xads = (XADataSource)ctx.lookup(“jdbc/xa/" +
"inventory_xa”);
// xacon implements XAConnection
XAConnection xacon = xads.getXAConnection(“myID”, “mypasswd”);
// Get a logical connection to pass back up to the application
Connection con = xacon.getConnection();
CODE EXAMPLE 12-5 Getting a logical connection from an

XAResource

XAResource 接口是 JTA 规范中的一个定义,也是 Java 语言中对应 X/Open 组织的 XA 的对等概念。 XAResource 对象通过调用 XAConnection.getXAResource 方法获得,通过调用该方式将 XAConnection 与一个分布式事务绑定。同一时刻,一个 XAConnection 只能与一个分布式事务绑定。JDBC 驱动应维护 XAResourceXAConnection 的一对一关系,也就是说对于 getXAResource 方法的多次调用,都返回同一个 XAResource 对象。

通常,中间应用服务器调用 XAConnection.getXAResource 方法得到一个 XAResource 对象后,会将它交给外部的事务管理器,事务管理器并不需要直接与 XAConnection 交互,它直接利用 XAResource 对象。

事务管理器管理多个 XAResource 对象,每一个都代表一个参与到分布式事务的资源管理器,注意,不同的 XAResource 对象可能指向同一个资源管理器,当不同的事务参与者使用同一个 XADataSource ,就有可能出现这种情况。

XAResource 定义了以下的方法,来完成两阶段提交协议,每个方法都要求有一个 xid 参数,用来标识一个分布式事务。

  • start 方法。通知资源管理器后续的操作来处于一个分布式事务中。
  • end 方法。通知资源管理器事务结束。
  • prepare 方法。获取资源管理器关于事务应该回滚还是提交的投票。
  • commit 方法。通知资源管理器提交它的事务分支。只有当所有的参与者都投票提交全局事务时,这个方法才能被调用。
  • rollback 方法。通知资源管理器回滚它的事务分支。只有当至少一个事务参与者投票回滚全局事务时,这个方法才会被调用。

JTA 规范中有对 XAResource 完整的描述。

事务管理

通过 XAResource.start 和 XAResource.end 方法来定义事务边界。边界内事务模式为全局事务。边界外的事务为本地事务。

除了一些约束,一个事务参与者的应用代码应该怎么写,与它是否参与分布式事务没有什么关系。分布式事务的边界一般都是由外部的事务管理者来定义的,事务的参与者不能调用 Connection 类的某些方法:

  • setAutoCommit(true)
  • commit
  • rollback
  • setSavepoint

如果事务参与者试图调用这些方法,JDBC 驱动应抛出 SQLException。当分布式事务结束后,这些方法的调用就不再有限制,并且应用于本地事务。

在事务边界内,事务参与者应该避免调用 Connection.setTransactionIsolation 方法,这个方法的行为不做约束,由驱动自主决定。

如果一个连接在参与分布式事务前的 autocommit 属性已经为 true,那么当它参与分布式事务时,这个属性会被忽略,当事务结束后,属性才重新生效。

两阶段提交

以下几个步骤阐述了事务管理器如何利用 XAResource 对象实现二阶段提交协议。这些步骤是基于使用具有外部事务管理器的应用服务器的“三层架构”实现的。

  1. 应用服务器从两个不同的连接中获取 XAResource 对象

    // XAConA connects to resource manager A
    javax.transaction.xa.XAResource resourceA = XAConA.getXAResource();
    // XAConB connects to resource manager B 
    javax.transaction.xa.XAResource resourceB = XAConB.getXAResource();
  2. 应用服务器传递 XAResource 对象给事务管理器,事务管理器不直接与 XAConnection 对象交互。
  3. 事务管理器利用 XAResource 将资源管理器纳入事务中,整个事务以一个 xid 作为标识,由事务管理器在启动事务时负责生成。

    // Send work to resource manager A. The TMNOFLAGS argument indicates
    // we are starting a new branch of the transaction, not joining or 
    // resuming an existing branch. 
    resourceA.start(xid, javax.transaction.xa.TMNOFLAGS);
    // do work with resource manager A
    ...
    // tell resource manager A that it’s done, and no errors have occurred 
    resourceA.end(xid, javax.transaction.xa.TMSUCCESS);
    // do work with resource manager B.
    resourceB.start(xid, javax.transaction.xa.TMNOFLAGS);
    // B’s part of the distributed transaction
    ...
    resourceB.end(xid, javax.transaction.xa.TMSUCCESS);
  4. 事务管理器启动两阶段提交协议,请求两个参与者进行投票:

    resourceA.prepare(xid);
    resourceB.prepare(xid);

如果一个事务参与者想要投票 rollback,它需要抛出一个 javax.transaction.xa.XAException

  1. 如果两个事务参与者都投票 commit,事务参与者通知两个参与者提交他们的事务分支:

    resourceA.commit(xid, false);
    resourceB.commit(xid, false);
  2. 如果任何一个事务参与者投票 rollback,事务管理器则通知各个参与者回滚它们的事务分支:

    resourceA.rollback(xid);
    resourceB.rollback(xid);

事务管理器在处理一个事务分支的不同阶段的时候,不一定要用的是同一个 XAResource 对象,只要这两个对象的连接是来自于同一个事务管理器。

连接关闭

当在分布式事务环境中,应用层使用完一条连接后,中间层服务器应该得到通知。在先前对 PooledConnection 对象的讨论中,我们指出了当 Connection.close 方法被调用时,中间件服务器会作为一个 ConnectionEventListener 得到通知。在这时,事务管理器也会得到通知,并且结束对应的事务分支。如果 DataSource 包含池化模块,那么池化模块也必须得到通知,以便将 XAConnection 归还。

注意,即使一个连接被关闭,分布式事务也依然可以处于活跃状态。

XAResource 接口的局限性

XAResource 接口只定义了 X/Open XA 规范中要求定义的方法。如果一个资源管理器支持了一些在 XA 规范中没有定义的特性,那么应用层无法明显地通过 API 去使用这些特性,只能交给具体的驱动在底层去处理。如果应用间接利用了这个特性,这又会带来一个可移植性的问题。


ytbean
3.1k 声望714 粉丝

十年学会编程


引用和评论

0 条评论