开源分布式数据库PolarDB-X源码解读——PolarDB-X源码解读(二):CN启动流程

本文涉及的产品
云原生数据库 PolarDB MySQL 版,通用型 2核8GB 50GB
云原生数据库 PolarDB PostgreSQL 版,标准版 2核4GB 50GB
简介: 开源分布式数据库PolarDB-X源码解读——PolarDB-X源码解读(二):CN启动流程

本文主要讲解PolarDB-X的CN节点(polardbx-sql)的启动过程包括参数加载、元信息加载等过程并对启动过程中设计的模块做简单的介绍。  


CN Server层的代码主要包含在polardbx-server模块中,main函数位于TddlLauncher


主逻辑入口在CobarServer.init()方法中。


CN启动分为以下几个流程:


1.CobarServer对象的创建


该类是单例,可以通过CobarServer.getInstance()获取。


2.参数加载


路径:TddlLauncher.main() -> CobarServer.new() -> CobarConfig.new() -> CobarConfig.initCobarConfig() -> ServerLoader.load()


CN的参数均为key-value的形式。


ServerLoader.load()中,CN主要从以下位置读取参数:


 server.properties文件,其默认位置在classpath的根目录中,所以一般使

 用           IDE          进         行            开          发          时 ,     加          载          的         是

polardbx-server\src\main\resources\server.properties文件


String conf = 
System.getProperty("server.conf","classpath:server.properties");


 Java运行参数中


例如java...-DserverPort=8527


 环境变量中


serverProps.putAll(System.getProperties());
serverProps.putAll(System.getenv());


以上参数来源中,server.properties的优先级最低,环境变量中的优先级最高,当包含同名参数时,高优先级的参数来源会覆盖低优先级的参数来源。


加载的参数,会保存在SystemConfig中,该类为单例,可以通过CobarServer.getInstance().getConfig().getSystem()获取。


3.从MetaDB读取元数据,并初始化实例级的系统组件


路径:

TddlLauncher.main()->CobarServer.new()->CobarConfig.new()->CobarConfig.initCobarConfig()->ServerLoader.load()->ServerLoader.initPolarDbXComponents()


1)初始化元数据库的连接池


MetaDbDataSource.initMetaDbDataSource


会使用SystemConfig中存储的MetaDB的地址、端口、用户名、密码、库名等信息,建立与MetaDB的连接。


MetaDbDataSource
            .initMetaDbDataSource(this.system.getMetaDbAddr(), this.system.getMetaDbName(),
                this.system.getMetaDbProp(),
                this.system.getMetaDbUser(),
                this.system.getMetaDbPasswd());


MetaDbDataSource是一个单例,实现了JDBC的程序中可以使用MetaDbDataSource.getInstance().getConnection()获取与MetaDB的连接(实现了JDBC接口),并使用该连对MetaDB进行访问。


2)对系统表进行创建或者升级


SchemaChangeManager.getInstance().handle();


polardbx-gms\src\main\resources\ddl\中保存了系统表的表结构,并且使用alter语句记录了每一次版本的变更。SchemaChangeManager在初始化时,会检测当前每个系统表的表结构版本,如果版本较老,会依次使用这些语句对alter语句进行更新。


3)读取实例ID信息


ServerInstIdManager.getInstance();


一个PolarDB-X集群也称为一个实例,在MetaDB中有一个唯一的实例ID。同时,PolarDB-X实例有主实例与只读实例两种模式。当存在只读实例时,必然存在一个主实例。该步骤从MetaDB中读取实例ID,如果当前实例为只读实例,则还会读取其主实例的实例ID。  


4)MetaDbConfigManager


先简单介绍下这个东西是干啥的。


我们存在MetaDB中的配置信息,如果发生变化,CN需要能感知到这个变化。例如,某个表增加了一个列。作为CN,如何能感知到这个变化呢?


一个比较简单的思路是对元数据做轮询,各几秒查一次。但如果让每个模块都去做这样的事情,会有个比较大的问题是对MetaDB的访问压力会很大。


MetaDbConfigManager对此作了封装,作了一个统一的轮询机制。但由于每个模块元数据表的格式千变万化,所以MetaDbConfigManager并不是直接轮询每个模块的元数据表,而是轮询config_listener这张表。这张表的几个关键列是data_id、op_version、gmt_modified。


我们可以为每个data_id在代码中注册一个listener,当MetaDbConfigManager轮询到data_id的op_version发生变化的时,会回调这个listener,一般情况下,各模块实现的listener会按需要再读取对应的元数据表。


例如,d1.t1表在config_listener中有一行记录:


polardbx.meta.table.d1.t1


往t1表中增加了一个列,我们会修改MetaDB中的columns表,同时,我们会修改config_listener表中 polardbx.meta.table.d1.t1这行记录的op_version,进行+1的操作。


几秒钟后,MetaDbConfigManager会轮询到这行记录的op_version列发生了变化,会回调表结构管理模块的listener(类:TableMetaListener),该listener会重新加载d1.t1表的元数据。


在启动时,CN会初始化MetaDbConfigManager用于做轮询的线程和定时任务。


5)MetaDbInstConfigManager


初始化实例级的配置项,这里的配置项类似于MySQL中的System Variables,例如SQL的内存大小限制之类的。该阶段会从MetaDB的inst_config表中加载所有的配置项并保存在内存中。


并且会注册对应的listener,这样当inst_config表发生变化的时候,会回调MetaDbInstConfigManager的listener。  


6)ConnPoolConfigManager


这个是用来存连接池(CN与DN之间的连接池)相关的配置项的(其实它用到的配置MetaDbInstConfigManager中都有),也是从inst_config中读取。


7)StorageHaManager


这个比较重要。


目前每一组DN(这里的组指一个Paxos组)内选举的结果是存储在该组DN自己的系统表中,并不会写到MetaDB中。因此CN需要有机制去从DN的系统表中探测各节点的角色。


StorageHaManager会从storage_info表中读取所有的DN节点的连接信息,并且内部会有轮询的线程去检测角色信息。当DN发生HA的时候,StorageHaManager会探测到这个变化,并感知到最新的Leader等角色。


8)初始化系统库


initSystemDbIfNeed初始化information_schema和polardbx等几个系统库。


4.创建线程池


路径:CobarServer.new()


几个主要的线程池的创建:


 managerExecutor,负责Manager端口的请求。

 killExecutor,专门用来执行kill指令

 serverExecutor,各种SQL都是由这个线程池执行


5.CobarServer.init


路径:TddlLauncher.main()->CobarServer.init()  


1)逻辑库(TDataSource)的初始化


入口在GmsAppLoader.initDbUserPrivsInfo,类里的“App”指的就是一个逻辑库。这里会加载每个逻辑库的用户权限信息以及最重要的TDataSouce。


TDataSource在CN中与逻辑库一一对应。它是每个逻辑库执行SQL的入口。TDatSource的初始化逻辑在MatrixConfigHolder.init中,包含了以下内容:


 初始化拓扑,例如这个库包含了哪些DN,并初始化与这些DN之间的连接池

 (TopologyHandler.initPolarDbXTopology


 获取每个DN的信息,包括版本,特性的支持程度等

StorageInfoManager.init


 初始化分片的路由信息(mode='drds',TddlRuleManager.init

 mode='auto',PartitionInfoManager.init


 初始化表管理器(有哪些表、每个表有哪些列哪些索引等)(GmsTableMetaManager.init


 初始化事务管理器(transactionManager.init()


 创建Plan Cache(PlanCache planCache = new PlanCache(schemaName);


 启动DDL任务引擎(ddlEngineInit()


 统计信息管理器的初始化(StatisticManager.init


 SPM的初始化(PlanManager.init


2)GmsClusterLoader.loadPolarDbXCluster


加载集群信息,例如集群中有哪些CN节点等。初始化CclService,这个是用来做SQL限流的。


3)warmup


CN的SQL函数目前是反射机制进行加载的,这里主要作用是加载下所有的函数。


6.网络层的初始化


该步骤结束后,服务端口便会打开,该CN进程就能开始对外提供服务了。  


CN在这一步会启动Server端口与Manager端口两个端口。  


Server端口对应MySQL的3306端口,是提供给前端应用使用的。


Manager端口用于内部的管理使用,例如,SHOW PROCEESSLIST指令需要收集所有CN的执行情况,就会使用该端口进行收集。


1)NIOProcessor


processors = new NIOProcessor[system.getProcessors()];
for (int i = 0; i < processors.length; i++) {
      processors[i] = new NIOProcessor(i, "Processor" + i,this.serverExecutor);
processors[i].startup();
}


CN网络层处理请求的入口是NIOProcessor,每个NIOProcessor有读写两个线程。这里会来启动NIOProcessor的W与R线程。


2)NIOAcceptor


CN网络层用来处理连接建立请求的是NIOAcceptor,NIOAcceptor的new过程中,会打开端口进行监听:  


public NIOAcceptor(String name, int port, FrontendConnectionFactory factory, boolean online) throws IOException {
        super.setName(name);
        ...
            this.selector = Selector.open();
            this.serverChannel = ServerSocketChannel.open();
            this.serverChannel.socket().bind(new InetSocketAddress(port), 65535);
        ...
    }


同时,NIOAcceptor也是一个线程,会处理连接建立的请求。当连接建立后,NIOAcceptor.accept方法会将连接绑定到一个NIOProcessor上,由NIOProcessor继续处理该连接上后续的读写请求。


可以看出,对于服务端口来说,NIOAcceptor只有一个,NIOProcessor的数目则一般和CPU核数保持一致。


3)MPP Server的启动


CN作为MPP集群中的一个,还需要启动端口来进行CN之间的通信。


startMppServer会进行MPP服务的启动。  


4)CDC服务的启动


CobarServer.tryStartCdcManager会进行CDC的启动。


7:结语


至此,CN就启动完成了。这个过程包含了CN中绝大多数组件,鉴于篇幅原因,没有完整介绍每个组件的作用。如果你对这些组件中的哪一个感兴趣,欢迎留言给我们,我们会在后续的文章中,对一些关键的组件做进一步详细的介绍。  

相关实践学习
快速体验PolarDB开源数据库
本实验环境已内置PostgreSQL数据库以及PolarDB开源数据库:PolarDB PostgreSQL版和PolarDB分布式版,支持一键拉起使用,方便各位开发者学习使用。
相关文章
|
1月前
|
关系型数据库 MySQL 分布式数据库
[PolarDB实操课] 05.通过源码部署PolarDB-X标准版
本课程介绍如何通过源码部署PolarDB-X标准版,涵盖基于Paxos的MySQL三副本工作原理和技术特点。主要内容包括: 1. **Paxos三副本工作原理**:讲解Leader和Follower节点的角色及数据同步机制。 2. **技术特点**:强调高性能、数据不丢失(RPO=0)和自动HA切换。 3. **源码部署步骤**:详细演示从编译生成RPM包到启动DN节点的过程,包括配置my.cnf文件和初始化数据库。 4. **高可用体验**:通过三台机器模拟三副本集群,展示Leader选举和故障转移机制,确保数据一致性和服务可用性。
|
1月前
|
存储 关系型数据库 分布式数据库
[PolarDB实操课] 01.PolarDB分布式版架构介绍
《PolarDB实操课》之“PolarDB分布式版架构介绍”由阿里云架构师王江颖主讲。课程涵盖PolarDB-X的分布式架构、典型业务场景(如实时交易、海量数据存储等)、分布式焦点问题(如业务连续性、一致性保障等)及技术架构详解。PolarDB-X基于Share-Nothing架构,支持HTAP能力,具备高可用性和容错性,适用于多种分布式改造和迁移场景。课程链接:[https://developer.aliyun.com/live/253957](https://developer.aliyun.com/live/253957)。更多内容可访问阿里云培训中心。
[PolarDB实操课] 01.PolarDB分布式版架构介绍
|
1月前
|
关系型数据库 编译器 分布式数据库
PolarDB实操课] 04.通过源码部署PolarDB-X企业版
本次课程由PolarDB开源架构师王江颖分享,详细介绍了通过源码部署PolarDB-X企业版的全过程。主要内容包括: 1. **编译基础** 2. **使用源码编译部署PolarDB-X企业版** 3. **演示实例**:通过阿里云ECS进行实际操作演示,从创建用户、赋予权限到最终启动并连接PolarDB-X数据库,展示了完整的部署过程。 4. **总结**
|
1月前
|
关系型数据库 分布式数据库 PolarDB
[PolarDB实操课] 02.使用云起实验室资源快速体验PolarDB分布式版
本次课程由阿里云PolarDB开源架构师黄心雨分享,重点介绍如何使用云起实验室资源快速体验PolarDB分布式版。主要内容包括: 1. **PolarDB-X的四种安装方法**:Docker、PXD工具、Kubernetes和源码编译。 2. **容器技术简介**:解释容器在云原生环境中的作用,解决代码跨环境迁移问题。 3. **云起实验室实操**:通过云起实验室提供的零门槛平台,快速部署PolarDB-X,体验其主要功能。 4. **课程小结**:总结PolarDB-X的安装方式及实际操作步骤,并展望后续课程内容。
|
2月前
|
Cloud Native 关系型数据库 分布式数据库
PolarDB 分布式版 V2.0,安全可靠的集中分布式一体化数据库管理软件
阿里云PolarDB数据库管理软件(分布式版)V2.0 ,安全可靠的集中分布式一体化数据库管理软件。
|
6月前
|
C# UED 定位技术
WPF控件大全:初学者必读,掌握控件使用技巧,让你的应用程序更上一层楼!
【8月更文挑战第31天】在WPF应用程序开发中,控件是实现用户界面交互的关键元素。WPF提供了丰富的控件库,包括基础控件(如`Button`、`TextBox`)、布局控件(如`StackPanel`、`Grid`)、数据绑定控件(如`ListBox`、`DataGrid`)等。本文将介绍这些控件的基本分类及使用技巧,并通过示例代码展示如何在项目中应用。合理选择控件并利用布局控件和数据绑定功能,可以提升用户体验和程序性能。
191 0
|
4月前
|
NoSQL Java Redis
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
Redis分布式锁在高并发场景下是重要的技术手段,但其实现过程中常遇到五大深坑:**原子性问题**、**连接耗尽问题**、**锁过期问题**、**锁失效问题**以及**锁分段问题**。这些问题不仅影响系统的稳定性和性能,还可能导致数据不一致。尼恩在实际项目中总结了这些坑,并提供了详细的解决方案,包括使用Lua脚本保证原子性、设置合理的锁过期时间和使用看门狗机制、以及通过锁分段提升性能。这些经验和技巧对面试和实际开发都有很大帮助,值得深入学习和实践。
太惨痛: Redis 分布式锁 5个大坑,又大又深, 如何才能 避开 ?
|
8天前
|
缓存 NoSQL 中间件
Redis,分布式缓存演化之路
本文介绍了基于Redis的分布式缓存演化,探讨了分布式锁和缓存一致性问题及其解决方案。首先分析了本地缓存和分布式缓存的区别与优劣,接着深入讲解了分布式远程缓存带来的并发、缓存失效(穿透、雪崩、击穿)等问题及应对策略。文章还详细描述了如何使用Redis实现分布式锁,确保高并发场景下的数据一致性和系统稳定性。最后,通过双写模式和失效模式讨论了缓存一致性问题,并提出了多种解决方案,如引入Canal中间件等。希望这些内容能为读者在设计分布式缓存系统时提供有价值的参考。感谢您的阅读!
Redis,分布式缓存演化之路
|
2月前
|
存储 NoSQL Java
使用lock4j-redis-template-spring-boot-starter实现redis分布式锁
通过使用 `lock4j-redis-template-spring-boot-starter`,我们可以轻松实现 Redis 分布式锁,从而解决分布式系统中多个实例并发访问共享资源的问题。合理配置和使用分布式锁,可以有效提高系统的稳定性和数据的一致性。希望本文对你在实际项目中使用 Redis 分布式锁有所帮助。
210 5
|
3月前
|
NoSQL Java 数据处理
基于Redis海量数据场景分布式ID架构实践
【11月更文挑战第30天】在现代分布式系统中,生成全局唯一的ID是一个常见且重要的需求。在微服务架构中,各个服务可能需要生成唯一标识符,如用户ID、订单ID等。传统的自增ID已经无法满足在集群环境下保持唯一性的要求,而分布式ID解决方案能够确保即使在多个实例间也能生成全局唯一的标识符。本文将深入探讨如何利用Redis实现分布式ID生成,并通过Java语言展示多个示例,同时分析每个实践方案的优缺点。
101 8

相关产品

  • 云原生数据库 PolarDB