Day06 Druid 的事务实现
今天分析下 pool 下的 xa 包:package com.alibaba.druid.pool.xa;
一、XA 协议
XA 协议是由 X/Open 组织提出的分布式事务处理规范,主要定义了事务管理器 TM 和局部资源管理器 RM 之间的接口。Mysql 从 5.0 版本开始,InnoDB 存储引擎已经支持 XA 协议。
1. 两阶段提交
分布式事务的两阶段提交是把整个事务提交分为 prepare 和 commit 两个阶段:
第一阶段,事务协调者向事务参与者发送 prepare 请求,事务参与者收到请求后,如果可以提交事务,回复 yes,否则回复 no。
第二阶段,如果所有事务参与者都回复了 yes,事务协调者向所有事务参与者发送 commit 请求,否则发送 rollback 请求。
两阶段提交存在三个问题:
- 同步阻塞,本地事务在 prepare 阶段锁定资源,如果有其他事务需要访问锁定的资源,就必须等待前面的事务完成。这样就造成了系统性能下降。
- 协调节点单点故障,如果第一个阶段 prepare 成功了,但是第二个阶段协调节点发出 commit 指令之前宕机了,所有服务的数据资源处于锁定状态,事务将无限期地等待。
- 数据不一致,如果第一阶段 prepare 成功了,但是第二阶段协调节点向某个节点发送 commit 命令时失败,就会导致数据不一致。
2. 三阶段提交
为了解决两阶段提交的问题,三阶段提交做了改进:
- 协调节点、事务参与者都引入了超时机制
- 第一阶段的 prepare 阶段分成了两步,canCommi 和 preCommit。
引入 preCommit 阶段后,协调节点会在 commit 之前再次检查各个事务参与者的状态,保证它们的状态是一致的。但是也存在问题,那就是如果第三阶段发出 rollback 请求,有的节点没有收到,那没有收到的节点会在超时之后进行提交,造成数据不一致。
接下来看看 Druid 中 分布式事务的实现。
二、JtdsXAResource
类
JtdsXAResource
类实现了 XAResource
接口, XAResource
接口位于 javax.transaction.xa
包下,提供了事务相关的功能封装:
package javax.transaction.xa;
public interface XAResource {
int TMENDRSCAN = 8388608;
int TMFAIL = 536870912;
int TMJOIN = 2097152;
int TMNOFLAGS = 0;
int TMONEPHASE = 1073741824;
int TMRESUME = 134217728;
int TMSTARTRSCAN = 16777216;
int TMSUCCESS = 67108864;
int TMSUSPEND = 33554432;
int XA_RDONLY = 3;
int XA_OK = 0;
// 提交事务
void commit(Xid var1, boolean var2) throws XAException;
// 结束事务
void end(Xid var1, int var2) throws XAException;
// 告诉资源管理器忽略一个 prepared 状态的事务分支
void forget(Xid var1) throws XAException;
// 获取事务超时时间
int getTransactionTimeout() throws XAException;
// 判断目标对象所代表的资源管理器实例是否与参数var1所代表的资源管理器实例相同
boolean isSameRM(XAResource var1) throws XAException;
// 设置事务超时时间
int prepare(Xid var1) throws XAException;
// 查看存在的 prepared 状态的xa事务
Xid[] recover(int var1) throws XAException;
// 事务回滚
void rollback(Xid var1) throws XAException;
// 设置事务超时时间
boolean setTransactionTimeout(int var1) throws XAException;
// 开始事务
void start(Xid var1, int var2) throws XAException;
}
JtdsXAResource
类 使用 jTDS JDBC Drive 来实现了这些事务相关的功能。
jTDS 是一个开源的、纯 Java 的JDBC 3.0 驱动程序。
三、DruidXADataSource
类:
DruidXADataSource 类继承自 DruidDataSource,实现了 XADataSource 接口。XADataSource 类位于 javax.sql
包下,是内部使用的 XAConnection 对象的工厂。
public class DruidXADataSource extends DruidDataSource implements XADataSource {
private final static Log LOG = LogFactory.getLog(DruidXADataSource.class);
private static final long serialVersionUID = 1L;
private Object h2Factory = null;
/**
* 尝试建立可用于分布式事务的物理数据库连接
* @return 一个 XAConnection 对象,表示与数据源的物理连接,可用于分布式事务
*/
@Override
public XAConnection getXAConnection() throws SQLException {
DruidPooledConnection conn = this.getConnection();
Connection physicalConn = conn.unwrap(Connection.class);
// 创建用于分布式事务的物理数据库连接
XAConnection rawXAConnection = createPhysicalXAConnection(physicalConn);
return new DruidPooledXAConnection(conn, rawXAConnection);
}
protected void initCheck() throws SQLException {
super.initCheck();
DbType dbType = DbType.of(this.dbTypeName);
if (JdbcUtils.H2.equals(dbType)) {
h2Factory = H2Utils.createJdbcDataSourceFactory();
}
}
private XAConnection createPhysicalXAConnection(Connection physicalConn) throws SQLException {
DbType dbType = DbType.of(this.dbTypeName);
if (dbType == null) {
throw new SQLException("xa not support dbType : " + this.dbTypeName);
}
switch (dbType) {
case oracle:
try {
return OracleUtils.OracleXAConnection(physicalConn);
} catch (XAException xae) {
LOG.error("create xaConnection error", xae);
return null;
}
case mysql:
case mariadb:
// 通过反射创建 MySQL XA 连接
return MySqlUtils.createXAConnection(driver, physicalConn);
case postgresql:
return PGUtils.createXAConnection(physicalConn);
case h2:
return H2Utils.createXAConnection(h2Factory, physicalConn);
case jtds:
return new JtdsXAConnection(physicalConn);
default:
throw new SQLException("xa not support dbType : " + this.dbTypeName);
}
}
/**
* 尝试使用给定的用户名和密码建立物理数据库连接。 返回的连接是可用于分布式事务的连接
* @param user 数据库用户名
* @param password 数据库密码
* @return 可用于分布式事务的连接
*/
@Override
public XAConnection getXAConnection(String user, String password) throws SQLException {
throw new UnsupportedOperationException("Not supported by DruidDataSource");
}
}
四、JtdsXAConnection
类
JtdsXAConnection
类实现了 XAConnection
接口来进行连接池管理。
通过 jTDS 的 XASupport
类来进行连接池管理。
五、DruidPooledXAConnection
类
DruidPooledXAConnection
实现了 XAConnection
接口来进行连接池管理,XAConnection
接口位于 javax.sql
包下并继承自 PooledConnection
,为分布式事务提供支持。 XAConnection 对象可以通过 XAResource 对象加入到分布式事务中。 事务管理器通过 XAResource 对象管理 XAConnection 对象。
PooledConnection 对象表示与数据源的物理连接。当应用程序完成连接时,连接会被回收而不是关闭,通过连接复用来减少需要建立的连接数量。
连接池管理器维护一个 PooledConnection 对象池。如果池中有可用的 PooledConnection 对象,则连接池管理器会返回一个 Connection 对象,该对象是该物理连接的句柄。如果没有可用的 PooledConnection 对象,则连接池管理器调用 ConnectionPoolDataSource 方法 getPoolConnection 来创建新的物理连接,实现 ConnectionPoolDataSource 的 JDBC 驱动程序便会创建一个新的 PooledConnection 对象并返回一个句柄。