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都是用于日志输出。都是用的拷贝传值。