TiDB源码学习笔记:启动TiDB

神州数码云基地 · · 589 次点击 · · 开始浏览    
这是一个创建于 的文章,其中的信息可能已经有所发展或是发生改变。

作者:院长,神州数码云基地开发工程师,目前专注于TiDB源码研究。

TiDB源码研究系列第一篇,简述TiDB的核心架构,从tidb-server/mian.go开始,探索启动TiDB的方法。

最近因为一些任务,开始入门学习TiDB源码,作为一名刚刚接触数据库底层的孩子来说,这个过程并不容易,好在TiDB的官方文档提供了源码阅读系列文章,能够让我们比较快速的入门,但是内容比较简单,即使依靠着官方文档去学习源码,对于我这种完全新入门的还是有那么亿点点的难度,所以为了帮助那些和我一样,看源码看的眼花撩换的孩子,就将自己的学习过程记录下来,希望对大家入门TiDB源码有一定的帮助。

在这里我就不去介绍TiDB是什么,有什么作用,有什么特点这种废话了,可以直接上官方文档,有非常详细和专业的介绍:

TiDB 简介​
Blog-cns|PingCAP​

TiDB的核心架构分为三块,分别是TiDB,TiKV 和 PD。

TiKV是负责数据存储的,我们老大写了一篇关于TiKV的源码阅读文章,大家对于TiKV有兴趣可以去看看,TiKV源码是用Rust写的,比较硬核,难度较高。

Jinn Jin:TiKV源码略读-Config

PD则是负责调度和管理的,作为整个集群的管理中心。

TiDB就是我现在要去学习的一部分了,他作为SQL引擎,主要负责连接客户端,获取请求,解析SQL,然后到TiKV那里拿到数据,最后返回给客户端。

下面就开始进入TiDB学习了,TiDB源码是用golang写的,所以需要有一定的Go语言基础,最起码能看懂最基本的语法知识(其实我也是刚刚开始学习Go语言的)。要想学习源码,不能干看代码,尤其是对语言不熟悉的,看代码只会看的一头雾水,我们需要在本地去启动TiDB,然后打断点,逐步跟踪,才会更好了解代码逻辑和结构。

准备好本地环境后,从GitHub上拉取TiDB源码,我现在拉的版本是2020.11.13的版本,版本不同代码可能会存在差异。

pingcap/tidb​!

首先通过官方提供的源码阅读文章,可以找到程序启动的入口,在tidb-server/mian.go中可以找到入口main(),那么如果用的goland编译器的话,点击左边的小三角,就可以很方便的直接调试和启动。

看一下mian方法中,启动的每一个流程都封装成为了一个方法,如果想要对某一块进行学习,直接进入相对应的方法中查看即可,一个高可读的代码确实能让其他人感受到便利,所以大家开发中一定要注意代码规范。

直接进入main方法中看一下

func main() {
    // 读取命令行中的一些参数信息
   flag.Parse()
    // 如果用户只是想查看版本信息,输出并结束
   if *version {
      fmt.Println(printer.GetTiDBInfo())
      os.Exit(0)
   }
    // 注册存储
   registerStores()
    // 注册监控
   registerMetrics()
    // 初始化全局设置
   config.InitializeConfig(*configPath, *configCheck, *configStrict, reloadConfig, overrideConfig)

    // 判断一下是否有 SQL 语句的内存使用超出 mem-quota-query 
    // 如果有限制的话启用临时磁盘,更新TempStoragePath
   if config.GetGlobalConfig().OOMUseTmpStorage {
      config.GetGlobalConfig().UpdateTempStoragePath()
      err := disk.InitializeTempDir()
      terror.MustNil(err)
      checkTempStorageQuota()
   }
    // 设置全局变量
   setGlobalVars()
    // CPU亲和性设置
   setCPUAffinity()
    // 设置日志信息
   setupLog()
    // 设置堆文件跟踪程序
   setHeapProfileTracker()
    // 设置分布式跟踪系统 Jaeger
   setupTracing() // Should before createServer and after setup config.
    // 输出当前TiDB信息
   printInfo()
    // 设置binlog客户端
   setupBinlogClient()
    // 设置监控 Prometheus
   setupMetrics()
    // 创建存储和终端,后台的一些服务
   createStoreAndDomain()
    // 创建服务
   createServer()
    // 对当前服务信号进行设置和处理
   signal.SetupSignalHandler(serverShutdown)
    // 启动服务,开始监听
   runServer()
    // 清除连接并关闭服务
   cleanup()
    // 同步日志
   syncLog()
}

随着项目运行,每个模块会一一启动,这个地方也不进到每个方法内看具体实现了,内容比较多,研究那一块,我们就着重学习那一块。

这个地方可以关注一下这个方法 registerStores() ,可以发现,当我们没有设置TiKV或者仅仅单独启动TiDB时,并不会启动失败,而是初始化一个MockTiKV服务来进行本地的存储,同样的,当没有设置PD服务时,TiDB也会启动一个MockPD来进行调度和管理,所以我们完全可以单独启动TiDB在本地做为测试。当然,每次重新启动也不会导致数据的丢失,他会帮我们存在本地文件中。

func registerStores() {
    err := kvstore.Register("tikv", tikv.Driver{})
    terror.MustNil(err)
    tikv.NewGCHandlerFunc = gcworker.NewGCWorker
    err = kvstore.Register("mocktikv", mockstore.MockTiKVDriver{})
    terror.MustNil(err)
    err = kvstore.Register("unistore", mockstore.EmbedUnistoreDriver{})
    terror.MustNil(err)
}

初始化环境变量和配置后,通过 runServer() 来启动整个服务,这个方法很简单,里面就运行了一个 svr.Run() 的方法,我们跟进去看一下。

// Run runs the server.
func (s *Server) Run() error {
   ......
   for {
      conn, err := s.listener.Accept()

      .......

      // 创建一个新的连接
      clientConn := s.newConn(conn)

      if s.dom != nil && s.dom.IsLostConnectionToPD() {
         logutil.BgLogger().Warn("reject connection due to lost connection to PD")
         terror.Log(clientConn.Close())
         continue
      }

      go s.onConn(clientConn)
   }
}

代码稍微简化了一下,它会进行一个For循环来不断的监听是否有连接的请求,当监听到有客户端发起连接请求后,会通过 ewConn() 创建一个新的连接。(每一个连接对应一个Session,Session在TiDB中是非常重要的模块,后面我们会经常看到)创建了新的连接,就会进入到 onConn() 方法中,抓取数据进行数据处理,这里有一个go function() 的语法,对于go语言不熟悉,可能就不理解,他是用于并发,会开启一个新的 goroutine 来运行这个方法,这个是go语言中很重要的模块,后面可以进行深入的了解。现在我们先进入这个 onConn() 中,了解一下。

func (s *Server) onConn(conn *clientConn) {
   ctx := logutil.WithConnID(context.Background(), conn.connectionID)
   if err := conn.handshake(ctx); err != nil {

   ......

   }

   ......  

   sessionVars := conn.ctx.GetSessionVars()
   if plugin.IsEnable(plugin.Audit) {
      sessionVars.ConnectionInfo = conn.connectInfo()
   }

   ......

   connectedTime := time.Now()
   conn.Run(ctx)

   ......
}

监听到客户端的连接请求,建立连接后,会创建一个上下文并绑定连接ID,接着进行握手验证,这个地方的握手类似TCP的握手流程,但是层次会更高,高在哪里,就需要单独研究了。握手成功,会检查连接是否开启审计,来记录一次连接信息,最后通过 conn.Run(ctx) 来读取客户端发的请求内容,继续跟进。

// Run reads client query and writes query result to client in for loop, if there is a panic during query handling,
// it will be recovered and log the panic error.
// This function returns and the connection is closed if there is an IO error or there is a panic.
func (cc *clientConn) Run(ctx context.Context) {
   if !atomic.CompareAndSwapInt32(&cc.status, connStatusDispatching, connStatusReading) {
     return
   }

   // Usually, client connection status changes between [dispatching] <=> [reading].
   // When some event happens, server may notify this client connection by setting
   // the status to special values, for example: kill or graceful shutdown.
   // The client connection would detect the events when it fails to change status
   // by CAS operation, it would then take some actions accordingly.
   for {
      ......

      // close connection when idle time is more than wait_timeout
      waitTimeout := cc.getSessionVarsWaitTimeout(ctx)
      cc.pkt.setReadTimeout(time.Duration(waitTimeout) * time.Second)
      start := time.Now()
      data, err := cc.readPacket()

      ......

      if !atomic.CompareAndSwapInt32(&cc.status, connStatusReading, connStatusDispatching) {
         return
      }

      startTime := time.Now()
      if err = cc.dispatch(ctx, data);err != nil {
      ......
      }

      ......
   }
}

还是老套路,开一个For循环,来读取客户端发过来的数据包 data,err := cc.readPacket(),前面有个验证,看请求空闲时间是否超时,超时了,就会关闭连接。cc.dispatch(crx, data)就是将抓取的数据进行处理,也就是我们发送的各种SQL语句都会进入这个方法,做详细的处理,并返回结果给客户端,毫无疑问,这个方法是TiDB中的关键方法,后面我们在进行跟踪TiDB处理SQL语句时,就要到这个方法中一探究竟。

整个TiDB的启动流程,我们大致有了一定的了解,还包括如何监听客户端请求,建立连接并抓取数据等,知道了这些,我们才算正真开始了TiDB的源码学习路程,下一步我们要进行的就是发送一句SQL请求,并进行断点跟踪,来看看这句SQL的一生都经历了什么。

最后附上这次学习的流程图:

出处:院长:TiDB源码学习笔记:启动TiDB


有疑问加站长微信联系(非本文作者)

本文来自:简书

感谢作者:神州数码云基地

查看原文:TiDB源码学习笔记:启动TiDB

入群交流(和以上内容无关):加入Go大咖交流群,或添加微信:liuxiaoyan-s 备注:入群;或加QQ群:692541889

589 次点击  
加入收藏 微博
暂无回复
添加一条新回复 (您需要 登录 后才能回复 没有账号 ?)
  • 请尽量让自己的回复能够对别人有帮助
  • 支持 Markdown 格式, **粗体**、~~删除线~~、`单行代码`
  • 支持 @ 本站用户;支持表情(输入 : 提示),见 Emoji cheat sheet
  • 图片支持拖拽、截图粘贴等方式上传