SpringCloud Alibaba系列——17Seata AT模式源码分析(上)

老老老JR老北
发布于 2022-8-16 11:53
浏览
0收藏

作者 | 一起撸Java
来源 |今日头条

学习目标

  • Seata AT模式源码流程
    第1章 AT模式流程
    1.1 思维流程推导
    上文中已经讲了AT模式的大体原理,在源码中,通过README也能看出来AT模式的使用,那本文将从底层源码层面去分析AT模式的原理,在分析原理之前咱们先来看三幅图,理解一下他的工作思路和模式:

先看看思维推导图SpringCloud Alibaba系列——17Seata AT模式源码分析(上)-鸿蒙开发者社区1.2 初始化流程推导
SpringCloud Alibaba系列——17Seata AT模式源码分析(上)-鸿蒙开发者社区1.3 执行流程推导
SpringCloud Alibaba系列——17Seata AT模式源码分析(上)-鸿蒙开发者社区第2章 源码分析
2.1 SeataAutoConfiguration
对于seata源码的研究主要看seata如何拦截业务SQL生成undo_log数据,如何在一阶段完成后提交全局事务,如何在一阶段业务失败后通过undo_log回滚事务,进行事务补偿。

seata也是与spring整合使用的,结合SpringBoot,seata也是做了一些自动配置SpringCloud Alibaba系列——17Seata AT模式源码分析(上)-鸿蒙开发者社区seata的自动配置类命名非常的直接,就叫做:SeataAutoConfiguration,我们打开这个类

@ComponentScan(basePackages = "io.seata.spring.boot.autoconfigure.properties")@ConditionalOnProperty(prefix = StarterConstants.SEATA_PREFIX, name = "enabled", havingValue = "true", matchIfMissing = true)@Configuration@EnableConfigurationProperties({SeataProperties.class})public class SeataAutoConfiguration {    }

首先,@ComponentScan扫描了一下properties包,加载了一大堆类似SeataProperties的Bean对象。

@ConditionalOnProperty将配置类生效条件设置为seata.enabled=true,默认值是true,所以可以开关分布式事务功能(在client端的file.conf里面可以配置)。

@Configuration表明,SeataAutoConfiguration被定义为了spring的配置类。

@EnableConfigurationProperties将配置包转成了一个SeataProperties的Bean对象来使用。

接下来阅读SeataAutoConfiguration的内部代码

@Bean@DependsOn({BEAN_NAME_SPRING_APPLICATION_CONTEXT_PROVIDER, BEAN_NAME_FAILURE_HANDLER})@ConditionalOnMissingBean(GlobalTransactionScanner.class)public GlobalTransactionScanner globalTransactionScanner(SeataProperties seataProperties, FailureHandler failureHandler) {    if (LOGGER.isInfoEnabled()) {        LOGGER.info("Automatically configure Seata");    }    return new GlobalTransactionScanner(seataProperties.getApplicationId(), seataProperties.getTxServiceGroup(), failureHandler);}

自动配置的核心点落在了下面的一个Bean,GlobalTransactionScanner。

我们看到构造这个Bean非常的简单,构造方法只需要一个applicationId和txServiceGroup。

applicationId: 就是spring.application.name=你定义的当前应用的名字,例如:userService

txServiceGroup: 就是以applicationId 加上 -seata-service-group命名的,例如:
userService-seata-service-group。如果版本较低的话,那时候可能还不叫seata而是fescar,因此默认命名就是以fescar为后缀。

new了一个GlobalTransactionScanner对象,SeataAutoConfiguration这个自动配置类的作用就结束了。SeataAutoConfiguration只是做了一个启动引导的作用。

2.2 GlobalTransactionScanner
既然核心点落在GlobalTransactionScanner这个类,我们继续关注它。看这个名字其实就可以猜测到一点它的作用,扫描@GlobalTransactional这个注解,并对代理方法进行拦截增强事务的功能。

要了解这个类,不得不先阅读一下它的UML图SpringCloud Alibaba系列——17Seata AT模式源码分析(上)-鸿蒙开发者社区可以看到,GlobalTransactionScanner主要有4个点值得关注:

1)ApplicationContextAware表示可以拿到spring容器

2)InitializingBean接口,表达了初始化的时候会进行一些操作

3)AbstractAutoProxyCreator表示它会对spring容器中的Bean进行切面增强,也就是我们上面的拦截事务增强的猜测。

4)Disposable接口,表达了spring容器销毁的时候会进行一些操作

这里我们稍微关注一下这4个的执行顺序:

ApplicationContextAware -> InitializingBean -> AbstractAutoProxyCreator -> DisposableBean

2.3 InitializingBean

@Overridepublic void afterPropertiesSet() {    if (disableGlobalTransaction) {        if (LOGGER.isInfoEnabled()) {            LOGGER.info("Global transaction is disabled.");        }        return;    }    initClient();}

初始化Seata的Client端的东西,Client端主要包括TransactionManager和ResourceManager。或许是为了简化吧,并没有把initClient这件事从GlobalTransactionScanner里面独立出来一个类。

跟进initClient方法

private void initClient() {    //init TM    TMClient.init(applicationId, txServiceGroup);       //init RM    RMClient.init(applicationId, txServiceGroup);      registerSpringShutdownHook();}

initClient逻辑并不复杂,单纯调用TMClient.init初始化TransactionManager的RPC客户端,RMClient.init初始化ResourceManager的RPC客户端。seata的RPC采用netty来实现,seata封装简化了一下使用。并注册了一个Spring的ShutdownHook钩子函数

2.3.1 TMClient初始化

@Overridepublic void init() {    timerExecutor.scheduleAtFixedRate(new Runnable() {        @Override        public void run() {            clientChannelManager.reconnect(getTransactionServiceGroup());        }    }, SCHEDULE_DELAY_MILLS, SCHEDULE_INTERVAL_MILLS, TimeUnit.MILLISECONDS);...}

启动了一个定时器不断进行重连操作,调用
clientChannelManager.reconnect方法进行重连

void reconnect(String transactionServiceGroup) {    List<String> availList = null;    try {        availList = getAvailServerList(transactionServiceGroup);    } catch (Exception e) {       ...    }   ...    for (String serverAddress : availList) {        try {            acquireChannel(serverAddress);        } catch (Exception e) {            ...        }    }}

根据transactionServiceGroup获取seata-server的ip地址列表,然后进行重连

private List<String> getAvailServerList(String transactionServiceGroup) throws Exception {    List<InetSocketAddress> availInetSocketAddressList = RegistryFactory.getInstance()        .lookup(transactionServiceGroup);    if (CollectionUtils.isEmpty(availInetSocketAddressList)) {        return Collections.emptyList();    }     return availInetSocketAddressList.stream()        .map(NetUtil::toStringAddress)        .collect(Collectors.toList());}

RegistryFactory.getInstance().lookup(transactionServiceGroup);是对不同注册中心做了适配的,默认看下Nacos形式的实现SpringCloud Alibaba系列——17Seata AT模式源码分析(上)-鸿蒙开发者社区先根据事务分组找到分组所属的server集群名称,这里是default,然后根据集群名称找到server对应ip端口地址

@Overridepublic List<InetSocketAddress> lookup(String key) throws Exception {    //default    String clusterName = getServiceGroup(key);    if (clusterName == null) {        return null;    }    if (!LISTENER_SERVICE_MAP.containsKey(clusterName)) {        synchronized (LOCK_OBJ) {            if (!LISTENER_SERVICE_MAP.containsKey(clusterName)) {                List<String> clusters = new ArrayList<>();                clusters.add(clusterName);                List<Instance> firstAllInstances = getNamingInstance().getAllInstances(getServiceName(), getServiceGroup(), clusters);                if (null != firstAllInstances) {                    List<InetSocketAddress> newAddressList = firstAllInstances.stream()                        .filter(instance -> instance.isEnabled() && instance.isHealthy())                        .map(instance -> new InetSocketAddress(instance.getIp(), instance.getPort()))                        .collect(Collectors.toList());                    CLUSTER_ADDRESS_MAP.put(clusterName, newAddressList);                }                subscribe(clusterName, event -> {                    List<Instance> instances = ((NamingEvent) event).getInstances();                    if (null == instances && null != CLUSTER_ADDRESS_MAP.get(clusterName)) {                        CLUSTER_ADDRESS_MAP.remove(clusterName);                    } else if (!CollectionUtils.isEmpty(instances)) {                        List<InetSocketAddress> newAddressList = instances.stream()                            .filter(instance -> instance.isEnabled() && instance.isHealthy())                            .map(instance -> new InetSocketAddress(instance.getIp(), instance.getPort()))                            .collect(Collectors.toList());                        CLUSTER_ADDRESS_MAP.put(clusterName, newAddressList);                    }                });            }        }    }    return CLUSTER_ADDRESS_MAP.get(clusterName);}

Seata-server的IP地址已获取到,然后调用acquireChannel

Channel acquireChannel(String serverAddress) {    Channel channelToServer = channels.get(serverAddress);    if (channelToServer != null) {        channelToServer = getExistAliveChannel(channelToServer, serverAddress);        if (channelToServer != null) {            return channelToServer;        }    }...    channelLocks.putIfAbsent(serverAddress, new Object());    synchronized (channelLocks.get(serverAddress)) {        return doConnect(serverAddress);    }}

最后将获取到的seata-server的IP地址放到Netty中封装,TmClient就初始化完毕

TmClient初始化总结:

  • 启动定时器,尝试进行一次重连seata-server
  • 重连时,先从nacos(或则其他配置)中根据分组名称(service_group)找到集群名称(cluster_name)
  • 再根据集群名称找到集群ip端口列表
  • 从ip列表中选择一个用netty进行连接
    2.3.2 RMClient初始化
public static void init(String applicationId, String transactionServiceGroup) {    // 获取单例对象    RmRpcClient rmRpcClient = RmRpcClient.getInstance(applicationId, transactionServiceGroup);    // 设置ResourceManager的单例对象    rmRpcClient.setResourceManager(DefaultResourceManager.get());    // 添加监听器,监听Server端的消息推送    rmRpcClient.setClientMessageListener(new RmMessageListener(DefaultRMHandler.get()));    // 初始化RPC    rmRpcClient.init();}​

和TMClient想比,RMClient多出了一个监听Server端消息并处理的机制。也就是说TM的职责更多的是主动与Server端通信,比如:全局事务的begin、commit、rollback等。

而RM除了主动操作本地资源外,还会因为全局事务的commit、rollback等的消息推送,从而对本地资源进行相关操作。

设置资源管理器resourceManager,设置消息回调监听器用于接收TC在二阶段发出的提交或者回滚请求,Seata中对ResourceManager,AbstractRMHandler做了SPI适配,以ResouceManager为例:

public class DefaultResourceManager implements ResourceManager {    protected void initResourceManagers() {        //init all resource managers        List<ResourceManager> allResourceManagers = EnhancedServiceLoader.loadAll(ResourceManager.class);        if (CollectionUtils.isNotEmpty(allResourceManagers)) {            for (ResourceManager rm : allResourceManagers) {                resourceManagers.put(rm.getBranchType(), rm);            }        }    }}

可以看到初始化DefaultResouceManager时会使用ClassLoader去加载对应Jar下的实现,而默认AT模式使用的实现是数据库,也就是rm-datasource包下的实现,找实现类路径需要定位到/resources/META-INF/扩展接口全路径去找,就会找到对应的实现类 SpringCloud Alibaba系列——17Seata AT模式源码分析(上)-鸿蒙开发者社区ResourceManager对应实现类全路径
io.seata.rm.datasource.DataSourceManager,该类中指定了了提交和回滚的方法,DefaultRMHandler对应实现类全路径io.seata.rm.RMHandlerAT,是个接收server消息并做对应提交或者回滚操作的回调处理类。

RMClinet的init()方法与TMClient基本一致

2.3.3 总结

  • Spring启动时,初始化了2个客户端TmClient、RmClient
  • TmClient与seata-server通过Netty建立连接并发送消息
  • RmClient与seata-server通过Netty建立连接,负责接收二阶段提交、回滚消息并在回调器(RmHandler)中做处理

分类
已于2022-8-16 11:53:53修改
收藏
回复
举报
回复
    相关推荐