Netty 和 RPC 框架线程模型分析
Posted Netty之家
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Netty 和 RPC 框架线程模型分析相关的知识,希望对你有一定的参考价值。
1. 背景
1.1 线程模型的重要性
对于 RPC 框架而言,影响其性能指标的主要有三个要素:
I/O 模型:采用的是同步 BIO、还是非阻塞的 NIO、以及全异步的事件驱动 I/O(AIO)。
协议和序列化方式:它主要影响消息的序列化、反序列化性能,以及消息的通信效率。
线程模型:主要影响消息的读取和发送效率、以及调度的性能。
除了对性能有影响,在一些场景下,线程模型的变化也会影响到功能的正确性,例如 Netty 从 3.X 版本升级到 4.X 版本之后,重构和优化了线程模型。当业务没有意识到线程模型发生变化时,就会踩到一些性能和功能方面的坑。
1.2 Netty 和 RPC 框架的线程模型关系
作为一个高性能的 NIO 通信框架,Netty 主要关注的是 I/O 通信相关的线程工作策略,以及提供的用户扩展点 ChannelHandler 的执行策略,示例如下:
该线程模型的工作特点如下:
有专门一个(一组)NIO 线程 -Acceptor 线程用于监听服务端,接收客户端的 TCP 连接请求。
网络 I/O 操作 - 读、写等由一个 NIO 线程池负责,线程池可以采用标准的 JDK 线程池实现,它包含一个任务队列和 N 个可用的线程,由这些 NIO 线程负责消息的读取、解码、编码和发送。
1 个 NIO 线程可以同时处理 N 条链路,但是 1 个链路只对应 1 个 NIO 线程,防止发生并发操作问题。
对于 RPC 框架,它的线程模型会更复杂一些,除了通信相关的 I/O 线程模型,还包括服务接口调用、服务订阅 / 发布等相关的业务侧线程模型。对于基于 Netty 构建的 RPC 框架,例如 gRPC、Apache ServiceComb 等,它在重用 Netty 线程模型的基础之上,也扩展实现了自己的线程模型。
2. Netty 线程模型
2.1 线程模型的变更
2.1.1 Netty 3.X 版本线程模型
Netty 3.X 的 I/O 操作线程模型比较复杂,它的处理模型包括两部分:
Inbound:主要包括链路建立事件、链路激活事件、读事件、I/O 异常事件、链路关闭事件等。
Outbound:主要包括写事件、连接事件、监听绑定事件、刷新事件等。
我们首先分析下 Inbound 操作的线程模型:
从上图可以看出,Inbound 操作的主要处理流程如下:
I/O 线程(Work 线程)将消息从 TCP 缓冲区读取到 SocketChannel 的接收缓冲区中。
由 I/O 线程负责生成相应的事件,触发事件向上执行,调度到 ChannelPipeline 中。
I/O 线程调度执行 ChannelPipeline 中 Handler 链的对应方法,直到业务实现的 Last Handler。
Last Handler 将消息封装成 Runnable,放入到业务线程池中执行,I/O 线程返回,继续读 / 写等 I/O 操作。
业务线程池从任务队列中弹出消息,并发执行业务逻辑。
通过对 Netty 3 的 Inbound 操作进行分析我们可以看出,Inbound 的 Handler 都是由 Netty 的 I/O Work 线程负责执行。
下面我们继续分析 Outbound 操作的线程模型:
从上图可以看出,Outbound 操作的主要处理流程如下:
业务线程发起 Channel Write 操作,发送消息。
Netty 将写操作封装成写事件,触发事件向下传播。
写事件被调度到 ChannelPipeline 中,由业务线程按照 Handler Chain 串行调用支持 Downstream 事件的 Channel Handler。
执行到系统最后一个 ChannelHandler,将编码后的消息 Push 到发送队列中,业务线程返回。
Netty 的 I/O 线程从发送消息队列中取出消息,调用 SocketChannel 的 write 方法进行消息发送。
2.1.2 Netty 4.X 版本线程模型
相比于 Netty 3.X 系列版本,Netty 4.X 的 I/O 操作线程模型比较简答,它的原理图如下所示:
从上图可以看出,Outbound 操作的主要处理流程如下:
I/O 线程 NioEventLoop 从 SocketChannel 中读取数据报,将 ByteBuf 投递到 ChannelPipeline,触发 ChannelRead 事件。
I/O 线程 NioEventLoop 调用 ChannelHandler 链,直到将消息投递到业务线程,然后 I/O 线程返回,继续后续的读写操作。
业务线程调用 ChannelHandlerContext.write(Object msg) 方法进行消息发送。
如果是由业务线程发起的写操作,ChannelHandlerInvoker 将发送消息封装成 Task,放入到 I/O 线程 NioEventLoop 的任务队列中,由 NioEventLoop 在循环中统一调度和执行。放入任务队列之后,业务线程返回。
I/O 线程 NioEventLoop 调用 ChannelHandler 链,进行消息发送,处理 Outbound 事件,直到将消息放入发送队列,然后唤醒 Selector,进而执行写操作。
通过流程分析,我们发现 Netty 4 修改了线程模型,无论是 Inbound 还是 Outbound 操作,统一由 I/O 线程 NioEventLoop 调度执行。
点击 左下角【阅读原文】查看全文
以上是关于Netty 和 RPC 框架线程模型分析的主要内容,如果未能解决你的问题,请参考以下文章
手写RPC框架-重写服务治理和引入netty,开启1000个线程看看执行调用情况