1. 进入main函数前
进入main函数前,会先执行config包的init函数。init函数中会调用initByLDFlags函数,initByLDFlags函数中有两个主要处理,如下面的代码所示。globalConf是一个原子值类型的变量,位于config包中。
func initByLDFlags(edition, checkBeforeDropLDFlag string) {
if edition != versioninfo.CommunityEdition {
defaultConf.EnableTelemetry = false
}
// 创建默认配置信息
conf := defaultConf
// 将配置信息保存到globalConf变量中
StoreGlobalConfig(&conf)
if checkBeforeDropLDFlag == "1" {
CheckTableBeforeDrop = true
}
}
2. 进入main函数
main.go文件位于tidb-server文件夹下。main函数代码如下;
func main() {
flag.Parse()
if *version {
fmt.Println(printer.GetTiDBInfo())
os.Exit(0)
}
registerStores()
registerMetrics()
config.InitializeConfig(*configPath, *configCheck, *configStrict, reloadConfig, overrideConfig)
if config.GetGlobalConfig().OOMUseTmpStorage {
config.GetGlobalConfig().UpdateTempStoragePath()
err := disk.InitializeTempDir()
terror.MustNil(err)
checkTempStorageQuota()
}
setGlobalVars()
setCPUAffinity()
setupLog()
setHeapProfileTracker()
setupTracing() // Should before createServer and after setup config.
printInfo()
setupBinlogClient()
setupMetrics()
createStoreAndDomain()
createServer()
signal.SetupSignalHandler(serverShutdown)
runServer()
cleanup()
syncLog()
}
flag.Parse()用于解析程序启动时传入的参数。
registerStores函数用于向store包的stores变量写入3个空结构体的对象,主要是为了后面可以执行这些结构体的相关方法。
registerMetrics函数用于初始化prometheus的collector,TiDB Server在启动时初始化了一堆collector,主要是用于对TiDB进行监控。
调用了config包的InitializeConfig函数,传入的参数重点关注configPath和overrideConfig。configPath是通过下面一段代码获取的。跟踪这段代码,可以看到,实际上程序启动时需要带上config参数和配置文件路径,否则程序将直接退出。
configPath = flag.String(nmConfig, "", "config file path")
overrideConfig函数在main.go文件内,主要是解析程序启动时传入的参数,并用这些参数强行覆盖配置文件中的同名属性的值。传入的reloadConfig函数值,在InitializeConfig函数中实际上没有被调用。
InitializeConfig函数中,首先通过下面一段代码获取默认配置信息。
cfg := GetGlobalConfig()
然后调用Config结构体的Load方法,并传入配置文件路径。Load方法中通过DecodeFile函数用配置文件中信息覆盖默认配置信息中的同名属性的值。DecodeFile是一个第三方库(github.com/BurntSushi/toml)的函数。DecodeFile函数会返回一个MetaData对象,通过这个对象可以判断配置文件中是否存在非法的配置属性。如果存在,Load方法将返回错误信息。
加载完配置文件后将调用前面提到的overrideConfig函数,在Load方法中这个函数的名字稍微有点变化,叫做enforceCmdArgs。
随后,调用Config结构体的Valid方法,用于检验配置文件中的相关配置是否有效,具体检查项目可以自行查看代码,这里不做赘述。校验失败的情况,输出错误信息并结束程序。
配置信息校验成功的情况,再次调用config包的StoreGlobalConfig函数,将配置文件存入config包的globalConf变量中。下面这段代码主要是控制当单个SQL语句超出mem-quota-query指定的内存配额时,是否为某些运算符启用临时存储。Config结构体方法UpdateTempStoragePath用于指定临时存储区域的路径。
if config.GetGlobalConfig().OOMUseTmpStorage {
config.GetGlobalConfig().UpdateTempStoragePath()
err := disk.InitializeTempDir()
terror.MustNil(err)
checkTempStorageQuota()
}
InitializeTempDir函数用来初始化临时文件。该函数首先从全局配置信息中读取临时存储的路径,如果文件系统中不存在该路径则创建路径。接下来获取临时存储路径下_dir.lock文件的锁。读取临时存储路径下的所有文件,如果文件数量大于2,则启动一个goroutine用来删除_dir.lock和record文件以外的其他文件。
checkTempStorageQuota函数用于检查临时存储路径的磁盘空间是否满足配置文件中指定的大小。全局配置信息中的TempStorageQuota属性指定了临时存储的空间大小,如果是复数,不做任何处理。如果是非负数,则需要先获取临时存储路径下的剩余磁盘空间大小,如果剩余磁盘空间大小小于TempStorageQuota属性指定的值,向日志中写入异常信息。
setGlobalVars函数是用全局配置信息中的数据修改全局变量的默认设置。包括运行时可用CPU数,DDL租约时间,统计信息加载的租约时间,索引同步租约时间,bindinfo租约时间,TiDB是否启动自动分析worker等。这些系统变量的初始化处理位于variable包的init函数中。
setCPUAffinity函数,主要是通过读取程序启动时的命令行,判断是否有affinity-cpus参数。affinity-cpus参数的值主要是指定CPU的编号,用半角逗号隔开。如果没有该参数,则直接返回;如果有该参数,则解析参数的值,获取一共有几个CPU编号,用CPU编号数量设置运行时的空用CPU数量。
setupLog函数,主要通过全局配置信息中日志相关配置初始化日志,包括zaplog和golang自带的标准日志。日志对象保存在logutil包的SlowQueryLogger和SlowQueryZapLogger两个变量中。另外该函数还调用了util包的InternalHTTPClient函数,用于创建一个内部的标准httpclient。在创建httpclient时,会读取全局配置信息中TLS相关配置,如果没有相关配置信息则返回http.DefaultClient实例,如果有相关配置信息,则用配置信息初始化httpclient的Transport属性并返回httpclient实例。
setHeapProfileTracker函数,首先读取全局配置信息中的Performance.MemProfileInterval信息。该信息是一个时间间隔,单位为毫秒。然后以goroutine方式运行profile包的HeapProfileForGlobalMemTracker函数。HeapProfileForGlobalMemTracker函数中会创建一个计时器,定时获取TiDB中相关函数的内存使用情况,更新kvcache包的GlobalLRUMemUsageTracker变量。kvcache包的GlobalLRUMemUsageTracker变量是在kvcache包的init函数中被初始化的。
setupTracing函数,通过全局配置信息创建opentracing实例,并保存到golang自带的opentracing包的globalTracer变量中。关于opentracing的内容,可以参看https://segmentfault.com/a/1190000014546372这边文件,比较浅显易懂。
createStoreAndDomain函数
createStoreAndDomain函数,从全局配置信息中读取Store和Path配置信息,然后拼装成"store://path"格式的字符串,再将该字符串传递给store包的New函数。store包的New函数调用了newStoreWithRetry函数,具体代码如下。
func newStoreWithRetry(path string, maxRetries int) (kv.Storage, error) {
storeURL, err := url.Parse(path)
if err != nil {
return nil, err
}
name := strings.ToLower(storeURL.Scheme)
d, ok := stores[name]
if !ok {
return nil, errors.Errorf("invalid uri format, storage %s is not registered", name)
}
var s kv.Storage
err = util.RunWithRetry(maxRetries, util.RetryInterval, func() (bool, error) {
logutil.BgLogger().Info("new store", zap.String("path", path))
s, err = d.Open(path)
return kv.IsTxnRetryableError(err), err
})
if err == nil {
logutil.BgLogger().Info("new store with retry success")
} else {
logutil.BgLogger().Warn("new store with retry failed", zap.Error(err))
}
return s, errors.Trace(err)
}
newStoreWithRetry函数是一个带有retry功能的函数,默认retry次数为30次。该函数通过从传入的字符串获取scheme,并作为key从store包的stores中获取driver对象。driver对象是在registerStores函数中存入stores变量的。所有的driver对象都实现了Open方法。通过调用Open方法,获取Storage对象并返回。
Driver结构体的Open方法
Driver结构体位于tikv包中,是一个空结构体。
Open方法解析传入的字符串,获取TiKV地址的切片和disableGC。然后调用pd包的NewClient函数,创建TiKV的客户端。Open方法向NewClient函数传递了三个参数。
1,etcdAddrsTiKV地址的切片
2,SecurityOption结构体实例,从全局配置信息中获取security相关数据并设置到实例中成员变量中。
3,ClientOption函数。通过调用pd包的WithGRPCDialOptions函数返回ClientOption函数。从下面的代码可以看到,这个函数实际上是将传入的opts设置到baseClient结构体的gRPCDialOptions成员变量中(切片类型)。这里用到了grpc框架。opts传入了两个值,都是通过全局配置信息获取。分别是tikvConfig.GrpcKeepAliveTime和tikvConfig.GrpcKeepAliveTimeout。
tikvConfig.GrpcKeepAliveTime:After a duration of this time in seconds if the client doesn't see any activity it pings the server to see if the transport is still alive.
tikvConfig.GrpcKeepAliveTimeout:After having pinged for keepalive check, the client waits for a duration of Timeout in seconds and if no activity is seen even after that the connection is closed.
func WithGRPCDialOptions(opts ...grpc.DialOption) ClientOption {
return func(c *baseClient) {
c.gRPCDialOptions = append(c.gRPCDialOptions, opts...)
}
}
NewClient函数调用了NewClientWithContext函数,除了原封不动的传递了NewClient函数的参数外,还创建了一个新的context实例并传入。
------------------------------------------------------------------------------------分割线----------------------------------------------------------------------------------
上面主要是一些配置方面的处理,后面的几个函数才是真正重要的处理,包括创建存储和Domain,创建服务,启动服务。后面有时间再慢慢梳理。