seata 客户端启动
Seata Client
启动
Seata Server
启动了解了, 现在来看一下Seata Client
的启动, 需要对SpringBoot
有一些了解
因为Seata Server
解析的是1.5.1, 那么Seata Client
也解析1.5.1
引入Seata
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-seata</artifactId>
</dependency>
因为引入了seata
的starter, 所以会自动引入一些Jar包如下
因为SpringBoot
自动装配的时候会加载Jar 下的spring.factories
中实现EnableAutoConfiguration
的类, 这也是我们分析一些框架源码的入口
spring-cloud-starter-alibaba-seata-2.2.8.RELEASE.jar
在spring-cloud-starter-alibaba-seata
中的spring.factories
总共有4个类实现了EnableAutoConfiguration
接口: restTemplate
,handlerInteceptor
,feignClient
,Hystrix
相关的
SeataRestTemplateAutoConfiguration
从上述的代码中可以看出来,在SeataRestTemplateAutoConfiguration
生成Bean
的时候会调用init
方法,在init
方法中,会将一个interceptor
增加到restTemplate
中, restTemplate
发起请求的时候,会优先调用这个interceptor
, 而SeataRestTemplateInterceptor
是在一个配置类中生成的,可以注入到SeataRestTemplateAutoConfiguration
中
SeataRestTemplateInterceptor
可以看到在SeataRestTemplateInterceptor
并没有进行很复杂的操作,而是从seata
的全局上下文中获取到当前事务的全局事务Id, 然后添加到Header
中,这样就可以让全局事务ID 在各个微服务之间进行流转
SeataHandlerInterceptorConfiguration
在SeataHandlerInterceptorConfiguration
也是添加了一个拦截器, 不过是对SpringMVC
进行拦截,拦截器是SeataHandlerInterceptor
SeataHandlerInterceptor
SeataFeignClientAutoConfiguration
SeataFeignClientAutoConfiguration
是利用构造器模式生成SeataFeignClient
,同时支持hystrix
, sentinel
和普通模式
SeataFeignClient
我们来看一下execute
方法,在方法中会从上下文中获取到全局事务Xid
,然后将xid
放到请求头中
SeataHystrixAutoConfiguration
Hystrix
会将请求包装成Command
,然后将Command
由线程交给请求框架执行
seata-spring-autoconfigure-client-1.5.1.jar
下面来看一下seata-spring-autoconfigure-client-1.5.1.jar
的spring.factories
在client
自动装配存在两个类,一个是SeataTCCFenceAutoConfiguration
,一个是SeataClientEnvironmentPostProcessor
,SeataClientEnvironmentPostProcessor
主要是装配seataClient
的一些配置, SeataTCCFenceAutoConfiguration
主要是装配TCC
模式的一些配置
SeataTCCFenceAutoConfiguration
SeataClientEnvironmentPostProcessor
seata-spring-autoconfigure-core-1.5.1.jar
下面来看一下seata-spring-autoconfigure-core-1.5.1.jar
的spring.factories
seata
核心类中也存在两个自动装配类
SeataCoreAutoConfiguration
SpringApplicationContextProvider
实现了ApplicationContextAware
, 如果对spring 生命周期有些了解的话,会知道ApplicationContextAware
主要是用于方便获取到springApplicationContext
上下文
SeataCoreEnvironmentPostProcessor
SeataCoreEnvironmentPostProcessor
在init
初始化的时候会将seata
的配置,注册等信息全部保存到一个Map中,为了保证线程安全,这里用CAS
的模式进行添加
seata-spring-boot-starter-1.5.1.jar
seata-spring-boot-starter
的spring.factory
中的自动装配类就比较多了,包括seata
数据库代理,seata
, http请求
, seataSaga
模式这4中自动装配
SeataDataSourceAutoConfiguration
SeataDataSourceAutoConfiguration
中比较简单就是创建一个SeataAutoDataSourceProxyCreator
的bean
,SeataAutoDataSourceProxyCreator
是AbstractAutoProxyCreator
的子类,主要是实现Seata
AT模式数据库代理的切面
SeataAutoDataSourceProxyCreator
public class SeataAutoDataSourceProxyCreator extends AbstractAutoProxyCreator {
private static final Logger LOGGER = LoggerFactory.getLogger(SeataAutoDataSourceProxyCreator.class);
private final Set<String> excludes;
private final String dataSourceProxyMode;
private final Object[] advisors;
public SeataAutoDataSourceProxyCreator(boolean useJdkProxy, String[] excludes, String dataSourceProxyMode) {
setProxyTargetClass(!useJdkProxy);
this.excludes = new HashSet<>(Arrays.asList(excludes));
this.dataSourceProxyMode = dataSourceProxyMode;
this.advisors = buildAdvisors(dataSourceProxyMode);
}
// 将需要代理的对象方法注解构建Advice调用链,如果执行到方法有被指定注解修饰,那么最终会调用advice 链条中的invoke 方法
private Object[] buildAdvisors(String dataSourceProxyMode) {
Advice advice = new SeataAutoDataSourceProxyAdvice(dataSourceProxyMode);
return new Object[]{new DefaultIntroductionAdvisor(advice)};
}
@Override
protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource customTargetSource) {
return advisors;
}
@Override
protected boolean shouldSkip(Class<?> beanClass, String beanName) {
if (excludes.contains(beanClass.getName())) {
return true;
}
return SeataProxy.class.isAssignableFrom(beanClass);
}
@Override
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
// we only care DataSource bean
if (!(bean instanceof DataSource)) {
return bean;
}
// when this bean is just a simple DataSource, not SeataDataSourceProxy
if (!(bean instanceof SeataDataSourceProxy)) {
Object enhancer = super.wrapIfNecessary(bean, beanName, cacheKey);
if (bean == enhancer) {
return bean;
}
// 构建数据库代理,将代理对象储存在map中
DataSource origin = (DataSource) bean;
SeataDataSourceProxy proxy = buildProxy(origin, dataSourceProxyMode);
DataSourceProxyHolder.put(origin, proxy);
return enhancer;
}
LOGGER.warn("Manually register SeataDataSourceProxy(or its subclass) bean is discouraged! bean name: {}", beanName);
SeataDataSourceProxy proxy = (SeataDataSourceProxy) bean;
DataSource origin = proxy.getTargetDataSource();
Object originEnhancer = super.wrapIfNecessary(origin, beanName, cacheKey);
// this mean origin is either excluded by user or had been proxy before
if (origin == originEnhancer) {
return origin;
}
// else, put <origin, proxy> to holder and return originEnhancer
DataSourceProxyHolder.put(origin, proxy);
return originEnhancer;
}
SeataDataSourceProxy buildProxy(DataSource origin, String proxyMode) {
// AT模式
if (BranchType.AT.name().equalsIgnoreCase(proxyMode)) {
return new DataSourceProxy(origin);
}
// XA 模式
if (BranchType.XA.name().equalsIgnoreCase(proxyMode)) {
return new DataSourceProxyXA(origin);
}
throw new IllegalArgumentException("Unknown dataSourceProxyMode: " + proxyMode);
}
}
SeataAutoConfiguration
SeataAutoConfiguration
主要是生成了2个bean
, 一个是FailureHandler
,这个是事务处理的类, 一个是GlobalTransactionScanner
,这个类是一个相当重要的类,TMClient
, RMClient
对象都在GlobalTransactionScanner
进行初始化
FailureHandler
DefaultFailureHandlerImpl
是FailureHandler
的默认实现类,主要包括commit
失败,rollback
失败和重试策略,DefaultFailureHandlerImpl
这里的处理的逻辑比较简单,就是单纯打印日志,一般需要重新实现FailureHandler
实现一些自定义的逻辑
GlobalTransactionScanner
public class GlobalTransactionScanner extends AbstractAutoProxyCreator
implements ConfigurationChangeListener, InitializingBean, ApplicationContextAware, DisposableBean {
我们来看一下GlobalTransactionScanner
实现了InitializingBean
, 那么在加入到Spring
中的时候会调用afterPropertiesSet
方法,然后在方法中调用initClient
会实例化TmClient
,RmClient
public void afterPropertiesSet() {
if (disableGlobalTransaction) {
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Global transaction is disabled.");
}
ConfigurationCache.addConfigListener(ConfigurationKeys.DISABLE_GLOBAL_TRANSACTION,
(ConfigurationChangeListener)this);
return;
}
// 初始化seata client, 为了线程安全使用了CAS
if (initialized.compareAndSet(false, true)) {
initClient();
}
}
private void initClient() {
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Initializing Global Transaction Clients ... ");
}
if (DEFAULT_TX_GROUP_OLD.equals(txServiceGroup)) {
...
}
if (StringUtils.isNullOrEmpty(applicationId) || StringUtils.isNullOrEmpty(txServiceGroup)) {
throw new IllegalArgumentException(String.format("applicationId: %s, txServiceGroup: %s", applicationId, txServiceGroup));
}
//init TM
TMClient.init(applicationId, txServiceGroup, accessKey, secretKey);
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Transaction Manager Client is initialized. applicationId[{}] txServiceGroup[{}]", applicationId, txServiceGroup);
}
//init RM
RMClient.init(applicationId, txServiceGroup);
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Resource Manager is initialized. applicationId[{}] txServiceGroup[{}]", applicationId, txServiceGroup);
}
if (LOGGER.isInfoEnabled()) {
LOGGER.info("Global Transaction Clients are initialized. ");
}
registerSpringShutdownHook();
}
TMClient.init
在init
方法中会生成一个netty
的远程连接对象
TmNettyRemotingClient#init
AbstractNettyRemotingClient#init
clientBootstrap.start
启动netty client
RMClient#init
RMClient
同样使用NettyClient
进行连接
RmNettyRemotingClient#init
RmNettyRemotingClient
和 TmNettyRemotingClient
都是AbstractNettyRemotingClient
的子类,super.init()
方法是相同的,都是利用启动netty
客户端
HttpAutoConfiguration
HttpAutoConfiguration
就是在springMVC
注入一个拦截器,这个拦截器会往事务全局上下文添加Xid
和清楚Xid
TransactionPropagationInterceptor
SeataSagaAutoConfiguration
SeataSagaAutoConfiguration
主要是实现Saga
模式,因为平常基本使用AT
,TCC
模式居多,这里暂时不做分析
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 25岁的心里话
· 闲置电脑爆改个人服务器(超详细) #公网映射 #Vmware虚拟网络编辑器
· 零经验选手,Compose 一天开发一款小游戏!
· 通过 API 将Deepseek响应流式内容输出到前端
· 因为Apifox不支持离线,我果断选择了Apipost!