golang 的grpc库是 https://github.com/grpc/grpc-go
grpc server端和服务端网络协议是在tcp基础上的 http2协议,http2协议负责grpc基础的数据传输、连接管理、流控等, 具体的业务层service 定义是基于 protobuf的
整个的网络过程和关键点如下图
说明:
http2协议是支持在一个tcp连接上, client端同时发送多个request (不同request, streamid不同),不必等待server端响应第一个request后再发送第一个, 这是http2协议的应用层多路复用,这个特点,可以大大提高单个tcp连接的使用效率。 更多http2协议的细节参考https://www.jianshu.com/p/e57ca4fec26f
在http2协议的支持下, client端可以多个协程同时发送request , 而server端收到请求后,首先会在单个协程内完成 http2 frame数据帧的解码工作, 如果是个业务请求(DataFrame), 会启动一个新的协程来处理单个请求(这样保证一个tcp连接处理请求的并发能力)。grpc下每个request开一个协程的关键代码如下
//google.golang.org/grpc/server.go
func (s *Server) serveStreams(st transport.ServerTransport) {
defer st.Close()
var wg sync.WaitGroup
// HandleStreams 是注册 grpc server处理 http2 stream 数据的处理函数
st.HandleStreams(func(stream *transport.Stream) {
wg.Add(1)
//每次有新request时会调用这个方法, 这个方法就是开新的协程处理请求
go func() {
defer wg.Done()
s.handleStream(st, stream, s.traceInfo(st, stream))
}()
}, func(ctx context.Context, method string) context.Context {
if !EnableTracing {
return ctx
}
tr := trace.New("grpc.Recv."+methodFamily(method), method)
return trace.NewContext(ctx, tr)
})
wg.Wait()
}
grpc连接池。 如果单个连接的http2 编解码能力(这个是单协程内完成)成为你的瓶颈,可以考虑连接池功能。 可以参考
https://github.com/processout/grpc-go-pool
https://github.com/rfyiamcool/grpc-client-pool/blob/master/client.gokeepalive 特性
tcp 系统层面可以设置keepalive。 但是这不是grpc的keepalive原理, grpc的keepalive 是通过有规律的 ping pong 包维持的。 详细的client 和server端代码参考grpc 的feature demo
https://github.com/grpc/grpc-go/tree/master/examples/features/keepalive
TCP KeepAlive则是为了探测/保鲜(心跳检测,连接错误检测):用于探测对端的状态及网络情况(有可能客户端崩溃、强制关闭了应用、主机不可达等等),也有保鲜功能。比如如防止nat超时。TCP keepalive则是通过发送发送侦测包实现。在Linux中通过net.ipv4.tcp_keepalive_intvl,net.ipv4.tcp_keepalive_probes,net.ipv4.tcp_keepalive_time配置。比如gnet 网络框架中的实现https://github.com/panjf2000/gnet/blob/master/netpoll/netpoll_unix.go
- grpc 下http2 数据包查看
运行client的时候加入 GODEBUG=http2debug=2 环境变量可以看到 http2数据frame的发送细节
export GODEBUG=http2debug=2 && go run grpc_temp.go
有疑问加站长微信联系(非本文作者)