
铿然架构 | 作者 / 铿然一叶 这是铿然架构的第 104 篇原创文章
相关阅读:
萌新快速成长之路
如何编写软件设计文档
JAVA编程思想(一)通过依赖注入增加扩展性
JAVA编程思想(二)如何面向接口编程
JAVA编程思想(三)去掉别扭的if,自注册策略模式优雅满足开闭原则
JAVA编程思想(四)Builder模式经典范式以及和工厂模式如何选?
Java编程思想(七)使用组合和继承的场景
JAVA基础(一)简单、透彻理解内部类和静态内部类
JAVA基础(二)内存优化-使用Java引用做缓存
JAVA基础(三)ClassLoader实现热加载
JAVA基础(四)枚举(enum)和常量定义,工厂类使用对比
JAVA基础(五)函数式接口-复用,解耦之利刃
Seata源码(一)初始化
Seata源码(二)事务基础对象
Seata源码(三)事务处理类结构和流程
Seata源码(四)全局锁GlobalLock
Seata源码(五)Seata数据库操作
Seata源码(六)Seata的undo日志操作
Seata源码(七)Seata事务故障处理
Seata源码(八)Seata事务生命周期hook
Seata源码(九)TCC核心类和处理逻辑
Seata源码(十)RM接收到请求后的调用过程
Seata源码(十一)TC接收到请求后的处理过程
Seata源码(十二)Session管理和持久化
Seata源码(十三)Seata消息处理框架,掌握了可以当大佬~!!
码字不易,先赞后看!!!
1. 概念
1.1 TC (Transaction Coordinator) - 事务协调者
维护全局和分支事务的状态,驱动全局事务提交或回滚。
1.2 RM (Resource Manager) - 资源管理器
管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。
RM资源由RM向TC注册。
2. 源码分析
2.1 RM注册请求类结构
类结构如下:

● 紫色部分是最开始的触发点,如果不使用seata数据源,直接创建DataSourceProxy和DataSourceProxyXA,则不会走从SeataDataSourceAutoConfiguration触发的路径。
● 绿色部分是要注册的资源。
● 粉红色是每类资源对应的RM。
● 图中的标号是关键调用顺序。
2.2 构造资源
2.2.1 DataSourceProxy
构造资源时没有设置resourceId,会在使用DataSourceProxy时,通过getResourceId()方法返回:
DataSourceProxy.java
java
private void init(DataSource dataSource, String resourceGroupId) {
this.resourceGroupId = resourceGroupId;
try (Connection connection = dataSource.getConnection()) {
jdbcUrl = connection.getMetaData().getURL();
dbType = JdbcUtils.getDbType(jdbcUrl);
if (JdbcConstants.ORACLE.equals(dbType)) {
userName = connection.getMetaData().getUserName();
}
} catch (SQLException e) {
throw new IllegalStateException("can not init dataSource", e);
}
DefaultResourceManager.get().registerResource(this);
获取resourceId:
DataSourceProxy.java
java
public String getResourceId() {
if (JdbcConstants.POSTGRESQL.equals(dbType)) {
return getPGResourceId();
} else if (JdbcConstants.ORACLE.equals(dbType) && userName != null) {
return getDefaultResourceId() + "/" + userName;
} else {
return getDefaultResourceId();
}
}
private String getDefaultResourceId() {
if (jdbcUrl.contains("?")) {
return jdbcUrl.substring(0, jdbcUrl.indexOf('?'));
} else {
return jdbcUrl;
}
}
private String getPGResourceId() {
if (jdbcUrl.contains("?")) {
StringBuilder jdbcUrlBuilder = new StringBuilder();
jdbcUrlBuilder.append(jdbcUrl.substring(0, jdbcUrl.indexOf('?')));
StringBuilder paramsBuilder = new StringBuilder();
String paramUrl = jdbcUrl.substring(jdbcUrl.indexOf('?') + 1, jdbcUrl.length());
String[] urlParams = paramUrl.split("&");
for (String urlParam : urlParams) {
if (urlParam.contains("currentSchema")) {
paramsBuilder.append(urlParam);
break;
}
}
if (paramsBuilder.length() > 0) {
jdbcUrlBuilder.append("?");
jdbcUrlBuilder.append(paramsBuilder);
}
return jdbcUrlBuilder.toString();
} else {
return jdbcUrl;
}
}
2.2.2 XA资源
JdbcUtils.java
构造资源时设置了resourceId:
java
public static void initDataSourceResource(BaseDataSourceResource dataSourceResource, DataSource dataSource, String resourceGroupId) {
dataSourceResource.setResourceGroupId(resourceGroupId);
try (Connection connection = dataSource.getConnection()) {
String jdbcUrl = connection.getMetaData().getURL();
dataSourceResource.setResourceId(buildResourceId(jdbcUrl));
String driverClassName = com.alibaba.druid.util.JdbcUtils.getDriverClassName(jdbcUrl);
dataSourceResource.setDriver(loadDriver(driverClassName));
dataSourceResource.setDbType(com.alibaba.druid.util.JdbcUtils.getDbType(jdbcUrl, driverClassName));
} catch (SQLException e) {
throw new IllegalStateException("can not init DataSourceResource with " + dataSource, e);
}
DefaultResourceManager.get().registerResource(dataSourceResource);
}
resourceId生成规则:
java
public static String buildResourceId(String jdbcUrl) {
if (jdbcUrl.contains("?")) {
return jdbcUrl.substring(0, jdbcUrl.indexOf('?'));
}
return jdbcUrl;
}
取jdbcUrl的?号前面部分,如:
jdbc:mysql://192.68.5.43:3306/accountdb?serverTimezone=GMT
则为:
jdbc:mysql://192.68.5.43:3306/accountdb
2.2.3 TCC资源
构造资源时没有设置resourceId,会在使用TCCResource时,通过getResourceId()方法返回:
DefaultRemotingParser.java
java
for (Method m : methods) {
TwoPhaseBusinessAction twoPhaseBusinessAction = m.getAnnotation(TwoPhaseBusinessAction.class);
if (twoPhaseBusinessAction != null) {
TCCResource tccResource = new TCCResource();
tccResource.setActionName(twoPhaseBusinessAction.name());
tccResource.setTargetBean(targetBean);
tccResource.setPrepareMethod(m);
tccResource.setCommitMethodName(twoPhaseBusinessAction.commitMethod());
tccResource.setCommitMethod(ReflectionUtil
.getMethod(interfaceClass, twoPhaseBusinessAction.commitMethod(),
new Class[] {BusinessActionContext.class}));
tccResource.setRollbackMethodName(twoPhaseBusinessAction.rollbackMethod());
tccResource.setRollbackMethod(ReflectionUtil
.getMethod(interfaceClass, twoPhaseBusinessAction.rollbackMethod(),
new Class[] {BusinessActionContext.class}));
//registry tcc resource
DefaultResourceManager.get().registerResource(tccResource);
}
}
获取resourceId:
TCCResource.java
java
public String getResourceId() {
return actionName;
}
可以看到是actionName,取值来自业务方法上的TwoPhaseBusinessAction注解的name属性:
java
@TwoPhaseBusinessAction(name = "TccActionOne", commitMethod = "commit", rollbackMethod = "rollback")
public boolean prepare(BusinessActionContext actionContext, int a);
2.3 标号1 获取资源实例调用注册方法
根据资源分支类型获取对应的AbstractResourceManager子类实例:
DefaultResourceManager.java
java
public void registerResource(Resource resource) {
getResourceManager(resource.getBranchType()).registerResource(resource);
}
其中DataSourceManager和TCCResourceManager实现了registerResource方法,ResourceManagerXA则调用父类AbstractDataSourceCacheResourceManager的registerResourc方法,三者最后都会调用父类AbstractResourceManager的registerResourc方法。
这些子类的registerResourc方法中都将资源映射关系放入了缓存,在后面会通过此缓存获取resourceId:
TCCResourceManager.java
java
public void registerResource(Resource resource) {
TCCResource tccResource = (TCCResource)resource;
// 这里的缓存在后面会用于获取resourceId
tccResourceCache.put(tccResource.getResourceId(), tccResource);
super.registerResource(tccResource);
}
DataSourceManager.java
java
public void registerResource(Resource resource) {
DataSourceProxy dataSourceProxy = (DataSourceProxy) resource;
// 这里的缓存在后面会用于获取resourceId
dataSourceCache.put(dataSourceProxy.getResourceId(), dataSourceProxy);
super.registerResource(dataSourceProxy);
}
AbstractDataSourceCacheResourceManager.java
java
public void registerResource(Resource resource) {
// 这里的缓存在后面会用于获取resourceId
dataSourceCache.put(resource.getResourceId(), resource);
super.registerResource(resource);
}
2.4 标号2 调用RmNettyRemotingClient
直接调用RmNettyRemotingClient的registerResource方法:
AbstractResourceManager.java
java
public void registerResource(Resource resource) {
RmNettyRemotingClient.getInstance().registerResource(resource.getResourceGroupId(), resource.getResourceId());
}
2.5 标号3 调用NettyClientChannelManager
这里的调用逻辑需要注意,并不是想当然的会调用后面的sendRegisterMessage方法(相信初看代码的人都会这么认为),实则是走到了前面的getClientChannelManager().reconnect方法内,多数人也不会想到RM注册发生在reconnect这条路径上:
RmNettyRemotingClient.java
java
public void registerResource(String resourceGroupId, String resourceId) {
// Resource registration cannot be performed until the RM client is initialized
if (StringUtils.isBlank(transactionServiceGroup)) {
return;
}
if (getClientChannelManager().getChannels().isEmpty()) {
// 实际走到了这里
getClientChannelManager().reconnect(transactionServiceGroup);
return;
}
synchronized (getClientChannelManager().getChannels()) {
for (Map.Entry<String, Channel> entry : getClientChannelManager().getChannels().entrySet()) {
String serverAddress = entry.getKey();
Channel rmChannel = entry.getValue();
if (LOGGER.isInfoEnabled()) {
LOGGER.info("will register resourceId:{}", resourceId);
}
// 刚开始会想当然理解走到这里,顺着这条路走,最终似乎收不到应答消息,也可能特殊场景会走到,没有验证到
sendRegisterMessage(serverAddress, rmChannel, resourceId);
}
}
}
2.6 标号4 和标号5 调用NettyClientChannelManager
这两个调用点都在同一个方法内,方法触发顺序是reconnect -> acquireChannel -> doConnect:
NettyClientChannelManager.java
java
private Channel doConnect(String serverAddress) {
Channel channelToServer = channels.get(serverAddress);
if (channelToServer != null && channelToServer.isActive()) {
return channelToServer;
}
Channel channelFromPool;
try {
// 回调RmNettyRemotingClient的getPoolKeyFunction方法
NettyPoolKey currentPoolKey = poolKeyFunction.apply(serverAddress);
NettyPoolKey previousPoolKey = poolKeyMap.putIfAbsent(serverAddress, currentPoolKey);
if (previousPoolKey != null && previousPoolKey.getMessage() instanceof RegisterRMRequest) {
RegisterRMRequest registerRMRequest = (RegisterRMRequest) currentPoolKey.getMessage();
((RegisterRMRequest) previousPoolKey.getMessage()).setResourceIds(registerRMRequest.getResourceIds());
}
// GenericKeyedObjectPool的borrowObject方法
channelFromPool = nettyClientKeyPool.borrowObject(poolKeyMap.get(serverAddress));
channels.put(serverAddress, channelFromPool);
} catch (Exception exx) {
LOGGER.error("{} register RM failed.",FrameworkErrorCode.RegisterRM.getErrCode(), exx);
throw new FrameworkException("can not register RM,err:" + exx.getMessage());
}
return channelFromPool;
}
其中:
java
NettyPoolKey currentPoolKey = poolKeyFunction.apply(serverAddress);
回调了RmNettyRemotingClient的getPoolKeyFunction方法,是个函数接口的匿名实现,返回NettyPoolKey,NettyPoolKey的其中一个属性是RegisterRMRequest,后面将获取到它并发送RM注册请求:
RmNettyRemotingClient.java
java
protected Function<String, NettyPoolKey> getPoolKeyFunction() {
return serverAddress -> {
// 获取资源id列表
String resourceIds = getMergedResourceKeys();
if (resourceIds != null && LOGGER.isInfoEnabled()) {
LOGGER.info("RM will register :{}", resourceIds);
}
// 构造资源注册请求,所有RM资源一起注册了
RegisterRMRequest message = new RegisterRMRequest(applicationId, transactionServiceGroup);
message.setResourceIds(resourceIds);
// 记住NettyPoolKey的message属性存的就是RegisterRMRequest
return new NettyPoolKey(NettyPoolKey.TransactionRole.RMROLE, serverAddress, message);
};
}
这个getPoolKeyFunction方法在AbstractNettyRemotingClient的构造方法中,构造NettyClientChannelManager时传入:
AbstractNettyRemotingClient.java
java
public AbstractNettyRemotingClient(NettyClientConfig nettyClientConfig, EventExecutorGroup eventExecutorGroup,
ThreadPoolExecutor messageExecutor, NettyPoolKey.TransactionRole transactionRole) {
super(messageExecutor);
this.transactionRole = transactionRole;
clientBootstrap = new NettyClientBootstrap(nettyClientConfig, eventExecutorGroup, transactionRole);
clientBootstrap.setChannelHandlers(new ClientHandler());
// 这里的getPoolKeyFunction方法是其子类RmNettyRemotingClient的方法
clientChannelManager = new NettyClientChannelManager(
new NettyPoolableFactory(this, clientBootstrap), getPoolKeyFunction(), nettyClientConfig);
}
AbstractNettyRemotingClient是RmNettyRemotingClient的父类,因此是在构造RmNettyRemotingClient的时候创建:
RmNettyRemotingClient.java
java
private RmNettyRemotingClient(NettyClientConfig nettyClientConfig, EventExecutorGroup eventExecutorGroup,
ThreadPoolExecutor messageExecutor) {
super(nettyClientConfig, eventExecutorGroup, messageExecutor, TransactionRole.RMROLE);
}
再接着前面,看看RmNettyRemotingClient的getPoolKeyFunction方法里调用的getMergedResourceKeys是如何获取resourceId列表的:
RmNettyRemotingClient.java
java
public String getMergedResourceKeys() {
// 这里的resourceManager是DefaultResourceManager,获取到所有RM的子类和resourceId映射关系Map,key是resourceId
Map<String, Resource> managedResources = resourceManager.getManagedResources();
Set<String> resourceIds = managedResources.keySet();
if (!resourceIds.isEmpty()) {
StringBuilder sb = new StringBuilder();
boolean first = true;
for (String resourceId : resourceIds) {
if (first) {
first = false;
} else {
sb.append(DBKEYS_SPLIT_CHAR);
}
sb.append(resourceId);
}
return sb.toString();
}
return null;
}
在DefaultResourceManager中通过rm.getManagedResources()获取到所有RM子类的资源数据:
DefaultResourceManager.java
java
public Map<String, Resource> getManagedResources() {
Map<String, Resource> allResource = new HashMap<>();
for (ResourceManager rm : resourceManagers.values()) {
// RM子类方法,返回前面缓存的数据
Map<String, Resource> tempResources = rm.getManagedResources();
if (tempResources != null) {
allResource.putAll(tempResources);
}
}
return allResource;
}
子类中的dataSourceCache为缓存数据,在之前存入,以DataSourceManager为例:
DataSourceManager.java
java
// 返回缓存数据
public Map<String, Resource> getManagedResources() {
return dataSourceCache;
}
// 前面章节提到过,注册时放入了缓存
public void registerResource(Resource resource) {
DataSourceProxy dataSourceProxy = (DataSourceProxy) resource;
dataSourceCache.put(dataSourceProxy.getResourceId(), dataSourceProxy);
super.registerResource(dataSourceProxy);
}
2.7 标号6 调用NettyPoolableFactory
GenericKeyedObjectPool是一个开源的池化管理框架,其结构是:

GenericKeyedObjectPool的构造器参数传入一个KeyedPoolableObjectFactory子类对象,当调用GenericKeyedObjectPool的borrowObject方法时,会自动调用传入的子类对象的makeObject方法,这里是NettyPoolableFactory的makeObject方法。
两者的关系在构造NettyClientChannelManager时建立:
NettyClientChannelManager.java
java
NettyClientChannelManager(final NettyPoolableFactory keyPoolableFactory, final Function<String, NettyPoolKey> poolKeyFunction,
final NettyClientConfig clientConfig) {
nettyClientKeyPool = new GenericKeyedObjectPool<>(keyPoolableFactory);
nettyClientKeyPool.setConfig(getNettyPoolConfig(clientConfig));
this.poolKeyFunction = poolKeyFunction;
}
2.8 标号7 调用RmNettyRemotingClient
NettyPoolableFactory的makeObject方法调用RmNettyRemotingClient发送注册请求,并处理应答:
NettyPoolableFactory.java
java
public Channel makeObject(NettyPoolKey key) {
InetSocketAddress address = NetUtil.toInetSocketAddress(key.getAddress());
if (LOGGER.isInfoEnabled()) {
LOGGER.info("NettyPool create channel to " + key);
}
Channel tmpChannel = clientBootstrap.getNewChannel(address);
long start = System.currentTimeMillis();
Object response;
Channel channelToServer = null;
if (key.getMessage() == null) {
throw new FrameworkException("register msg is null, role:" + key.getTransactionRole().name());
}
try {
// 这里的key.getMessage(),就是前面放入的RegisterRMRequest
response = rpcRemotingClient.sendSyncRequest(tmpChannel, key.getMessage());
if (!isRegisterSuccess(response, key.getTransactionRole())) {
rpcRemotingClient.onRegisterMsgFail(key.getAddress(), tmpChannel, response, key.getMessage());
} else {
channelToServer = tmpChannel;
rpcRemotingClient.onRegisterMsgSuccess(key.getAddress(), tmpChannel, response, key.getMessage());
}
} catch (Exception exx) {
if (tmpChannel != null) {
tmpChannel.close();
}
throw new FrameworkException(
"register " + key.getTransactionRole().name() + " error, errMsg:" + exx.getMessage());
}
if (LOGGER.isInfoEnabled()) {
LOGGER.info("register success, cost " + (System.currentTimeMillis() - start) + " ms, version:" + getVersion(
response, key.getTransactionRole()) + ",role:" + key.getTransactionRole().name() + ",channel:"
+ channelToServer);
}
return channelToServer;
}
2.9 RmNettyRemotingClient注册请求处理
发送请求调用的是父类AbstractNettyRemotingClient的方法:
AbstractNettyRemotingClient.java
java
public Object sendSyncRequest(Channel channel, Object msg) throws TimeoutException {
if (channel == null) {
LOGGER.warn("sendSyncRequest nothing, caused by null channel.");
return null;
}
RpcMessage rpcMessage = buildRequestMessage(msg, ProtocolConstants.MSGTYPE_RESQUEST_SYNC);
return super.sendSync(channel, rpcMessage, NettyClientConfig.getRpcRequestTimeout());
}
后续的消息发送部分在"Seata消息处理框架"那篇文章中已经阐述。
RM注册成功后的处理:
RmNettyRemotingClient.java
java
public void onRegisterMsgSuccess(String serverAddress, Channel channel, Object response,
AbstractMessage requestMessage) {
RegisterRMRequest registerRMRequest = (RegisterRMRequest)requestMessage;
RegisterRMResponse registerRMResponse = (RegisterRMResponse)response;
if (LOGGER.isInfoEnabled()) {
LOGGER.info("register RM success. client version:{}, server version:{},channel:{}", registerRMRequest.getVersion(), registerRMResponse.getVersion(), channel);
}
// 建立地址和channel的关系
getClientChannelManager().registerChannel(serverAddress, channel);
// 下面这段逻辑应该加上注释,说明什么场景下还需要再次注册,虽然比较关系说明不一致就注册,但是什么场景下会不一致应描述
String dbKey = getMergedResourceKeys();
if (registerRMRequest.getResourceIds() != null) {
if (!registerRMRequest.getResourceIds().equals(dbKey)) {
sendRegisterMessage(serverAddress, channel, dbKey);
}
}
}
RM注册失败后的处理,抛出异常:
RmNettyRemotingClient.java
java
public void onRegisterMsgFail(String serverAddress, Channel channel, Object response,
AbstractMessage requestMessage) {
RegisterRMRequest registerRMRequest = (RegisterRMRequest)requestMessage;
RegisterRMResponse registerRMResponse = (RegisterRMResponse)response;
String errMsg = String.format(
"register RM failed. client version: %s,server version: %s, errorMsg: %s, " + "channel: %s", registerRMRequest.getVersion(), registerRMResponse.getVersion(), registerRMResponse.getMsg(), channel);
throw new FrameworkException(errMsg);
}
阅过留痕,点赞,收藏,关注三连!
