[toc]
GRPC线程模型#
gRPC 服务端线程模型整体上可以分为两大类:
-
网络通信相关的线程模型,基于 Netty4.1 的线程模型实现
即HTTP/2 服务端创建、HTTP/2 请求消息的接入和响应发送都由 Netty 负责 -
服务接口调用线程模型,基于 JDK 线程池实现
即gRPC 消息的序列化和反序列化、以及应用服务接口的调用由 gRPC 的 SerializingExecutor 线程池负责。
grpc消息发送全流程详解#
-
服务端NIO Selector 轮询,监听客户端连接
-
如果监听到客户端连接,则创建客户端 SocketChannel 连接,从 workerGroup 中随机选择一个 NioEventLoop 线程,将 SocketChannel 注册到该线程持有的 NIO-Selector
-
NioEventLoop 执行NIO的标记读取和read操作
-
Netty 的 NioEventLoop 线程切换到 gRPC 的 SerializingExecutor,进行消息的反序列化、以及服务接口的调用
-
响应消息的发送,由 SerializingExecutor 发起,将响应消息头和消息体序列化,
-
调用 Netty NioSocketChannle 的 write 方法,发送到 Netty 的 ChannelPipeline 中,由 gRPC 的 NettyServerHandler 拦截之后,真正写入到 SocketChannel 中
总结而言,就是网络处理线程 和 实际业务处理线程,分成2个了,这样网络IO和CPU计算可以分开处理,不会占用同一个线程。
Q: 为什么不在Netty线程里做序列化和反序列话?#
A:
序列化和反序列化操作,都是 CPU 密集型操作,更适合在业务应用线程池中执行,提升并发处理能力。因此,gRPC 并没有在 I/O 线程中做消息的序列化和反序列化。
Q: netty4的串行化线程模型是什么?#
A:
Netty4 之后,对线程模型进行了优化,通过串行化的设计避免线程竞争:
(当系统在运行过程中,如果频繁的进行线程上下文切换,会带来额外的性能损耗)。
从消息的读取、编码以及后续 Handler 的执行,始终都由 I/O 线程 NioEventLoop 负责,这就意外着整个流程不会进行线程上下文的切换,数据也不会面临被并发修改的风险
一个 NioEventLoop 聚合了一个多路复用器 Selector,因此可以处理成百上千的客户端连接,
Netty 的处理策略是每当有一个新的客户端接入,则从 NioEventLoop 线程组中顺序获取一个可用的 NioEventLoop
最终效果就是线程之间并没有交集,这样既可以充分利用多核提升并行处理能力,同时避免了线程上下文的切换和并发保护带来的额外性能损耗
Q: grpc启动2个客户端,访问同一个端口,会有2个连接吗?#
A:
如果与路由选中的服务端之间没有可用的连接,则创建NettyClientTransport和NettyClientHandler,发起HTTP/2连接
客户端使用的work线程组并非通常意义的EventLoopGroup,而是一个EventLoop:即HTTP/2客户端使用的work线程并非一组线程(默认线程数为CPU内核 * 2),而是一个EventLoop线程。这个其实也很容易理解,一个NioEventLoop线程可以同时处理多个HTTP/2客户端连接,它是多路复用的,对于单个HTTP/2客户端,如果默认独占一个work线程组,将造成极大的资源浪费,同时也可能会导致句柄溢出(并发启动大量HTTP/2客户端)。
grpc连接池: