Sharding-Jdbc (8)源码分析 之Seata柔性事务

1,047 阅读5分钟

Seata柔性事务简介

Seata是阿里集团和蚂蚁金服联合打造的分布式事务框架。 其 AT 事务的目标是在微服务架构下,提供增量的事务 ACID 语意,让开发者像使用本地事务一样,使用分布式事务,核心理念同 Apache ShardingSphere 一脉相承。

Seata AT 事务模型包含TM (事务管理器),RM (资源管理器) 和 TC (事务协调器)。 TC 是一个独立部署的服务,TM 和 RM 以 jar 包的方式同业务应用一同部署,它们同 TC 建立长连接,在整个事务生命周期内,保持远程通信。 TM 是全局事务的发起方,负责全局事务的开启,提交和回滚。 RM 是全局事务的参与者,负责分支事务的执行结果上报,并且通过 TC 的协调进行分支事务的提交和回滚。

Seata 管理的分布式事务的典型生命周期:

  1. TM 要求 TC 开始一个全新的全局事务。TC 生成一个代表该全局事务的 XID。
  2. XID 贯穿于微服务的整个调用链。
  3. 作为该 XID 对应到的 TC 下的全局事务的一部分,RM 注册本地事务。
  4. TM 要求 TC 提交或回滚 XID 对应的全局事务。
  5. TC 驱动 XID 对应的全局事务下的所有分支事务完成提交或回滚。

image.png

Seata柔性事务与ShardingSphere

整合 Seata AT 事务时,需要将 TM,RM 和 TC 的模型融入 Apache ShardingSphere 的分布式事务生态中。 在数据库资源上,Seata 通过对接 DataSource 接口,让 JDBC 操作可以同 TC 进行远程通信。 同样,Apache ShardingSphere 也是面向 DataSource 接口,对用户配置的数据源进行聚合。 因此,将 DataSource 封装为 基于Seata 的 DataSource 后,就可以将 Seata AT 事务融入到 Apache ShardingSphere的分片生态中。

image.png

SeataTansactionManager对比

首先比对下Seata和xa事务的区别

public final class SeataATShardingTransactionManager implements ShardingTransactionManager {
    
    private final Map<String, DataSource> dataSourceMap = new HashMap<>();
    
    private final String applicationId;
    
    private final String transactionServiceGroup;
    
    private final boolean enableSeataAT;
    
    public SeataATShardingTransactionManager() {
        FileConfiguration config = new FileConfiguration("seata.conf");
        enableSeataAT = config.getBoolean("sharding.transaction.seata.at.enable", true);
        applicationId = config.getConfig("client.application.id");
        transactionServiceGroup = config.getConfig("client.transaction.service.group", "default");
    }
    
    @Override
    public void init(final DatabaseType databaseType, final Collection<ResourceDataSource> resourceDataSources, final String transactionMangerType) {
        if (enableSeataAT) {
            initSeataRPCClient();
            for (ResourceDataSource each : resourceDataSources) {
                dataSourceMap.put(each.getOriginalName(), new DataSourceProxy(each.getDataSource()));
            }
        }
    }
    
    private void initSeataRPCClient() {
        Preconditions.checkNotNull(applicationId, "please config application id within seata.conf file.");
        TMClient.init(applicationId, transactionServiceGroup);
        RMClient.init(applicationId, transactionServiceGroup);
    }
    
    @Override
    public TransactionType getTransactionType() {
        return TransactionType.BASE;
    }
    
    @Override
    public boolean isInTransaction() {
        Preconditions.checkState(enableSeataAT, "sharding seata-at transaction has been disabled.");
        return null != RootContext.getXID();
    }

可以看到除了公共继承的方法之外,最大的不同就是initSeataRPCClient

private void initSeataRPCClient() {
    Preconditions.checkNotNull(applicationId, "please config application id within seata.conf file.");
    TMClient.init(applicationId, transactionServiceGroup);
    RMClient.init(applicationId, transactionServiceGroup);
}

TMClient初始化

initSeataRPCClient里面有两个初始化方法,首先来看TMClient, 调用 TmRpcClient.getInstance() 方法会获取一个 TM 客户端实例,在获取过程中,会创建 Netty 客户端配置文件对象,以及创建 messageExecutor 线程池,该线程池用于在处理各种与服务端的消息交互,在创建 TmRpcClient 实例时,创建 ClientBootstrap,用于管理 Netty 服务的启停,以及 ClientChannelManager,它是专门用于管理 Netty 客户端对象池,Seata 的 Netty 部分配合使用了对象池。

public final class TmRpcClient extends AbstractRpcRemotingClient {
 

    private TmRpcClient(NettyClientConfig nettyClientConfig, EventExecutorGroup eventExecutorGroup, ThreadPoolExecutor messageExecutor) {
        super(nettyClientConfig, eventExecutorGroup, messageExecutor, TransactionRole.TMROLE);
    }

    public static TmRpcClient getInstance(String applicationId, String transactionServiceGroup) {
        TmRpcClient tmRpcClient = getInstance();
        tmRpcClient.setApplicationId(applicationId);
        tmRpcClient.setTransactionServiceGroup(transactionServiceGroup);
        return tmRpcClient;
    }

    public static TmRpcClient getInstance() {
        if (null == instance) {
            Class var0 = TmRpcClient.class;
            synchronized(TmRpcClient.class) {
                if (null == instance) {
                    NettyClientConfig nettyClientConfig = new NettyClientConfig();
                    ThreadPoolExecutor messageExecutor = new ThreadPoolExecutor(nettyClientConfig.getClientWorkerThreads(), nettyClientConfig.getClientWorkerThreads(), 2147483647L, TimeUnit.SECONDS, new LinkedBlockingQueue(2000), new NamedThreadFactory(nettyClientConfig.getTmDispatchThreadPrefix(), nettyClientConfig.getClientWorkerThreads()), RejectedPolicies.runsOldestTaskPolicy());
                    instance = new TmRpcClient(nettyClientConfig, (EventExecutorGroup)null, messageExecutor);
                }
            }
        }

        return instance;
    }

调用 TM 客户端 init() 方法,最终会启动 netty 客户端(此时还未真正启动,在对象池被调用时才会被真正启动);开启一个定时任务,定时重新发送 RegisterTMRequest(RM 客户端会发送 RegisterRMRequest)请求尝试连接服务端,具体逻辑是在 NettyClientChannelManager 中的 channels 中缓存了客户端 channel,如果此时 channels 不存在获取已过期,那么就会尝试连接服务端以重新获取 channel 并将其缓存到 channels 中;开启一条单独线程,用于处理异步请求发送。

protected Function<String, NettyPoolKey> getPoolKeyFunction() {
    return (severAddress) -> {
        RegisterTMRequest message = new RegisterTMRequest(this.applicationId, this.transactionServiceGroup);
        return new NettyPoolKey(TransactionRole.TMROLE, severAddress, message);
    };
}

RMClient初始化

接下来来看RMClient, RmRpcClient.getInstance 处理逻辑与 TM 大致相同;其中一点区别就在ResourceManager

public void setResourceManager(ResourceManager resourceManager) {
    this.resourceManager = resourceManager;
}

ResourceManager 是 RM 资源管理器,负责分支事务的注册、提交、上报、以及回滚操作,以及全局锁的查询操作,DefaultResourceManager 会持有当前所有的 RM 资源管理器,进行统一调用处理,而 get() 方法主要是加载当前的资源管理器,主要用了类似 SPI 的机制,进行灵活加载。

public class DefaultResourceManager implements ResourceManager {
    protected static Map<BranchType, ResourceManager> resourceManagers = new ConcurrentHashMap();

    private DefaultResourceManager() {
        this.initResourceManagers();
    }

    public static DefaultResourceManager get() {
        return DefaultResourceManager.SingletonHolder.INSTANCE;
    }

    public static void mockResourceManager(BranchType branchType, ResourceManager rm) {
        resourceManagers.put(branchType, rm);
    }

    protected void initResourceManagers() {
        List<ResourceManager> allResourceManagers = EnhancedServiceLoader.loadAll(ResourceManager.class);
        if (CollectionUtils.isNotEmpty(allResourceManagers)) {
            Iterator var2 = allResourceManagers.iterator();

            while(var2.hasNext()) {
                ResourceManager rm = (ResourceManager)var2.next();
                resourceManagers.put(rm.getBranchType(), rm);
            }
        }

    }

EnhancedServiceLoader.load(ExtConfigurationProvider.class).provide(configuration); (EnhancedServiceLoader 是 Seata SPI 实现核心类),这行代码会加载 META-INF/services/和 META-INF/seata/目录中文件填写的类名。

还有NettyClientChannelManager NettyClientChannelManager 这个类集成连接池机制和建立新连接的实现以后,成为了一个连接管理器。 当根据内部逻辑,需要向服务端发送信息时,都会通过 NettyClientChannelManager 获取连接。 虽然 NettyClientChannelManager 已经集成了一个连接池,不过它还是内部维护了 channel 的缓存

private final ConcurrentMap<String, Channel> channels = new ConcurrentHashMap();

当外部调用它来获取连接时,它先从自己的缓存里找,如果找到了,先验证channel 是否有效性,如果有效,直接返回这个 channel。 如果 channel 已经无效,或者没有缓存,那么就会委托连接池去建立新连接。这些细节在它的 acquireChannel 方法里:

Channel acquireChannel(String serverAddress) {
    Channel channelToServer = (Channel)this.channels.get(serverAddress);
    if (channelToServer != null) {
        channelToServer = this.getExistAliveChannel(channelToServer, serverAddress);
        if (null != channelToServer) {
            return channelToServer;
        }
    }

    if (LOGGER.isInfoEnabled()) {
        LOGGER.info("will connect to " + serverAddress);
    }

    this.channelLocks.putIfAbsent(serverAddress, new Object());
    synchronized(this.channelLocks.get(serverAddress)) {
        return this.doConnect(serverAddress);
    }
}

事务调用

屏幕快照 2021-09-01 下午10.22.08.png

经过TMClient和 RMClient的初始化,seata的事务管理器GlobalTransaction做好了准备,程序走到begin() 和commit的部分。


/**
 * Seata transaction holder.
 */
@NoArgsConstructor(access = AccessLevel.PRIVATE)
final class SeataTransactionHolder {
    
    private static final ThreadLocal<GlobalTransaction> CONTEXT = new ThreadLocal<>();
    
    /**
     * Set seata global transaction.
     *
     * @param transaction global transaction context
     */
    static void set(final GlobalTransaction transaction) {
        CONTEXT.set(transaction);
    }
    
    /**
     * Get seata global transaction.
     *
     * @return global transaction
     */
    static GlobalTransaction get() {
        return CONTEXT.get();
    }
    
    /**
     * Clear global transaction.
     */
    static void clear() {
        CONTEXT.remove();
    }
}