TiDB源码阅读(一) TiDB启动流程

写这个东西也是因为想简单掌握下TiDB的源码,在同事的指导下有了一些阅读思路,所以积累在这里。有些地方理解的有问题还请指出,如果不小心误导了读者,还请见谅

TiDB源码 

TiDB模块是使用Go语言开发的,使用GoLand编译器就可以了。

TiDB源码阅读(一) TiDB启动流程_第1张图片

闲话少说,阅读源码,要寻找好的切入点,我们选择

tidb-server/main.go

作为阅读源码的入口。

理由是,这里的main函数可以debug,也是TiDB启动的源头。稍微简化一下

func main() {
   registerStores()
   registerMetrics()
   config.InitializeConfig(*configPath, *configCheck, *configStrict, reloadConfig, overrideConfig)
   if config.GetGlobalConfig().OOMUseTmpStorage {
      config.GetGlobalConfig().UpdateTempStoragePath()
      initializeTempDir()
   }
   setCPUAffinity()
   setupLog()
   setupTracing() 
   setupBinlogClient()
   setupMetrics()
   createStoreAndDomain()
   createServer()
   runServer()
}

从代码可以看出来,TiDB的启动流程做了很多操作,每个步骤都按照函数封装好了,大致了解下都做什么。

// 注册store
func registerStores() {
	err := kvstore.Register("tikv", tikv.Driver{}) //注册TiKV
	tikv.NewGCHandlerFunc = gcworker.NewGCWorker //为TiKV生成GCworker
	err = kvstore.Register("mocktikv", mockstore.MockDriver{}) //注册MockTiKV
}
//共注册100+ prometheus监控项,这里只表一项
func RegisterMetrics() { 
	prometheus.MustRegister(AutoAnalyzeCounter)
}
// get全局config
func InitializeConfig(confPath string, configCheck, configStrict bool, reloadFunc ConfReloadFunc, enforceCmdArgs func(*Config)) {
	cfg := GetGlobalConfig() 
	StoreGlobalConfig(cfg)
}
if config.GetGlobalConfig().OOMUseTmpStorage {
		config.GetGlobalConfig().UpdateTempStoragePath()
		initializeTempDir()
}

这是判断什么呢?

设置是否在单条 SQL 语句的内存使用超出mem-quota-query 限制时为某些算子启用临时磁盘。故若为true,则初始化TempStoragePath。 

// 设置CPU亲和性
func setCPUAffinity() { /
	err := linux.SetAffinity(cpu)
	runtime.GOMAXPROCS(len(cpu))
}
//配置系统log
func setupLog() { 
	err = logutil.InitLogger(cfg.Log.ToLogConfig()) // 这里配置格式、文件名、slowlog等
}
//注册分布式系统追踪链 jaeger
func setupTracing() {
	tracingCfg := cfg.OpenTracing.ToTracingConfig()
	tracingCfg.ServiceName = "TiDB"
	tracer, _, err := tracingCfg.NewTracer()
	opentracing.SetGlobalTracer(tracer)
}
// 设置binlog信息
func setupBinlogClient() {
	if !cfg.Binlog.Enable { //若binlog.enable=false,则不开启binlog
		return
	}
	if cfg.Binlog.IgnoreError { //若为true,则忽略binlog报错
		binloginfo.SetIgnoreError(true)
	}
	if len(cfg.Binlog.BinlogSocket) == 0 { //配置binlog输出网络地址
		...
	}
	binloginfo.SetPumpsClient(client) //配置binlog信息到pump客户端
}
// 配置监控
func setupMetrics() {
	runtime.SetMutexProfileFraction(10)// 对锁调用的跟踪
	systimeErrHandler := func() {
		metrics.TimeJumpBackCounter.Inc() // 表示TiDB的进程是否仍然存在。
                                          // 若10分钟内tidb_monitor_keep_alive_total               
                                          // 次数<100,TiDB可能退出,此时会报警
	}
	callBackCount := 0
	sucessCallBack := func() {
		callBackCount++
		if callBackCount >= 5 {
			callBackCount = 0
			metrics.KeepAliveCounter.Inc() // KeepAlive监控 
		}
	}
}
// 启动了一些重要的后台进程
func createStoreAndDomain() {
	fullPath := fmt.Sprintf("%s://%s", cfg.Store, cfg.Path)
	storage, err = kvstore.New(fullPath)
	dom, err = session.BootstrapSession(storage)}
}
// 创建TiDB server
func createServer() {
	driver := server.NewTiDBDriver(storage)
	svr, err = server.NewServer(cfg, driver)
	svr.SetDomain(dom)
}
runServer() //启动服务

可以看到,Run()是启动TiDB流程中的的最后一步。成功后,TiDB就在这里“接客”。所以我们跳转到Run()中

当TiDB的配置、参数、监控、日志、文件初始化等等工作完成之后,Run()方法负责接收来自客户端的请求。这里有很多接受请求时的异常处理逻辑,在此不表。简化一下大概有四步,如下:

if s.cfg.Status.ReportStatus {
	s.startStatusHTTP() //配置路由信息
}
for{
    conn, err := s.listener.Accept()// 监听客户端请求
    clientConn := s.newConn(conn)// 创建connection
    go s.onConn(clientConn)// 使用connection处理请求
}

首先配置了关于TiDB组件的很多路由信息,有兴趣的可以深入查看。

之后在for(死循环 中,不断监听客户端的请求,接收到请求后,create connection,并用这个connection处理客户端请求。

所以我们需要来到这个地方一探究竟接下来的流程,简化下代码,大致如下

func (s *Server) onConn(conn *clientConn) {
	ctx := logutil.WithConnID(context.Background(), conn.connectionID)
	if err := conn.handshake(ctx); err != nil {
		if plugin.IsEnable(plugin.Audit) && conn.ctx != nil {
			conn.ctx.GetSessionVars().ConnectionInfo = conn.connectInfo()
			})
		}
	}
	connectedTime := time.Now()
	conn.Run(ctx)
}

建链成功后,通过conn.Run(ctx)处理客户端请求

func (cc *clientConn) Run(ctx context.Context) {
	for {
		waitTimeout := cc.getSessionVarsWaitTimeout(ctx)
		cc.pkt.setReadTimeout(time.Duration(waitTimeout) * time.Second)
		data, err := cc.readPacket()
		if err = cc.dispatch(ctx, data); err != nil {
            ...
		}
	}
}

简化后,处理逻辑大致是超时则断链,cc.readPacket()读取客户端发来的网络包。

cc.dispatch(ctx,data)是处理SQL请求的入口。在这里,通过解析网络包,走到不同query的cmd,来进行SQL的处理。

下一章会介绍TiDB支持的SQL类型,SQL语句是如何处理的。

 

 

你可能感兴趣的:(数据库,mysql,golang,linux)