Seata-Server 启动流程源码分析

认识Seata

Seata 是一款开源的分布式事务解决方案,致力于提供高性能和简单易用的分布式事务服务。Seata 将为用户提供了 AT、TCC、SAGA 和 XA 事务模式,为用户打造一站式的分布式解决方案。

Seata的TXC模型

img

根据上图可知整个TXC模型有三个重要的组件

  • TC 事务协调器,维护全局和分支事务的状态,驱动全局事务提交或回滚。(单独部署)
  • TM 事务管理器 定义全局事务的范围:开始全局事务、提交或回滚全局事务。
  • RM 资源管理器 管理分支事务处理的资源,与TC交谈以注册分支事务和报告分支事务的状态,并驱动分支事务提交或回滚。

简单理解就是TM事务管理器通过RPC与TC通讯请求开启一个全局事务

简单理解过程就是: Business作为服务起始方(此时它是TM)发起全局事务并注册到TC。在调用协同服务时,协同服务的事务分支事务会先完成阶段一的事务提交或回滚,并生成事务回滚的undo_log日志,同时注册当前服务到TC并上报其事务状态,归并到同一个业务的全局事务中。此时若没有问题继续下一个服务的调用,期间任何服务的分支事务回滚,都会通知到TC,TC在通知全局事务包含的所有已完成一阶段提交的分支事务回滚。如果所有分支事务都正常,最后回到全局事务发起方时,也会通知到TC,TC在通知全局事务包含的所有分支删除回滚日志。在这个过程中为了解决写隔离和度隔离的问题会涉及到TC管理的全局锁。

那么全局事务是如何在服务中传递的呢?实际在TM向TC请求开启一个全局事务的时候,TC会响应一个全局事务XID,只需要TM在调用其他协同服务时把XID传递给协同服务,这样就可以实现全局事务在分布式服务中传播,以及分支事务属于哪个全局事务。

Seata目前已经支持许多框架中的XID的自动传递了

  • dubbo
  • spring cloud
  • sofa-rpc

用户在使用Seata的时候对于XID的传递完全是无感知。

上文提到Seata中三个重要的组件TC TM RM.

其中TC作为事务协调者, 它负责驱动全局事务的提交与回滚。根据它的职责可知。它的重要性不言而喻。

那么作为一个优秀的协调者它需要具备哪些功能呢?

  • 高可用
  • 高性能
  • 支持扩展

那么我们根据我们的猜测来看看TC的实现模块Server是怎么来实现这写功能的。

Server模块介绍

image-20200331221613306

整个Server模块可以分成7个主要模块

  • RPC模块 负责与TM RM交互
  • Coordinator Core模块 TC实现事务协调的核心模块
  • Lock模块 资源全局锁的实现
  • Config模块 支持配置TC的配置模块
  • Store模块 TC运行时全局事务以及分支事务的相关信息需要通过Store模块持久化
  • Discover模块 Seata TC服务注册发现模块
  • HA-Cluste模块 TC Server实现高可用的模块

就一个Server端而言, 它就有7个模块。那么我们改从何看起呢。

我们可以用Server启动的main函数来理解清楚整个TC的运行流程

Server启动流程

本文所有源码基于Seata1.1.0 个人能力有限,如有不对欢迎指出。

整个Server端是一个java应用,它是通过java -jar启动的,所以主入口是一个main函数。

入口地址是io.seata.server.Server#main()

public static void main(String[] args) throws IOException {
        //1、 参数解析
        ParameterParser parameterParser = new ParameterParser(args);

        //2、 监控初始化
        MetricsManager.get().init();

        // 3、将存储模式放到系统环境变量÷
        System.setProperty(ConfigurationKeys.STORE_MODE, parameterParser.getStoreMode());

        // 4、创建与RM TM通讯的rpc服务器
        RpcServer rpcServer = new RpcServer(WORKING_THREADS);
        //server port
        rpcServer.setListenPort(parameterParser.getPort());
        UUIDGenerator.init(parameterParser.getServerNode());
        //log store mode : file, db
        // 5、设置资源存储模式
        SessionHolder.init(parameterParser.getStoreMode());
        // 6、核心事务协调器创建
        DefaultCoordinator coordinator = new DefaultCoordinator(rpcServer);
        coordinator.init();
        // 7、把协调器作为一个回调 传给netty rpc模块
        rpcServer.setHandler(coordinator);
        // 8、注册JVM关闭构造函数 
        ShutdownHook.getInstance().addDisposable(coordinator);
        ShutdownHook.getInstance().addDisposable(rpcServer);

        //127.0.0.1 and 0.0.0.0 are not valid here.
        if (NetUtil.isValidIp(parameterParser.getHost(), false)) {
            XID.setIpAddress(parameterParser.getHost());
        } else {
            XID.setIpAddress(NetUtil.getLocalIp());
        }
        XID.setPort(rpcServer.getListenPort());

        try {
            // 9、启动RPC模块 监听TM RM的请求
            rpcServer.init();
        } catch (Throwable e) {
            LOGGER.error("rpcServer init error:{}", e.getMessage(), e);
            System.exit(-1);
        }

        System.exit(0);
    }

首先看看参数解析,其实参数解析很简单主要是通过JCommander解析main函数中的args数组,不过在需要注意的是,由于Seata Server已经支持容器部署, 所以在容器环境启动参数的创建跟正常启动的参数是不同的。容器部署的启动参数需要通过System.getenv获取

io.seata.server.ParameterParser#init()

private void init(String[] args) {
        try {
            // 判断启动环境是否是容器
            boolean inContainer = this.isRunningInContainer();

            // 如果是容器启动 则从系统环境变量读取参数配置
            if (inContainer) {
                if (LOGGER.isInfoEnabled()) {
                    LOGGER.info("The server is running in container.");
                }
                this.seataEnv = StringUtils.trimToNull(System.getenv(ENV_SYSTEM_KEY));
                this.host = StringUtils.trimToNull(System.getenv(ENV_SEATA_IP_KEY));
                this.serverNode = NumberUtils.toInt(System.getenv(ENV_SERVER_NODE_KEY), SERVER_DEFAULT_NODE);
                this.port = NumberUtils.toInt(System.getenv(ENV_SEATA_PORT_KEY), SERVER_DEFAULT_PORT);
                this.storeMode = StringUtils.trimToNull(System.getenv(ENV_STORE_MODE_KEY));
            } else {
                // 否则使用JCommander 解析启动参数
                JCommander jCommander = JCommander.newBuilder().addObject(this).build();
                jCommander.parse(args);
                if (help) {
                    jCommander.setProgramName(PROGRAM_NAME);
                    jCommander.usage();
                    System.exit(0);
                }
            }
            if (StringUtils.isNotBlank(seataEnv)) {
                System.setProperty(ENV_PROPERTY_KEY, seataEnv);
            }
            if (StringUtils.isBlank(storeMode)) {
                storeMode = ConfigurationFactory.getInstance().getConfig(ConfigurationKeys.STORE_MODE,
                    SERVER_DEFAULT_STORE_MODE);
            }
        } catch (ParameterException e) {
            printError(e);
        }

    }

拿到启动参数后我们就要根据启动参数依次 启动监控、设置存储模型,创建协调核心对象、启动Rpc服务器。

为什么Rpc服务器要在最后一个启动呢? 下篇文章会解答。

由于监控对Seata的核心功能暂无影响所以本文已经后续文章暂不对监控进行分析。

总结

本文简单的介绍了一下Seata Server模块启动流程的一个分析,了解Seata的启动流程,但是都是比较简单没有深入,后续会陆续深入分析Rpc模块与核心协调模块。

你可能感兴趣的:(java,后端,spring,分布式,seata)