Apache Seata应用侧启动过程剖析——RM TM如何与TC建立连接

embedded/2024/9/18 12:36:16/ 标签: apache, 分布式, seata, 分布式事务

本文来自 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.ppmy.cn/embedded/58232.html

相关文章

【云原生】Kubernetes之持久化

Kubernetes 持久化存储 文章目录 Kubernetes 持久化存储一、为什么要做持久化存储二、都有哪些存储2.1、emptyDir2.1.1、什么是emptyDir2.1.2、emptyDir作用2.1.3、emptyDir的应用场景2.1.4、emptyDir优缺点2.1.5、emptyDir的使用方式 2.2、hostPath2.2.1、什么是hostPath2.2.2…

基于DPU的云原生计算资源共池管理解决方案

1. 方案背景和挑战 在传统的云环境中&#xff0c;通常存在着不同的技术栈&#xff0c;支撑多样化的计算服务&#xff0c;具体如下&#xff1a; ① OpenStack环境与虚拟化云主机及裸金属服务 OpenStack是一个开源的云计算管理平台项目&#xff0c;它提供了部署和管理大规模计…

【c++刷题笔记-动态规划】day32: 509. 斐波那契数 、 70. 爬楼梯 、 746. 使用最小花费爬楼梯

509. 斐波那契数 - 力扣&#xff08;LeetCode&#xff09; class Solution { public:int fib(int n) {if(n0){return 0;}vector<int>dp(n1);dp[0]0;dp[1]1;for(int i2;i<n;i){dp[i]dp[i-1]dp[i-2];}return dp[n];} }; 70. 爬楼梯 - 力扣&#xff08;LeetCode&#x…

【最新鸿蒙应用开发】——Navigation路由管理

Navigation路由 1.引言 一多开发的项目适合使用Navigation进行统一的页面路由管理。Navigation还提供统一的标题栏、工具栏、菜单栏&#xff0c;并且自带导航返回功能。另外&#xff0c;Navigation还支持一些Router不支持的功能&#xff0c;比如&#xff1a;自带的路由拦截功…

【网络安全】这些网络安全知识请牢记!

随着社会信息化深入发展&#xff0c;互联网对人类文明进步将发挥更大促进作用&#xff0c;但与此同时&#xff0c;互联网领域的问题也日益凸显&#xff0c;网络犯罪、网络攻击等时有发生&#xff0c;网络安全与每个人都息息相关&#xff0c;下面一起来了解网络安全知识吧&#…

C语言 结构体和共用体——结构体类型与结构体变量

目录 问题的提出 数组的解决方法 我们希望的内存分配图 如何声明一个结构体类型&#xff1f; 如何定义一个结构体变量&#xff1f; 用typedef给数据类型定义一个别名 如何定义一个结构体变量&#xff1f; 结构体变量的初始化 问题的提出 数组的解决方法 我们希望的内存…

DBeaver安装教程(开发人员和数据库管理员通用数据库管理工具)

前言 DBeaver 是一个通用的数据库管理工具和 SQL 客户端&#xff0c;支持 MySQL, PostgreSQL, Oracle, DB2, MSSQL, Sybase, Mimer, HSQLDB, Derby, 以及其他兼容 JDBC 的数据库。DBeaver 提供一个图形界面用来查看数据库结构、执行SQL查询和脚本&#xff0c;浏览和导出数据&a…

Mac/Linux安装JMeter压测工具

Mac安装JMeter压测工具 介绍 Apache JMeter™应用程序是开源软件&#xff0c;是一个100%纯的Java应用程序&#xff0c;旨在加载测试功能行为和衡量性能。它最初是为测试Web应用程序而设计的&#xff0c;但后来扩展到其他测试功能。 我能用它做什么&#xff1f; Apache JMet…

STM32学习4

中断系统 定义 中断&#xff1a;在主程序运行过程中&#xff0c;出现了特定的中断触发条件&#xff08;中断源&#xff09;&#xff0c;使得CPU暂停当前正在运行的程序&#xff0c;转而去处理中断程序&#xff0c;处理完成后又返回原来被暂停的位置继续运行 中断优先级&…

生成图质量评价

1. RichHF-18K 论文地址 解决问题&#xff1a; 如何对生成图质量进行算法评价&#xff0c;以优化图片质量&#xff0c;提升模型生成能力 解决思路&#xff1a; 参考多模态模型&#xff0c;构建评价模型&#xff0c;从7个维度分三个分支对生成图进行测评&#xff1a; Tips&…

【大模型LLM面试合集】大语言模型基础_Word2Vec

Word2Vec 文章来源&#xff1a;Word2Vec详解 - 知乎 (zhihu.com) 1.Word2Vec概述 Word2Vec是google在2013年推出的一个NLP工具&#xff0c;它的特点是能够将单词转化为向量来表示&#xff0c;这样词与词之间就可以定量的去度量他们之间的关系&#xff0c;挖掘词之间的联系。 …

如何安全隐藏IP地址,防止网络攻击?

当您想在互联网上保持隐私或匿名时&#xff0c;您应该做的第一件事就是隐藏您的 IP 地址。您的 IP 地址很容易被追踪到您&#xff0c;并被用来了解您的位置。下面的文章将教您如何隐藏自己&#xff0c;不让任何试图跟踪您的活动的人发现。 什么是 IP 地址&#xff1f; 首先&am…

IPXProxy代理IP批量配置至Morelogin指纹浏览器的步骤解析

Morelogin指纹浏览器本身不提供代理IP服务&#xff0c;用户需要通过第三方来获取代理服务器&#xff0c;然后配置到Morelogin指纹浏览器中进行使用。指纹浏览器的连接速度取决于代理IP服务器的质量&#xff0c;因此选择合适的代理服务器十分重要&#xff0c;这里给大家推荐IPXP…

JAVA连接FastGPT实现流式请求SSE效果

FastGPT 是一个基于 LLM 大语言模型的知识库问答系统&#xff0c;提供开箱即用的数据处理、模型调用等能力。同时可以通过 Flow 可视化进行工作流编排&#xff0c;从而实现复杂的问答场景&#xff01; 一、先看效果 真正实流式请求&#xff0c;SSE效果&#xff0c;SSE解释&am…

数据结构--二叉树相关例题4

运用到malloc函数&#xff0c;因为之前忘记它的使用方法&#xff0c;因此附加一个 动态内存管理&#xff08;前面内容中有讲解过&#xff09;的知识点 1.二叉树遍历 //二叉树遍历 //属于IO类型题有输入有输出//因为输入包括1行字符串&#xff0c;长度不超过100&#xff0c;所以…

spring的bean注册

bean注册 第三方jar包的类想添加到ioc中&#xff0c;加不了Component该怎么办呢。 可以使用Bean和Import引入jar包&#xff0c;可以使用maven安装到本地仓库。 修改bean的名字&#xff1a;Bean("aaa")使用ioc的已经存在的bean对象&#xff0c;如Country&#xff1a;p…

说一下浏览器中的强缓存和协商缓存的区别

写在前面 对于一道高频的面试题&#xff0c;可能很多小伙伴还不知道这两者的概念&#xff0c;不知道是用来做什么的&#xff0c;以及有什么好处&#xff0c;强缓存和协商缓存是 Web 缓存机制的重要组成部分&#xff0c;它们在优化 Web 应用性能方面发挥了重要作用&#xff0c;…

【阿里云项目之可道云(ecs+rds-mysql+oss)】

目录 项目概述&#xff1a; 一、可道云项目下载地址二、阿里云-安全组与vpc1、vpc2、安全组3、创建ECS 三、阿里云-rds数据库1、连接数据库 四、可道云项目的部署1、修改配置文件2、下载可道云项目3、输入ip地址测试配置 五、 阿里云-访问控制-ram六、OSS对象存储&#xff08;存…

mysql面试题 Day5

1 什么是事务&#xff1f; 事务是指 多个数据库操作组成一个逻辑执行单元&#xff0c;满足ACID四个条件。 A是指原子性&#xff0c;事务保证操作要么全部完成&#xff0c;要么全部不完成&#xff0c;不会出现部分完成的情况&#xff1b; C是指一致性&#xff0c;事务执行后&…

诺基亚老年机突然翻红,为了情怀你会入手吗?

在智能手机功能日益丰富的今天&#xff0c;诺基亚3210的回归&#xff0c;似乎为人们提供了一种逃离现代科技束缚的选项。这款曾经的经典手机&#xff0c;以其复古的外观和简单的功能&#xff0c;吸引了一批怀旧用户的追捧。然而&#xff0c;它真的能够满足现代人的需求吗&#…