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模塊與核心協調模塊。

掃碼_搜索聯合傳播樣式-標準色版

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章