Apache Seata應用側啟動過程剖析——RM TM如何與TC建立連接

本文來自 Apache Seata官方文檔,歡迎訪問官網,查看更多深度文章。
本文來自 Apache Seata官方文檔,歡迎訪問官網,查看更多深度文章。
Apache Seata應用側啟動過程剖析——RM & TM如何與TC建立連接

前言

看過官網 README 的第一張圖片的同學都應該清楚,Seata 協調分布式事務的原理便在于通過其協調器側的 TC,來與應用側的 TM、RM 進行各種通信與交互,來保證分布式事務中,多個事務參與者的數據一致性。那么 Seata 的協調器側與應用側之間,是如何建立連接并進行通信的呢?

沒錯,答案就是 Netty,Netty 作為一款高性能的 RPC 通信框架,保證了 TC 與 RM 之間的高效通信,關于 Netty 的詳細介紹,本文不再展開,今天我們探究的重點,在于應用側在啟動過程中,如何通過一系列 Seata 關鍵模塊之間的協作(如 RPC、Config/Registry Center 等),來建立與協調器側之間的通信

從 GlobalTransactionScanner 說起

我們知道 Seata 提供了多個開發期注解,比如用于開啟分布式事務的@GlobalTransactional、用于聲明 TCC 兩階段服務的@TwoPhraseBusinessAction 等,它們都是基于 Spring AOP 機制,對使用了注解的 Bean 方法分配對應的攔截器進行增強,來完成對應的處理邏輯。而 GlobalTransactionScanner 這個 Spring Bean,就承載著為各個注解分配對應的攔截器的職責,從其 Scanner 的命名,我們也不難推斷出,它是為了在 Spring 應用啟動過程中,對與全局事務(GlobalTransactionScanner)相關的 Bean 進行掃描、處理的。

除此之外,應用側 RPC 客戶端(TMClient、RMClient)初始化、與 TC 建立連接的流程,也是在 GlobalTransactionScanner#afterPropertiesSet()中發起的:

    /*** package:io.seata.spring.annotation* class:GlobalTransactionScanner*/@Overridepublic void afterPropertiesSet() {if (disableGlobalTransaction) {if (LOGGER.isInfoEnabled()) {LOGGER.info("Global transaction is disabled.");}return;}//在Bean屬性初始化之后,執行TM、RM的初始化initClient();}

RM & TM 的初始化與連接過程

這里,我們以 RMClient.init()為例說明,TMClient 的初始化過程亦同理。

類關系的設計

查看 RMClient#init()的源碼,我們發現,RMClient 先構造了一個 RmNettyRemotingClient,然后執行其初始化init()方法。而 RmNettyRemotingClient 的構造器初始化方法,都會逐層調用父類的構造器與初始化方法

    /*** RMClient的初始化邏輯* package:io.seata.rm* class:RMClient*/public static void init(String applicationId, String transactionServiceGroup) {//① 首先從RmNettyRemotingClient類開始,依次調用父類的構造器RmNettyRemotingClient rmNettyRemotingClient = RmNettyRemotingClient.getInstance(applicationId, transactionServiceGroup);rmNettyRemotingClient.setResourceManager(DefaultResourceManager.get());rmNettyRemotingClient.setTransactionMessageHandler(DefaultRMHandler.get());//② 然后從RmNettyRemotingClient類開始,依次調用父類的init()rmNettyRemotingClient.init();}

上述 RMClient 系列各類之間的關系以及調用構造器和 init()初始化方法的過程如下圖示意:
RMClient.init簡化版流程與主要類之間的關系

那么為何要將 RMClient 設計成這樣較為復雜的繼承關系呢?其實是為了將各層的職責、邊界劃分清楚,使得各層可以專注于特定邏輯處理,實現更好的擴展性,這部分的詳細設計思路,可參考 Seata RPC 模塊重構 PR 的操刀者乘輝兄的文章Seata-RPC 重構之路)

初始化的完整流程

各類的構造器與初始化方法中的主要邏輯,大家可以借助下面這個能表意的序列圖來梳理下,此圖大家也可先跳過不看,在下面我們分析過幾個重點類后,再回頭來看這些類是何時登場、如何交互的協作的。
RMClient的初始化流程

抓住核心——Channel 的創建

首先我們需要知道,應用側與協調器側的通信是借助 Netty 的 Channel(網絡通道)來完成的,因此通信過程的關鍵在于 Channel 的創建,在 Seata 中,通過池化的方式(借助了 common-pool 中的對象池)方式來創建、管理 Channel。

這里我們有必要簡要介紹下對象池的簡單概念及其在 Seata 中的實現:
涉及到的 common-pool 中的主要類:

  • GenericKeydObjectPool<K, V>:KV 泛型對象池,提供對所有對象的存取管理,而對象的創建由其內部的工廠類來完成
  • KeyedPoolableObjectFactory<K, V>:KV 泛型對象工廠,負責池化對象的創建,被對象池持有

涉及到的 Seata 中對象池實現相關的主要類:

  • 首先,被池化管理的對象就是Channel,對應 common-pool 中的泛型 V
  • NettyPoolKey:Channel 對應的 Key,對應 common-pool 中的泛型 K,NettyPoolKey 主要包含兩個信息:
    • address:創建 Channel 時,對應的 TC Server 地址
    • message:創建 Channel 時,向 TC Server 發送的 RPC 消息體
  • GenericKeydObjectPool<NettyPoolKey,Channel>:Channel 對象池
  • NettyPoolableFactory:創建 Channel 的工廠類

認識了上述對象池相關的主要類之后,我們再來看看 Seata 中涉及 Channel 管理以及與 RPC 相關的幾個主要類:

  • NettyClientChannelManager:
    • 持有 Channel 對象池
    • 與 Channel 對象池交互,對應用側 Channel 進行管理(獲取、釋放、銷毀、緩存等)
  • RpcClientBootstrap:RPC 客戶端核心引導類,持有 Netty 框架的 Bootstrap 對象,具備啟停能力;具有根據連接地址來獲取新 Channel 的能力,供 Channel 工廠類調用
  • AbstractNettyRemotingClient:
    • 初始化并持有 RpcClientBootstrap
    • 應用側 Netty 客戶端的頂層抽象,抽象了應用側 RM/TM 取得各自 Channel 對應的 NettyPoolKey 的能力,供 NettyClientChannelManager 調用
    • 初始化 NettyPoolableFactory

了解上述概念后,我們可以把 Seata 中創建 Channel 的過程簡化如下:
創建Channel對象過程

看到這里,大家可以回過頭再看看上面的RMClient 的初始化序列圖,應該會對圖中各類的職責、關系,以及整個初始化過程的意圖有一個比較清晰的理解了。

建立連接的時機與流程

那么,RMClient 是何時與 Server 建立連接的呢?

在 RMClient 初始化的過程中,大家會發現,很多 init()方法都設定了一些定時任務,而 Seata 應用側與協調器的重連(連接)機制,就是通過定時任務來實現的:

    /*** package:io.seata.core.rpcn.netty* class:AbstractNettyRemotingClient*/public void init() {//設置定時器,定時重連TC ServertimerExecutor.scheduleAtFixedRate(new Runnable() {@Overridepublic void run() {clientChannelManager.reconnect(getTransactionServiceGroup());}}, SCHEDULE_DELAY_MILLS, SCHEDULE_INTERVAL_MILLS, TimeUnit.MILLISECONDS);if (NettyClientConfig.isEnableClientBatchSendRequest()) {mergeSendExecutorService = new ThreadPoolExecutor(MAX_MERGE_SEND_THREAD,MAX_MERGE_SEND_THREAD,KEEP_ALIVE_TIME, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<>(),new NamedThreadFactory(getThreadPrefix(), MAX_MERGE_SEND_THREAD));mergeSendExecutorService.submit(new MergedSendRunnable());}super.init();clientBootstrap.start();}

我們通過跟蹤一次 reconnect 的執行,看看上面探究的幾個類之間是如何協作,完成 RMClient 與 TC 的連接的(實際上首次連接可能發生在 registerResource 的過程中,但流程一致)
RMClient與TC Server連接過程

這個圖中,大家可以重點關注這幾個點:

  • NettyClientChannelManager 執行具體 AbstractNettyRemotingClient 中,獲取 NettyPoolKey 的回調函數(getPoolKeyFunction()):應用側的不同 Client(RMClient 與 TMClient),在創建 Channel 時使用的 Key 不同,使兩者在重連 TC Server 時,發送的注冊消息不同,這也是由兩者在 Seata 中扮演的角色不同而決定的:
    • TMClient:扮演事務管理器角色,創建 Channel 時,僅向 TC 發送 TM 注冊請求(RegisterTMRequest)即可
    • RMClient:扮演資源管理器角色,需要管理應用側所有的事務資源,因此在創建 Channel 時,需要在發送 RM 注冊請求(RegesterRMRequest)前,獲取應用側所有事務資源(Resource)信息,注冊至 TC Server
  • 在 Channel 對象工廠 NettyPoolableFactory 的 makeObject(制造 Channel)方法中,使用 NettyPoolKey 中的兩項信息,完成了兩項任務:
    • 使用 NettyPoolKey 的 address 創建新的 Channel
    • 使用 NettyPoolKey 的 message 以及新的 Channel 向 TC Server 發送注冊請求,這就是 Client 向 TC Server 的連接(首次執行)或重連(非首次,由定時任務驅動執行)請求

以上內容,就是關于 Seata 應用側的初始化及其與 TC Server 協調器側建立連接的全過程分析。

更深層次的細節,建議大家再根據本文梳理的脈絡和提到的幾個重點,細致地閱讀下源碼,相信定會有更深層次的理解和全新的收獲!

后記:考慮到篇幅以及保持一篇源碼分析文章較為合適的信息量,本文前言中所說的配置、注冊等模塊協作配合并沒有在文章中展開和體現。

在下篇源碼剖析中,我會以配置中心注冊中心為重點,為大家分析,在 RMClient/TM Client 與 TC Server 建立連接之前,Seata 應用側是如何通過服務發現找到 TC Server、如何從配置模塊獲取各種信息的。

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/web/41113.shtml
繁體地址,請注明出處:http://hk.pswp.cn/web/41113.shtml
英文地址,請注明出處:http://en.pswp.cn/web/41113.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

Android最近任務顯示的圖片

Android最近任務顯示的圖片 1、TaskSnapshot截圖1.1 snapshotTask1.2 drawAppThemeSnapshot 2、導航欄顯示問題3、Recentan按鍵進入最近任務 1、TaskSnapshot截圖 frameworks/base/services/core/java/com/android/server/wm/TaskSnapshotController.java frameworks/base/cor…

IPython 性能評估工具的較量:%%timeit 與 %timeit 的差異解析

IPython 性能評估工具的較量&#xff1a;%%timeit 與 %timeit 的差異解析 在 IPython 的世界中&#xff0c;性能評估是一項至關重要的任務。%%timeit 和 %timeit 是兩個用于測量代碼執行時間的魔術命令&#xff0c;但它們之間存在一些關鍵的差異。本文將深入探討這兩個命令的不…

2786. 訪問數組中的位置使分數最大

2786. 訪問數組中的位置使分數最大 題目鏈接&#xff1a;2786. 訪問數組中的位置使分數最大 代碼如下&#xff1a; //參考鏈接:https://leetcode.cn/problems/visit-array-positions-to-maximize-score/solutions/2810335/dp-by-kkkk-16-tn9f class Solution { public:long …

vue-router 4匯總

一、vue和vue-router版本&#xff1a; "vue": "^3.4.29", "vue-router": "^4.4.0" 二、路由傳參&#xff1a; 方式一&#xff1a; 路由配置&#xff1a;/src/router/index.ts import {createRouter,createWebHistory } from &quo…

探索 WebKit 的緩存迷宮:深入理解其高效緩存機制

探索 WebKit 的緩存迷宮&#xff1a;深入理解其高效緩存機制 在當今快速變化的網絡世界中&#xff0c;WebKit 作為領先的瀏覽器引擎之一&#xff0c;其緩存機制對于提升網頁加載速度、減少服務器負載以及改善用戶體驗起著至關重要的作用。本文將深入探討 WebKit 的緩存機制&am…

代碼隨想錄leetcode200題之額外題目

目錄 1 介紹2 訓練3 參考 1 介紹 本博客用來記錄代碼隨想錄leetcode200題之額外題目相關題目。 2 訓練 題目1&#xff1a;1365. 有多少小于當前數字的數字 解題思路&#xff1a;二分查找。 C代碼如下&#xff0c; class Solution { public:vector<int> smallerNumb…

卷積神經網絡(CNN)和循環神經網絡(RNN) 的區別與聯系

卷積神經網絡&#xff08;CNN&#xff09;和循環神經網絡&#xff08;RNN&#xff09;是兩種廣泛應用于深度學習的神經網絡架構&#xff0c;它們在設計理念和應用領域上有顯著區別&#xff0c;但也存在一些聯系。 ### 卷積神經網絡&#xff08;CNN&#xff09; #### 主要特點…

解決C++編譯時的產生的skipping incompatible xxx 錯誤

問題 我在編譯項目時&#xff0c;產生了一個 /usr/bin/ld: skipping incompatible ../../xxx/ when searching for -lxxx 的編譯錯誤&#xff0c;如下圖所示&#xff1a; 解決方法 由圖中的錯誤可知&#xff0c;在編譯時&#xff0c;是能夠在我們指定目錄下的 *.so 動態庫的…

python函數和c的區別有哪些

Python有很多內置函數&#xff08;build in function&#xff09;&#xff0c;不需要寫頭文件&#xff0c;Python還有很多強大的模塊&#xff0c;需要時導入便可。C語言在這一點上遠不及Python&#xff0c;大多時候都需要自己手動實現。 C語言中的函數&#xff0c;有著嚴格的順…

Java基礎(六)——繼承

個人簡介 &#x1f440;個人主頁&#xff1a; 前端雜貨鋪 ?開源項目&#xff1a; rich-vue3 &#xff08;基于 Vue3 TS Pinia Element Plus Spring全家桶 MySQL&#xff09; &#x1f64b;?♂?學習方向&#xff1a; 主攻前端方向&#xff0c;正逐漸往全干發展 &#x1…

【Web】

1、配倉庫 [rootlocalhost yum.repos.d]# vi rpm.repo ##本地倉庫標準寫法 [baseos] namemiaoshubaseos baseurl/mnt/BaseOS gpgcheck0 [appstream] namemiaoshuappstream baseurlfile:///mnt/AppStream gpgcheck0 2、掛載 [rootlocalhost ~]mount /dev/sr0 /mnt mount: /m…

QT操作各類數據庫用法詳解

文章目錄 創建內存SQLITE數據庫QSqlTableModel操作數據庫表連接國產數據庫多線程數據處理不指定數據庫名打開數據庫QT對各種數據庫的支持情況處理數據庫表名QT連接各種數據庫Qt提供了一個名為QtSQL模塊的強大組件, 使得在Qt應用程序中連接和操作多種類型的數據庫變得相對簡單。…

Vulnhub-Os-hackNos-1(包含靶機獲取不了IP地址)

https://download.vulnhub.com/hacknos/Os-hackNos-1.ova #靶機下載地址 題目&#xff1a;要找到兩個flag user.txt root.txt 文件打開 改為NAT vuln-hub-OS-HACKNOS-1靶機檢測不到IP地址 重啟靶機 按住shift 按下鍵盤字母"E"鍵 將圖中ro修改成…

Github 2024-07-06 開源項目日報 Top10

根據Github Trendings的統計,今日(2024-07-06統計)共有10個項目上榜。根據開發語言中項目的數量,匯總情況如下: 開發語言項目數量Python項目3TypeScript項目2Rust項目2非開發語言項目1C++項目1QML項目1MDX項目1JavaScript項目1Assembly項目1免費編程書籍和學習資源清單 創建…

JS 四舍五入使用整理

一、Number.toFixed() 把數字轉換為字符串,結果的小數點后有指定位數的數字,重點返回的數據類型為字符串 toFixed() 方法將一個浮點數轉換為指定小數位數的字符串表示,如果小數位數高于數字,則使用 0 來填充。 toFixed() 方法可把 Number 四舍五入為指定小數位數的數字。…

Vue 3集成krpano 全景圖展示

Vue 3集成krpano 全景圖展示 星光云全景系統源碼 VR全景體驗地址 星光云全景VR系統 將全景krpano靜態資源文件vtour放入vue項目中 導入vue之前需要自己制作一個全景圖 需要借助官方工具進行制作 工具下載地址&#xff1a;krpano工具下載地址 注意事項&#xff1a;vuecli…

Hook 實現 Windows 系統熱鍵屏蔽(二)

目錄 前言 一、介紹用戶賬戶控制&#xff08;UAC&#xff09; 1.1 什么是 UAC &#xff1f; 2.2 UAC 運行機制的概述 2.3 分析 UAC 提權參數 二、 NdrAsyncServerCall 函數的分析 2.1 函數聲明的解析 2.2 對 Winlogon 的逆向 2.3 對 rpcrt4 的靜態分析 2.4 對 rpcrt4…

YOLOv8_obb數據集可視化[旋轉目標檢測實踐篇]

先貼代碼,周末再補充解析。 這個篇章主要是對標注好的標簽進行可視化,雖然比較簡單,但是可以從可視化代碼中學習到YOLOv8是如何對標簽進行解析的。 import cv2 import numpy as np import os import randomdef read_obb_labels(label_file_path):with open(label_file_path,…

探索 IPython 的環境感知能力:詳解 %env 命令的妙用

探索 IPython 的環境感知能力&#xff1a;詳解 %env 命令的妙用 在數據科學和編程的海洋中&#xff0c;環境變量扮演著至關重要的角色。IPython&#xff0c;這一強大的交互式計算工具&#xff0c;通過其內置的魔術命令 %env&#xff0c;為我們提供了與環境變量交互的強大能力。…

git基礎指令總結持續更新之git分支簡介和基本操作,解決合并和沖突,回退和rebase(變基),分支命名和分支管理,學習筆記分享

git 分支簡介和基本操作 Git 分支是 Git 的核心特性之一&#xff0c;它允許開發者在不同的開發線上工作&#xff0c;而不會影響主代碼庫。以下是 Git 分支的簡介和一些基本操作&#xff1a; 分支的概念&#xff1a; 分支是 Git 中的一個獨立開發線。創建分支時&#xff0c;G…