Netty 和 RPC 框架线程模型分析

Posted Netty之家

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Netty 和 RPC 框架线程模型分析相关的知识,希望对你有一定的参考价值。

1. 背景

1.1 线程模型的重要性

对于 RPC 框架而言,影响其性能指标的主要有三个要素:

  1. I/O 模型:采用的是同步 BIO、还是非阻塞的 NIO、以及全异步的事件驱动 I/O(AIO)。

  2. 协议和序列化方式:它主要影响消息的序列化、反序列化性能,以及消息的通信效率。

  3. 线程模型:主要影响消息的读取和发送效率、以及调度的性能。

除了对性能有影响,在一些场景下,线程模型的变化也会影响到功能的正确性,例如 Netty 从 3.X 版本升级到 4.X 版本之后,重构和优化了线程模型。当业务没有意识到线程模型发生变化时,就会踩到一些性能和功能方面的坑。

1.2 Netty 和 RPC 框架的线程模型关系

作为一个高性能的 NIO 通信框架,Netty 主要关注的是 I/O 通信相关的线程工作策略,以及提供的用户扩展点 ChannelHandler 的执行策略,示例如下:

图 1 Netty 多线程模型

该线程模型的工作特点如下:

  1. 有专门一个(一组)NIO 线程 -Acceptor 线程用于监听服务端,接收客户端的 TCP 连接请求。

  2. 网络 I/O 操作 - 读、写等由一个 NIO 线程池负责,线程池可以采用标准的 JDK 线程池实现,它包含一个任务队列和 N 个可用的线程,由这些 NIO 线程负责消息的读取、解码、编码和发送。

  3. 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 操作线程模型比较复杂,它的处理模型包括两部分:

  1. Inbound:主要包括链路建立事件、链路激活事件、读事件、I/O 异常事件、链路关闭事件等。

  2. Outbound:主要包括写事件、连接事件、监听绑定事件、刷新事件等。

我们首先分析下 Inbound 操作的线程模型:

Netty 和 RPC 框架线程模型分析

图 2 Netty 3 Inbound 操作线程模型

从上图可以看出,Inbound 操作的主要处理流程如下:

  1. I/O 线程(Work 线程)将消息从 TCP 缓冲区读取到 SocketChannel 的接收缓冲区中。

  2. 由 I/O 线程负责生成相应的事件,触发事件向上执行,调度到 ChannelPipeline 中。

  3. I/O 线程调度执行 ChannelPipeline 中 Handler 链的对应方法,直到业务实现的 Last Handler。

  4. Last Handler 将消息封装成 Runnable,放入到业务线程池中执行,I/O 线程返回,继续读 / 写等 I/O 操作。

  5. 业务线程池从任务队列中弹出消息,并发执行业务逻辑。

通过对 Netty 3 的 Inbound 操作进行分析我们可以看出,Inbound 的 Handler 都是由 Netty 的 I/O Work 线程负责执行。

下面我们继续分析 Outbound 操作的线程模型:

图 3 Netty 3 Outbound 操作线程模型

从上图可以看出,Outbound 操作的主要处理流程如下:

  1. 业务线程发起 Channel Write 操作,发送消息。

  2. Netty 将写操作封装成写事件,触发事件向下传播。

  3. 写事件被调度到 ChannelPipeline 中,由业务线程按照 Handler Chain 串行调用支持 Downstream 事件的 Channel Handler。

  4. 执行到系统最后一个 ChannelHandler,将编码后的消息 Push 到发送队列中,业务线程返回。

  5. Netty 的 I/O 线程从发送消息队列中取出消息,调用 SocketChannel 的 write 方法进行消息发送。

2.1.2 Netty 4.X 版本线程模型

相比于 Netty 3.X 系列版本,Netty 4.X 的 I/O 操作线程模型比较简答,它的原理图如下所示:

图 4 Netty 4 Inbound 和 Outbound 操作线程模型

从上图可以看出,Outbound 操作的主要处理流程如下:

  1. I/O 线程 NioEventLoop 从 SocketChannel 中读取数据报,将 ByteBuf 投递到 ChannelPipeline,触发 ChannelRead 事件。

  2. I/O 线程 NioEventLoop 调用 ChannelHandler 链,直到将消息投递到业务线程,然后 I/O 线程返回,继续后续的读写操作。

  3. 业务线程调用 ChannelHandlerContext.write(Object msg) 方法进行消息发送。

  4. 如果是由业务线程发起的写操作,ChannelHandlerInvoker 将发送消息封装成 Task,放入到 I/O 线程 NioEventLoop 的任务队列中,由 NioEventLoop 在循环中统一调度和执行。放入任务队列之后,业务线程返回。

  5. I/O 线程 NioEventLoop 调用 ChannelHandler 链,进行消息发送,处理 Outbound 事件,直到将消息放入发送队列,然后唤醒 Selector,进而执行写操作。

通过流程分析,我们发现 Netty 4 修改了线程模型,无论是 Inbound 还是 Outbound 操作,统一由 I/O 线程 NioEventLoop 调度执行。

点击 左下角【阅读原文】查看全文

以上是关于Netty 和 RPC 框架线程模型分析的主要内容,如果未能解决你的问题,请参考以下文章

彻底搞懂 netty 线程模型

手写RPC框架-重写服务治理和引入netty,开启1000个线程看看执行调用情况

BIO/NIO 线程模型以及高性能通讯框架 Netty Reactor 模型初探

基于Netty构建高性能RPC通信框架

Netty_02_高性能的NIO框架

Netty_02_高性能的NIO框架