Seata柔性事务简介
Seata是阿里集团和蚂蚁金服联合打造的分布式事务框架。 其 AT 事务的目标是在微服务架构下,提供增量的事务 ACID 语意,让开发者像使用本地事务一样,使用分布式事务,核心理念同 Apache ShardingSphere 一脉相承。
Seata AT 事务模型包含TM (事务管理器),RM (资源管理器) 和 TC (事务协调器)。 TC 是一个独立部署的服务,TM 和 RM 以 jar 包的方式同业务应用一同部署,它们同 TC 建立长连接,在整个事务生命周期内,保持远程通信。 TM 是全局事务的发起方,负责全局事务的开启,提交和回滚。 RM 是全局事务的参与者,负责分支事务的执行结果上报,并且通过 TC 的协调进行分支事务的提交和回滚。
Seata 管理的分布式事务的典型生命周期:
- TM 要求 TC 开始一个全新的全局事务。TC 生成一个代表该全局事务的 XID。
- XID 贯穿于微服务的整个调用链。
- 作为该 XID 对应到的 TC 下的全局事务的一部分,RM 注册本地事务。
- TM 要求 TC 提交或回滚 XID 对应的全局事务。
- TC 驱动 XID 对应的全局事务下的所有分支事务完成提交或回滚。
Seata柔性事务与ShardingSphere
整合 Seata AT 事务时,需要将 TM,RM 和 TC 的模型融入 Apache ShardingSphere 的分布式事务生态中。 在数据库资源上,Seata 通过对接 DataSource 接口,让 JDBC 操作可以同 TC 进行远程通信。 同样,Apache ShardingSphere 也是面向 DataSource 接口,对用户配置的数据源进行聚合。 因此,将 DataSource 封装为 基于Seata 的 DataSource 后,就可以将 Seata AT 事务融入到 Apache ShardingSphere的分片生态中。
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);
}
}
事务调用
经过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();
}
}