TiDB启动(一)

TiDB的启动过程比较复杂,这里主要记录从runServer函数的启动过程,包括服务启动和初始化客户端连接。

main包的createServer函数调用server包的NewServer函数创建server结构体的实例,NewServer函数中创建了网络监听(net.Listen)。

main包的runServer函数用于整个TiDB的启动。该函数调用了server包的Run函数。具体如下:

func runServer() {

    err := svr.Run()

    terror.MustNil(err)

}

在调用server包的Run函数中用一个无限for循环从监听中接收客户端发送的连接请求,并创建网络连接。当没有新的连接请求时,程序会在Accept方法上阻塞。

conn, err := s.listener.Accept()

在网络连接创建成功后,调用server实例的newConn方法,用于创建clientConn结构体实例。创建成功后,将实例作为参数传递给server实例的onConn方法。onConn方法以goroutine的方式运行。下面详细说一下clientConn实例创建和onConn方法中处理。

newConn方法的具体代码如下:

func (s *Server) newConn(conn net.Conn) *clientConn {

    cc := newClientConn(s)

    if s.cfg.Performance.TCPKeepAlive {

        if tcpConn, ok := conn.(*net.TCPConn); ok {

            if err := tcpConn.SetKeepAlive(true); err != nil {

                logutil.BgLogger().Error("failed to set tcp keep alive option", zap.Error(err))

            }

        }

    }

    cc.setConn(conn)

    cc.salt = fastrand.Buf(20)

    return cc

}

newClientConn函数用于创建clientConn结构体的实例,并返回实例的指针。

setConn方法用于将网络连接conn设置到clientConn实例中。这个地方需要注意的是,clientConn结构体中的field。

clientConn结构体中有下面两个field需要注意

    pkt          *packetIO         // a helper to read and write data in packet format.

    bufReadConn  *bufferedReadConn // a buffered-read net.Conn or buffered-read tls.Conn.

bufferedReadConn是TiDB自己封装的结构体,具体如下:

// bufferedReadConn is a net.Conn compatible structure that reads from bufio.Reader.

type bufferedReadConn struct {

    net.Conn

    rb *bufio.Reader

}

packetIO也是TiDB自己封装的结构体,具体如下:

// packetIO is a helper to read and write data in packet format.

type packetIO struct {

    bufReadConn *bufferedReadConn

    bufWriter   *bufio.Writer

    sequence    uint8

    readTimeout time.Duration

}

setConn方法的具体内容如下:

func (cc *clientConn) setConn(conn net.Conn) {

    cc.bufReadConn = newBufferedReadConn(conn)

    if cc.pkt == nil {

        cc.pkt = newPacketIO(cc.bufReadConn)

    } else {

        // Preserve current sequence number.

        cc.pkt.setBufferedReadConn(cc.bufReadConn)

    }

}

首先通过newBufferedReaderConn函数创建了bufferedReadConn的实例,并把返回的实例的指针传递给clientConn实例的bufReadConn字段。然后判断clientConn实例的pkt字段是否为nil,如果是nil,则调用newPacketIO函数,创建packetIO实例。

newPacketIO函数的具体内容如下:

func newPacketIO(bufReadConn *bufferedReadConn) *packetIO {

    p := &packetIO{sequence: 0}

    p.setBufferedReadConn(bufReadConn)

    return p

}

首先是创建packetIO结构体的实例,然后掉也能setBufferedReadConn方法,将bufferedReadConn结构体实例设置到bufReadConn和bufWriter字段中。setBufferedReadConn具体代码如下:

func (p *packetIO) setBufferedReadConn(bufReadConn *bufferedReadConn) {

    p.bufReadConn = bufReadConn

    p.bufWriter = bufio.NewWriterSize(bufReadConn, defaultWriterSize)

}

因为bufferedReadConn结构体包含了net.Conn接口字段,而且net.Conn接口的Write方法与io.Writer接口的Write方法相同,所以可以将bufferedReadConn的实例作为参数传递给bufio包的NewWriterSize函数。再来看看NewWriterSize函数的具体实现:

func NewWriterSize(w io.Writer, size int) *Writer {

    // Is it already a Writer?

    b, ok := w.(*Writer)

    if ok && len(b.buf) >= size {

        return b

    }

    if size <= 0 {

        size = defaultBufSize

    }

    return &Writer{

        buf: make([]byte, size),

        wr:  w,

    }

}

传入bufferedReadConn实例后,实际上最后返回了一个新建的Writer结构体的实例的指针。bufferedReadConn实例被传递给了Writer结构体wr字段(io.Writer接口类型)。

最后的结构是下面的样子

这样packetIO的实例实际上具备了向网络连接读写的功能。不过为什么要搞一个这么复杂的结构,确实还有点儿想不太明白。后续在读代码的过程中再慢慢体会吧。

 

下面再来看看onConn方法。onConn方法首先调用了clientConn的handshake方法,在handshake方法中首先通过writeInitialHankshake方法向客户端发送握手相关信息,包括服务版本,连接ID和服务状态等信息。具体发送的内容涉及MySQL协议,这里不做展开。握手信息发送完成后,通过clientConn的readOptionalSSLRequestAndHandshakeResponse方法,获取客户端发送的握手相应或者是SSLRequest,这里具体的处理涉及MySQL的协议内容也不做展开,但是这个方法中有个很重要的处理就是调用clientConn实例的openSessionAndDoAuth方法为客户端连接创建session并做用户身份验证。完成session创建和用户验证后,会再向客户端发送响应信息。经过上面的步骤就完成了客户端与服务器端的握手。

完成握手处理后,将clientConn实例以实例的connectionID为key,加入到server实例的clients字段中(map),然后再调用clientConn的Run方法,该方法中有一个无限for循环用于通过网络连接获取客户端的sql。在获取到sql后,将sql作为参数传递给clientConn的dispath方法进行后续的处理。

关于连接超时的设置

关于连接超时的设置,是在调用clientConn的readPacket方法时进行设置的。第一次调用readPacket方法是在readOptionalSSLRequestAndHandshakeResponse方法中。readPacket方法实际是调用的packetIO的readOnePacket方法,每次调用readOnePacket方法,都会用当前系统时间加上超时时间形成一个新的readdeadline时间。具体是调用net.Conn的SetReadDeadline方法。

关于启动时context的设置

关于启动时context的设置,是在onConn方法的第一行,创建了一个新的context,并向context里面设置了zap.logger日志对象。到dispatch方法之前,创建的这个context都是用于日志输出。都是用的拷贝传值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值
>