最近在做grpc连接池的优化,正巧之前只是粗略地读了一些grpc的源码,借这个机会把相关grpc的源码认真读一读,更深入地理解grpc客户端连接的参数,帮助优化grpc连接池。
图解grpc客户端连接
后面的内容都是大段地贴代码,并且因为时间原因没有写得非常细致,只是罗列了大概的脉络。所以先贴一张图出来,方便对grpc client connection的层次结构建立整体的认知,无论是看接下来我贴的代码还是自己去读源码都会非常有帮助。
从Dial方法开始
在grpc client一侧,通常上来要先通过grpc.Dial或者grcp.DialContext方法创建客户端的连接(dial其实也是调用了dialContext)。那我们也先从DialContext方法开始,看下其做了哪些事情。
DialContext的签名为传入target以及options,然后得到clientConn对象,其中target表示server端的地址,options用来传递一些参数。所以DialContext方法开始就初始化了ClientConn对象,并将options参数应用到ClientConn的dopts(dial options)字段中,随后将注册的拦截器做了初始化操作。
接下来仍然是一些初始化的操作。channelz是可以提供grpc的运行时的信息,是属于可观测性工具,并不涉及具体的功能,所以看代码时可以略过这部分。首先是grpc加密安全相关的参数,其transportCredentials为接口类型,实现了ClientHandshake和ServerHandshake方法,可以在net.Conn上实现封装。接下来是service config相关,service config有两种方式传入,一是通过json的形式在初始化时传入,另外是通过传入chan *ServiceConfig,clientConn动态监听chan并且将变动应用。
再然后对传入的target进行解析,根据target解析结果的schema拿到resolverBuilder。这里的schema主要指带不同的协议,grpc支持dns、unix、tcp等协议,resolver的作用就是将相应的endpoint解析为具体的address。304行生成了balancer的参数。314行根据上面拿到的resolverBuilder生成resolver。如果创建连接时传入了grpc.WithBlock(),则在323行时会在循环里检查ClientConn的状态,只到连接成功或者建连出错或者超时;如果没有选择withBlock,则立即返回,此时的连接处于异步建连状态,不保证建连成功。
一次客户端调用
以unary的调用为例,说明grpc调用中客户端一侧的行为。
调用链路为ClientConn.Invoke -> invoke -> newClientStream
,然后分别调用clientStream.SendMsg和clientStream.RecvMsg方法,在底层的stream的异步行为之上构建同步的调用。
接下来,看下newClientStream做了什么事。184行,实际是调用newClientStreamWithParams。
newClientStreamWithParams这个方法比较长,我们只列出一些主干的部分。首先是参数准备。
实例化clientStream对象并调用newAttemptLocked方法初始化attempt对象。
333行代码封装了op方法,在334行中withRetry调用,其实就是调用attempt的newStream方法。
attempt对象里真正地持有了transport,也就是底层的连接。
上面说了attemp持有底层的transport对象,newStream就是在transport的基础上创建stream。
在http2连接上创建stream。
调用clientStream的SendMsg方法,调用链路为clientStream.SendMsg - > csAttempt.sendMsg -> http2Client.Write -> controlBuffer.Pu
。放到controlBuffer之后loopWriter协程异步地写到framer中,framer其实就是在底层连接上包了一层的buffer。
客户端连接
异步连接。 看下http2的连接是如何创建的。 dial方法创建传输层的连接,如果创建clientConnection时传入dialer则使用该方法,否则则使用默认方法。 实例化http2Client对象。 创建controlBuffer。 异步起reader和writer协程。如果觉得本文对您有帮助,可以请博主喝杯咖啡~