ShardingSphere如何轻松驾驭Seata柔性分布式事务?

0 前文

上一文解析了 ShardingSphere 强一致性事务支持 XAShardingTransactionManager ,本文继续:

  • 讲解该类
  • 介绍支持柔性事务的 SeataATShardingTransactionManager

sharding-transaction-xa-core中关于 XAShardingTransactionManager,本文研究 XATransactionManager 和 ShardingConnection 类实现。

1 XAShardingTransactionManager

1.1 init

java 复制代码
public void init(final DatabaseType databaseType, final Collection<ResourceDataSource> resourceDataSources) {
        for (ResourceDataSource each : resourceDataSources) {
            // 根据传入的 ResourceDataSource创建XATransactionDataSource并缓存
            cachedDataSources.put(each.getOriginalName(), new XATransactionDataSource(databaseType, each.getUniqueResourceName(), each.getDataSource(), xaTransactionManager));
        }
        // 对通过 SPI 创建的 XATransactionManager 也执行其 init 初始化
        xaTransactionManager.init();
}

1.2 其它方法

实现也简单:

java 复制代码
@Override
public TransactionType getTransactionType() {
    return TransactionType.XA;
}

@SneakyThrows
@Override
public boolean isInTransaction() {
    return Status.STATUS_NO_TRANSACTION != xaTransactionManager.getTransactionManager().getStatus();
}

@Override
public Connection getConnection(final String dataSourceName) throws SQLException {
    return cachedDataSources.get(dataSourceName).getConnection();
}

1.3 事务操作相关

begin、commit 和 rollback直接委托保存在 XATransactionManager#TransactionManager 完成:

java 复制代码
@SneakyThrows
@Override
public void begin() {
    xaTransactionManager.getTransactionManager().begin();
}

@SneakyThrows
@Override
public void commit() {
    xaTransactionManager.getTransactionManager().commit();
}

@SneakyThrows
@Override
public void rollback() {
    xaTransactionManager.getTransactionManager().rollback();
}

2 AtomikosTransactionManager

TransactionManager默认实现。

2.1 AtomikosXARecoverableResource

代表资源:

java 复制代码
public final class AtomikosXARecoverableResource extends JdbcTransactionalResource {

    private final String resourceName;

    AtomikosXARecoverableResource(final String serverName, final XADataSource xaDataSource) {
        super(serverName, xaDataSource);
        resourceName = serverName;
    }

      // 比对SingleXAResource#ResourceName,确定是否在使用资源,此即设计包装 XAResource 的 SingleXAResource 类的原因
    @Override
    public boolean usesXAResource(final XAResource xaResource) {
        return resourceName.equals(((SingleXAResource) xaResource).getResourceName());
    }
}

2.2 AtomikosXARecoverableResource

java 复制代码
public final class AtomikosTransactionManager implements XATransactionManager {

    private final UserTransactionManager transactionManager = new UserTransactionManager();

    private final UserTransactionService userTransactionService = new UserTransactionServiceImp();

    @Override
    public void init() {
        userTransactionService.init();
    }

    @Override
    public void registerRecoveryResource(final String dataSourceName, final XADataSource xaDataSource) {
        userTransactionService.registerResource(new AtomikosXARecoverableResource(dataSourceName, xaDataSource));
    }

    @Override
    public void removeRecoveryResource(final String dataSourceName, final XADataSource xaDataSource) {
        userTransactionService.removeResource(new AtomikosXARecoverableResource(dataSourceName, xaDataSource));
    }

    @Override
    @SneakyThrows
    public void enlistResource(final SingleXAResource xaResource) {
        transactionManager.getTransaction().enlistResource(xaResource);
    }

    @Override
    public TransactionManager getTransactionManager() {
        return transactionManager;
    }

    @Override
    public void close() {
        userTransactionService.shutdown(true);
    }
}

对 Atomikos 的 UserTransactionManager、UserTransactionService 简单调用,Atomikos#UserTransactionManager 实现 TransactionManager 接口,封装所有 TransactionManager 需要完成的工作。

看完 sharding-transaction-xa-atomikos-manager,再看 sharding-transaction-xa-bitronix-manager 工程。基于 bitronix 的 XATransactionManager 实现方案

3 BitronixXATransactionManager

java 复制代码
public final class BitronixXATransactionManager implements XATransactionManager {

    private final BitronixTransactionManager bitronixTransactionManager = TransactionManagerServices.getTransactionManager();

    @Override
    public void init() {
    }

    @SneakyThrows
    @Override
    public void registerRecoveryResource(final String dataSourceName, final XADataSource xaDataSource) {
        ResourceRegistrar.register(new BitronixRecoveryResource(dataSourceName, xaDataSource));
    }

    @SneakyThrows
    @Override
    public void removeRecoveryResource(final String dataSourceName, final XADataSource xaDataSource) {
        ResourceRegistrar.unregister(new BitronixRecoveryResource(dataSourceName, xaDataSource));
    }

    @SneakyThrows
    @Override
    public void enlistResource(final SingleXAResource singleXAResource) {
        bitronixTransactionManager.getTransaction().enlistResource(singleXAResource);
    }

    @Override
    public TransactionManager getTransactionManager() {
        return bitronixTransactionManager;
    }

    @Override
    public void close() {
        bitronixTransactionManager.shutdown();
    }
}

XA两阶段提交核心类:

4 ShardingConnection

上图的整个流程源头ShardingConnection类,构造函数发现创建 ShardingTransactionManager 过程:

java 复制代码
@Getter
public final class ShardingConnection extends AbstractConnectionAdapter {
      public ShardingConnection(...) {
        ...
        shardingTransactionManager = runtimeContext.getShardingTransactionManagerEngine().getTransactionManager(transactionType);
    }
}

ShardingConnection多处用到上面创建的shardingTransactionManager。如:

createConnection

获取连接:

java 复制代码
@Override
protected Connection createConnection(final String dataSourceName, final DataSource dataSource) throws SQLException {
        return isInShardingTransaction() ? shardingTransactionManager.getConnection(dataSourceName) : dataSource.getConnection();
}

isInShardingTransaction

判断是否在同一事务:

java 复制代码
private boolean isInShardingTransaction() {
        return null != shardingTransactionManager && shardingTransactionManager.isInTransaction();
}

setAutoCommit

java 复制代码
@Override
public void setAutoCommit(final boolean autoCommit) throws SQLException {
        if (TransactionType.LOCAL == transactionType) {
            super.setAutoCommit(autoCommit);
            return;
        }
        if (autoCommit && !shardingTransactionManager.isInTransaction() || !autoCommit && shardingTransactionManager.isInTransaction()) {
            return;
        }
        if (autoCommit && shardingTransactionManager.isInTransaction()) {
            shardingTransactionManager.commit();
            return;
        }
        if (!autoCommit && !shardingTransactionManager.isInTransaction()) {
            closeCachedConnections();
            shardingTransactionManager.begin();
        }
}

事务类型为本地事务时,直接调用 ShardingConnection 父类 AbstractConnectionAdapter#setAutoCommit 完成本地事务自动提交:

  • autoCommit=true 且运行在事务中,调shardingTransactionManager.commit()完成提交
  • autoCommit=false 且当前不在事务中时,调 shardingTransactionManager.begin() 启动事务

commit、rollback

类似setAutoCommit ,按事务类型决定是否进行分布式提交和回滚:

java 复制代码
@Override
public void commit() throws SQLException {
        if (TransactionType.LOCAL == transactionType) {
            super.commit();
        } else {
            shardingTransactionManager.commit();
        }
}

@Override
public void rollback() throws SQLException {
        if (TransactionType.LOCAL == transactionType) {
            super.rollback();
        } else {
            shardingTransactionManager.rollback();
        }
}

ShardingSphere提供两阶段提交的 XA 协议实现方案的同时,也实现柔性事务。看完 XAShardingTransactionManager,来看基于 Seata 框架的柔性事务 TransactionManager 实现类 SeataATShardingTransactionManager。

5 SeataATShardingTransactionManager

该类完全采用阿里Seata框架提供分布式事务特性,而非遵循类似 XA 这样的开发规范,所以代码实现比 XAShardingTransactionManager 类层结构简单,复杂性都屏蔽在了框架内部。

集成 Seata,先要初始化 TMClient、RMClient,在 Seata 内部,这两个客户端之间会基于RPC通信。

SeataATShardingTransactionManager#init的initSeataRPCClient初始化这俩客户端对象:

java 复制代码
// 根据 seata.conf 创建配置对象
FileConfiguration configuration = new FileConfiguration("seata.conf");

initSeataRPCClient() {
    String applicationId = configuration.getConfig("client.application.id");
    Preconditions.checkNotNull(applicationId, "please config application id within seata.conf file");
    String transactionServiceGroup = configuration.getConfig("client.transaction.service.group", "default");
    TMClient.init(applicationId, transactionServiceGroup);
    RMClient.init(applicationId, transactionServiceGroup);
}

Seata也提供一套构建在 JDBC 规范之上的实现策略,类似03文介绍的 ShardingSphere 与 JDBC 规范之间兼容性。

Seata使用DataSourceProxy、ConnectionProxy代理对象,如DataSourceProxy:

实现了自定义Resource接口,继承AbstractDataSourceProxy(最终实现JDBC的DataSource接口)。所以,初始化 Seata 框架时,也要根据输入 DataSource 对象构建 DataSourceProxy,并通过 DataSourceProxy 获取 ConnectionProxy。

init、getConnection

java 复制代码
@Override
public void init(final DatabaseType databaseType, final Collection<ResourceDataSource> resourceDataSources) {
     // 初始化 Seata 客户端
     initSeataRPCClient();
     // 创建 DataSourceProxy 并放入Map
     for (ResourceDataSource each : resourceDataSources) {
            dataSourceMap.put(each.getOriginalName(), new DataSourceProxy(each.getDataSource()));
     }
}

@Override
public Connection getConnection(final String dataSourceName) {
      // 根据 DataSourceProxy 获取 ConnectionProxy
      return dataSourceMap.get(dataSourceName).getConnection();
}

初始化后,提供了事务开启和提交相关的入口。Seata的GlobalTransaction是核心接口,封装了面向用户操作层的分布式事务访问入口:

java 复制代码
public interface GlobalTransaction {
    void begin() throws TransactionException;
    void begin(int timeout) throws TransactionException;
    void begin(int timeout, String name) throws TransactionException;
    void commit() throws TransactionException;
    void rollback() throws TransactionException;
    GlobalStatus getStatus() throws TransactionException;
    String getXid();
}

ShardingSphere 作 GlobalTransaction 的用户层,也基于 GlobalTransaction 完成分布式事务操作。但 ShardingSphere 并未直接使用这层,而是设计位于sharding-transaction-base-seata-at的SeataTransactionHolder类,保存线程安全的 GlobalTransaction 对象。

SeataTransactionHolder

java 复制代码
final class SeataTransactionHolder {

    private static final ThreadLocal<GlobalTransaction> CONTEXT = new ThreadLocal<>();

    static void set(final GlobalTransaction transaction) {
        CONTEXT.set(transaction);
    } 
    static GlobalTransaction get() {
        return CONTEXT.get();
    }

    static void clear() {
        CONTEXT.remove();
    }
}

使用 ThreadLocal 确保对 GlobalTransaction 访问的线程安全性。

咋判断当前操作是否处于一个全局事务?Seata存在一个上下文对象RootContex保存参与者和发起者之间传播的 Xid:

  • 当事务发起者开启全局事务,将 Xid 填入 RootContext
  • 然后 Xid 沿服务调用链一直传播,进而填充到每个事务参与者进程的 RootContext
  • 事务参与者发现 RootContext 存在 Xid,就可知自己处于全局事务

因此,只需判断:

java 复制代码
@Override
public boolean isInTransaction() {
        return null != RootContext.getXID();
}

Seata 也提供针对全局事务的上下文类 GlobalTransactionContext,可用:

  • getCurrent 获取一个 GlobalTransaction对象
  • 或通过 getCurrentOrCreate 在无法获取 GlobalTransaction 对象时新建一个

就不难理解如下实现了

begin

java 复制代码
@Override
@SneakyThrows
public void begin() {
               // 创建一个 GlobalTransaction,保存到 SeataTransactionHolder
        SeataTransactionHolder.set(GlobalTransactionContext.getCurrentOrCreate());
               // 从 SeataTransactionHolder 获取一个 GlobalTransaction,并调 begin 启动事务
        SeataTransactionHolder.get().begin();
        SeataTransactionBroadcaster.collectGlobalTxId();
}

注意到最后的类:

SeataTransactionBroadcaster

保存 Seata 全局 Xid 的一个容器类。事务启动时收集全局 Xid 并进行保存,而在事务提交或回滚时清空这些 Xid。

java 复制代码
class SeataTransactionBroadcaster {

    String SEATA_TX_XID = "SEATA_TX_XID";

    static void collectGlobalTxId() {
        if (RootContext.inGlobalTransaction()) {
            ShardingExecuteDataMap.getDataMap().put(SEATA_TX_XID, RootContext.getXID());
        }
    }

    static void broadcastIfNecessary(final Map<String, Object> shardingExecuteDataMap) {
        if (shardingExecuteDataMap.containsKey(SEATA_TX_XID) && !RootContext.inGlobalTransaction()) {
            RootContext.bind((String) shardingExecuteDataMap.get(SEATA_TX_XID));
        }
    }

    static void clear() {
        ShardingExecuteDataMap.getDataMap().remove(SEATA_TX_XID);
    }
}

因此

commit、rollback和close

实现就清楚了:

java 复制代码
@Override
public void commit() {
        try {
            SeataTransactionHolder.get().commit();
        } finally {
            SeataTransactionBroadcaster.clear();
            SeataTransactionHolder.clear();
        }
}

@Override
public void rollback() {
        try {
            SeataTransactionHolder.get().rollback();
        } finally {
            SeataTransactionBroadcaster.clear();
            SeataTransactionHolder.clear();
        }
}

@Override
public void close() {
        dataSourceMap.clear();
        SeataTransactionHolder.clear();
        TmRpcClient.getInstance().destroy();
        RmRpcClient.getInstance().destroy();
}

sharding-transaction-base-seata-at 工程中的代码实际上就只有这些内容,这些内容也构成了在 ShardingSphere中 集成 Seata 框架的实现过程。

6 从源码到开发

本文给出应用程序咋集成 Seata 分布式事务框架的详细过程,ShardingSphere 提供一种模版实现。日常开发,若想在业务代码集成 Seata,可参考 SeataTransactionHolder、SeataATShardingTransactionManager 等核心代码,而无需太多修改。

7 总结

XAShardingTransactionManager理解难在从 ShardingConnection 到底层 JDBC 规范的整个集成和兼容过程。

8 集成Seata框架

参考 ShardingSphere 的实现:


1. 配置 Seata 环境

  • 配置文件准备: 创建 seata.conf 文件,定义 applicationIdtransactionServiceGroup 等参数。
  • 启动 Seata 服务: 启动 Seata Server 并确保其与数据库的事务协调机制正常工作。

2. 初始化 Seata 客户端

项目中初始化 TMClientRMClient,它们分别代表事务管理器和资源管理器:

java 复制代码
FileConfiguration configuration = new FileConfiguration("seata.conf");
String applicationId = configuration.getConfig("client.application.id");
String transactionServiceGroup = configuration.getConfig("client.transaction.service.group", "default");
TMClient.init(applicationId, transactionServiceGroup);
RMClient.init(applicationId, transactionServiceGroup);

3. 数据源代理

构建 DataSourceProxy 使用 Seata 的 DataSourceProxy 对数据源进行代理。

java 复制代码
DataSourceProxy dataSourceProxy = new DataSourceProxy(originalDataSource);

获取连接代理:从代理数据源中获取 ConnectionProxy,使每个数据库连接支持事务传播。

java 复制代码
Connection connection = dataSourceProxy.getConnection();

4. 全局事务上下文管理

基于 GlobalTransactionContext 获取或创建事务对象:

java 复制代码
GlobalTransaction transaction = GlobalTransactionContext.getCurrentOrCreate();

绑定全局事务 XID: 当事务发起时,将全局事务的 XID 存储在 RootContext 中:

java 复制代码
RootContext.bind(transaction.getXid());

通过 RootContext 判断事务状态:

java 复制代码
boolean isInTransaction = RootContext.inGlobalTransaction();

5. 事务操作实现

开启事务:

java 复制代码
transaction.begin();

提交事务:

java 复制代码
try {
    transaction.commit();
} finally {
    RootContext.unbind();
}

回滚事务:

java 复制代码
try {
    transaction.rollback();
} finally {
    RootContext.unbind();
}

6. 整合业务逻辑

将分布式事务的核心逻辑封装在工具类中,例如 SeataTransactionHolder,以便方便地管理全局事务上下文:

java 复制代码
SeataTransactionHolder.set(GlobalTransactionContext.getCurrentOrCreate());

7. 清理资源

在应用关闭时,清理客户端资源:

java 复制代码
TmRpcClient.getInstance().destroy();
RmRpcClient.getInstance().destroy();

8. 注意事项

  • 确保所有数据源通过 DataSourceProxy 代理,避免事务管理失效。
  • 配置数据库支持 Undo Log 表,确保事务回滚记录正常存储。
  • 调试过程中,检查 Seata Server 日志和应用日志,定位事务协调的问题。

通过上述步骤,可以在业务代码中顺利集成 Seata,实现分布式事务管理,保障数据一致性。

关注我,紧跟本系列专栏文章,咱们下篇再续!

作者简介:魔都架构师,多家大厂后端一线研发经验,在分布式系统设计、数据平台架构和AI应用开发等领域都有丰富实践经验。

各大技术社区头部专家博主。具有丰富的引领团队经验,深厚业务架构和解决方案的积累。

负责:

  • 中央/分销预订系统性能优化

  • 活动&券等营销中台建设

  • 交易平台及数据中台等架构和开发设计

  • 车联网核心平台-物联网连接平台、大数据平台架构设计及优化

  • LLM Agent应用开发

  • 区块链应用开发

  • 大数据开发挖掘经验

  • 推荐系统项目

    目前主攻市级软件项目设计、构建服务全社会的应用系统。

参考:

相关推荐
_herbert37 分钟前
MAVEN构建分离依赖JAR
java
野犬寒鸦1 小时前
Pipeline功能实现Redis批处理(项目批量查询点赞情况的应用)
java·服务器·数据库·redis·后端·缓存
꧁༺摩༒西༻꧂1 小时前
Spring Boot Actuator 监控功能的简介及禁用
java·数据库·spring boot
Java中文社群1 小时前
快看!百度提前批的面试难度,你能拿下吗?
java·后端·面试
丨千纸鹤丨1 小时前
Tomcat
java·tomcat
发发发发8882 小时前
leetcode 674.最长连续递增序列
java·数据结构·算法·leetcode·动态规划·最长连续递增序列
回忆是昨天里的海2 小时前
3.3.2_1栈在表达式求值中的应用(上)
java··后缀表达式·前缀表达式
雨绸缪3 小时前
为什么 Java 在 2025 年仍然值得学习:开发人员的 25 年历程
java·后端·掘金·金石计划
花花无缺3 小时前
泛型类和泛型方法
java·后端
泉城老铁3 小时前
Spring Boot 中实现 COM 口数据监听并解析十六进制数据,结合多线程处理
java·后端·物联网