写点什么

Netty 高性能之 Reactor 模型

作者:C++后台开发
  • 2022 年 9 月 28 日
    湖南
  • 本文字数:3267 字

    阅读完需:约 11 分钟

Netty高性能之Reactor模型

Reactor 模型

Reactor 是一种模式,它要求主线程只负责监听文件描述符(I/O 处理单元)是否有事件发生,有的话就立即将该事件通知工作线程(逻辑单元)完成处理。除此之外,主线程不做任何其他实质性的工作。读写数据以及处理客户请求均在工作线程中完成。

Netty 中的 Reactor 模型

Netty 中的 Reactor 模型主要由多路复用器 (Acceptor)、事件分发器 (Dispatcher)、事件处理器 (Handler) 组成,可以分为单线程模型,多线程模型和主从 Reactor 多线程模型三种。

  • Reactor 单线程模型

  • Reactor 多线程模型

  • 主从 Reactor 多线程模型

Reactor 模式可以参考:Reactor 模式详解+源码实现

1. 单线程模型

所有 I/O 操作都由一个线程完成,即多路复用、事件分发和处理都是在一个 Reactor 线程上完成的,一个 Reactor 线程就是一个 NIO 线程。

​Reactor 单线程模型使用的是一个 NIO 线程, NIO 使用的是非阻塞 I/O,所有的 I/O 操作都不会阻塞,所以一个线程可以处理多个 TCP 连接请求。

对于一些小容量应用场景,可以使用单线程模型,但是对于高负载、大并发的应用却不合适,主要原因如下:

  • 一个 NIO 线程同时处理成百上千的链路,性能上无法支撑。即便 NIO 线程的 CPU 负荷达到 100%,也无法满足海量消息的编码、解码、读取和发送;

  • 当 NIO 线程负载过重之后,处理速度将变慢,这会导致大量客户端连接超时,超时之后往往进行重发,这更加重了 NIO 线程的负载,最终导致大量消息积压和处理超时,NIO 线程会成为系统的性能瓶颈;

  • 可靠性问题。一旦 NIO 线程意外跑飞,或者进入死循环,会导致整个系统通讯模块不可用,不能接收和处理外部信息,造成节点故障。

2 Reactor 多线程模型

Reactor 多线程模型与单线程模型最大区别就是有一组 NIO 线程处理 I/O 操作,它的特点如下:

  • 有一个专门的 NIO 线程用于监听服务端,接收客户端的 TCP 连接请求;

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

  • 1 个 NIO 线程可以同时处理 N 条链路,但是 1 个链路只对应 1 个 NIO 线程,防止发生并发操作问题。

​在绝大多数场景下,Reactor 多线程模型都可以满足性能需求;但是,在极特殊应用场景中,一个 NIO 线程负责监听和处理所有的客户端连接可能会存在性能问题。例如百万客户端并发连接,或者服务端需要对客户端的握手信息进行安全认证,认证本身非常损耗性能。这类场景下,单独一个 Acceptor 线程可能会存在性能不足问题,为了解决性能问题,产生了第三种 Reactor 线程模型 - 主从 Reactor 多线程模型。

C/C++Linux服务器开发高级架构师/C++后台开发架构师​免费学习地址

另外还整理一些 C++后台开发架构师 相关学习资料,面试题,教学视频,以及学习路线图,免费分享详情看以下视频

3 主从 Reactor 多线程模型

服务端用于接收客户端连接的不再是 1 个单独的 NIO 线程,而是一个独立的 NIO 线程池。Acceptor 接收到客户端 TCP 连接请求处理完成后(可能包含接入认证等),将新创建的 SocketChannel 注册到 I/O 线程池(subReactor 线程池)的某个 I/O 线程上,由它负责 SocketChannel 的读写和编解码工作。

Acceptor 线程池只用于客户端的登录、握手和安全认证,一旦链路建立成功,就将链路注册到后端 subReactor 线程池的 I/O 线程上,有 I/O 线程负责后续的 I/O 操作。

1 Netty 线程模型

Netty 的线程模型并不是一成不变的,它实际取决于启动参数配置。通过设置不同的启动参数来支持 Reactor 不同的线程模型。Netty 支持 Reactor 单线程模型、多线程模型、主从多线程模型。

Netty 启动示例

这里给出的是都是 Netty 服务端的启动示例。

public static void main(String[] args) throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap();        b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) { ChannelPipeline p = ch.pipeline();                    p.addLast(new DiscardServerHandler()); } }); ChannelFuture f = b.bind(PORT).sync();        f.channel().closeFuture().sync(); } finally {        workerGroup.shutdownGracefully();        bossGroup.shutdownGracefully(); }} 
复制代码

每一个 EventLoopGroup 都是 Reactor 的线程池。ServerBootstrap.group 需要接受两个参数 EventLoopGroup 参数。 一个是处理接收客户端的 TCP 连接(NIO 的 SelectionKey.OPCONNECT),另一个是处理 I/O 相关的操作(NIO 的 SelectionKey.OPREAD, SelectionKey.OP_WRITE)。

1 Netty 单线程模型

public static void main(String[] args) throws Exception { EventLoopGroup group = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap();        b.group(group, group) // 或者 b.group(group) .channel(NioServerSocketChannel.class) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) { ChannelPipeline p = ch.pipeline();                    p.addLast(new DiscardServerHandler()); } }); ChannelFuture f = b.bind(PORT).sync();        f.channel().closeFuture().sync(); } finally {        workerGroup.shutdownGracefully();        bossGroup.shutdownGracefully(); }}
复制代码

创建只有一个线程的 Reactor 线程池。把处理接收客户端 TCP 连接的 Reactor 线程池和处理 I/O 相关操作的 Reactor 线程池都是使用这个只有一个线程的 Reactor 线程池。

2 Netty 多线程模型

public static void main(String[] args) throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(1); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap();        b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) { ChannelPipeline p = ch.pipeline();                    p.addLast(new DiscardServerHandler()); } }); ChannelFuture f = b.bind(PORT).sync();        f.channel().closeFuture().sync(); } finally {        workerGroup.shutdownGracefully();        bossGroup.shutdownGracefully(); }}
复制代码

创建两个 Reactor 线程池,处理客户端 TCP 连接的线程池只有 1 个线程,而处理 I/O 的 Reactor 线程池有多个线程处理。不知道线程数则默认是 2* CPU 个数。

Netty 主从多线程模型

public static void main(String[] args) throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap();        b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) .handler(new LoggingHandler(LogLevel.INFO)) .childHandler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) { ChannelPipeline p = ch.pipeline();                    p.addLast(new DiscardServerHandler()); } }); ChannelFuture f = b.bind(PORT).sync();        f.channel().closeFuture().sync(); } finally {        workerGroup.shutdownGracefully();        bossGroup.shutdownGracefully(); }} 
复制代码

创建两个 Reactor 线程池,处理客户端 TCP 连接的线程池和处理 I/O 操作的线程池都是多个线程处理。


原文地址:Netty高性能之Reactor模型

用户头像

C/C++后台开发技术交流qun:720209036 2022.05.06 加入

还未添加个人简介

评论

发布
暂无评论
Netty高性能之Reactor模型_后台开发_C++后台开发_InfoQ写作社区