浅谈 TiDB 初始化系统库过程
作者: alexshen 原文来源:https://tidb.net/blog/9c7f795b
【是否原创】是
【首发渠道】TiDB 社区
【正文】
1. init() 函数
在 golang 中,有两个保留函数,一个是 main 函数,另一个就是 init 函数。我们都知道,golang 里的 main 函数是程序的入口函数,main 函数执行完成之后,整个程序也就执行完成了。但是,golang 在执行 main 之前,会干三件事,第一件事,也是最先做的,就是导入程序中用到的各种依赖包,第二件事,就是做完第一件事情之后,对程序中的变量和常量进行初始化,那么,第三件事,就是在做完前面两件事之后,才会开始的,执行所有包中的 init 函数。在所有 init 函数执行完毕之后,才会去执行 main 函数。
init 主要用途就是 初始化,不能使用初始化表达式初始化的,变量。在 TiDB 源码中,初始化系统库 information_schema 时,就利用到了 init 函数。
2. TiDB 中表的分类
在 TiDB 中,将表分为三大类,分别是 normal table、virtual table、cluster table。
normal table,也就是物理表,它是持久化存储到 TiKV 当中的表,主要用于存储用户数据。全数据库共享唯一一份数据,不同的 TiDB 读到的数据完全一致。
virtual table,即虚拟表,它只有表结构信息,而不存储数据,当到使用的时候,比如查询这个表的数据的时候,它会从内存中提取相应数据返回,多见于 performance_schema,现在仅仅上只有兼容性上的意义。
cluster table,也可以称之为内存表,它是 TiDB 启动之后,根据表的实际定义,生成的仅存在于 TiDB 内存中的表,具体可以参考 information_schema 中的表,像 SLOW_QUERY、PROCESSLIST 表等。
3. 本地启动调试 TiDB
TiDB 启动时会默认创建五个 SCHEMA,分别是 INFORMATION_SCHEMA、METRICS_SCHEMA、PERFORMANCE_SCHEMA、mysql 以及 test。
其中,TiDB 源码每次启动时,都会创建 INFORMATION_SCHEMA、METRICS_SCHEMA、PERFORMANCE_SCHEMA,但是却不会每次都去创建 mysql 以及 test,这就与 TiDB 表的分类有关了。mysql 以及 test 中的表都属于 NormalTable,所以这两个 schema 中的 table 数据最终是会存储落实在 TiKV 或者是 MockTiKV 中,而在本地,第一次编译 TiDB 源码启动时,TiDB 会在本地创建一个 MockTiKV 存储的文件,一般是存在 ‘///tmp/tikv ’中,那么这些 NormalTable 就最终就会存储在这个 MockTiKV 中,那么第二次编译启动 TiKV 时,则不会再去创建一个新的 mysql 以及 test 的 SCHEMA,而是直接使用缓存中的 SCHEMA。那么剩下的三个 SCHEMA,里面的大部分表都属于 VirtualTable,只有在 INFORMATION_SCHEMA 中,以 *CLUSER_* 开头的表是属于 ClusterTable,其余的则都是 VirtualTable。这些就与 TiDB 系统数据库初始化过程息息相关了。
接下来,我们就来看一下,整个 TiDB 项目源码启动时,都做了什么事情,这里我们要跳出 tidb-server/main.go 这个文件,去见识更为广阔的天地。这里我们就仅仅拿创建系统 SCHEMA 的过程来讲一下,至于其余的部分,我们先不做处理。
step1 init 过程,init 过程中会去创建 INFORMATION_SCHEMA,以及 METRICS_SCHEMA 的系统库。
step2 执行 main 函数,初始化 tidb-sever。main 函数中,createStoreAndDomain() 会通过 session.BootstrapSession() 去初始化一个 session,而在这个过程中,有一个函数,getStoreBootStrapVersion(),这个函数是用来获取本地 MockTiKV 的版本信息,默认是 0。在第一次在本地初始化启动 TiDB 的时候,会在工程目录的根目录下创建一个 tmp 文件夹,用来存储 MockTiKV 的数据。当这个文件存在时,那么 TiDB 启动就会获取到这个文件中 TiKV 的版本信息,目前手上的源码,TiKV 版本号是 62,而最新的 TiDB 源码仓库,这个版本号是等于 65;如果获取到的版本信息小于 62,则会认为,本地 TiKV 版本老旧,就会去执行 upgrade 函数,去升级 TiKV;但是如果本地没有这样的一个 TiKV,那么这个版本就是 0,就会去执行 bootstrap()。
step3 在我们来先默认本地没有 TiKV 的缓存副本,就当作第一次启动 TiDB。那么,在初始化 session 的过程中,TiDB 就会在 createSession() 中初始化 PERFORMANCE_SCHEMA 系统库,然后通过 bootstrap() 执行 DDL 以及 DML 语句,将 mysql、test 这两个剩余的数据库创建,并且存储进本地的 MockTiKV 中。如果存在 TiKV 缓存的话,TiDB 就只会去在初始化 session 的过程中初始化 PERFORMANCE_SCHEMA 数据库,而不会去执行创建 mysql 以及 test 数据库的 DDL 以及 DML 语句。
至于 TiDB 为什么要在这一步进行 PERFORMANCE_SCHEMA 的初始化,而不是选择直接在 init 阶段就将初始化的事情做了。猜测:根据源代码的命名方式上来看,一开始是打算这么做的,但是,问题就出在 TiDB 建立 PERFORMANCE_SCHEMA 的代码上。TiDB 中去创建这个系统数据库里面的表是通过执行 SQL 语句的方式去做的,那么就会带来一个问题,由于 init 函数的特性,其执行顺序的不确定,解析 SQL 语句的模块就不一定就会先运行起来,就不一定能够去正确解析 SQL 语句。这个时候,就为了保证程序能够顺利启动,就必须就得先初始化 plan/core 的 init 函数,也就是必须得保证 SQL 解析模块是正确运行起来的,这个时候,再通过 go 中的 synv.Once 方法,将 PERFORMANCE_SCHEMA 通过 SQL 语句的方式建立起来,并且写入到内存中保存,记住,这里是内存,不是负责存储的 TiKV,也就是说,PERFORMANCE_SCHEMA 中的表同样的全部都属于虚拟表,VirtualTable,不会存储在 TiKV 中。其实我觉着这里很怪异,在每次启动代码的时候,就都要先去解析一堆 SQL 语句,解析出来就会得到数据库中的表结构,有了表结构,后续的创建过程就与创建 INFORMATION_SCHEMA 的方式无异,将表的信息封装进 PERFORMANCE_SCHEMA 的数据库对象中,再通过 registerVirtualTable 的方式去创建 PERFORMANCE_SCHEMA 的表,然后存进内存,而不是通过执行 SQL 语句的方式,将表存到 TiKV 中。TiDB 执行 SQL 语句的流程,最终是将语句下发到底层 TiKV 上去执行,但是对于 PERFORMANCE_SCHEMA 而言,那一堆建表的 SQL 语句的作用,就是给解析模块儿来进行解析,得到表结构,然后拿到表结构去将这些表注册成虚拟表,存进内存。这一点,我无法理解为什么要这么干,看源码关于这一块的时候,我看到一堆 ‘CREATE TABLE if not exists performance_schema.table_name’ 静态常量语句的时候,才明白过来。
版权声明: 本文为 InfoQ 作者【TiDB 社区干货传送门】的原创文章。
原文链接:【http://xie.infoq.cn/article/0f4f4e8b21fe00b8e20f96f82】。文章转载请联系作者。
评论