Reator 模式 + Netty 线程模型 + 最佳实践建议

 

Reactor 模式

大部分网络框架的设计都基于 Reactor 模式。

这种模式基于事件驱动,特别适合处理大量的 IO 事件。

根据线程数量,我们可以将 Reactor 模式大致分为以下3种(以服务端实现为例):

单线程 Reactor

单个 Reactor 线程负责对TCP链路读写数据和编解码(包括执行业务逻辑)。

(很多人把该模式称为 “1 - 1”。其实这种称呼并不贴切。)

适用场景

适合 并发度低、请求处理快 的小应用。

 

缺陷

不适合 高并发、高负载的场景。因为:

  • 单线程处理大量并发链路时性能不高,也无法发挥多核计算机的优势。无法满足大量消息的编解码和读写需求。

  • 单线程负载过高后,处理速度变慢,可能导致大量客户端连接超时。超时会引起消息重发,线程负载更重。最终大量消息积压、超时,成为系统的性能瓶颈。

  • 单线程可靠性不足。单个线程意外终止或陷入死循环,会导致整个系统通信瘫痪,无法接受和处理外部请求。

 

Reator 模式 + Netty 线程模型 + 最佳实践建议_第1张图片

 

多线程 Reactor

与单线程Reactor模式不同,此模式用一个 NIO线程池 代替原来的单个Reactor线程。

通常,Reactor 线程池中,每个线程可以同时处理 N条链路;

但是一个链路只能由1个线程处理,以防止线程安全问题。

缺陷

这种模式可以满足绝大多数场景的性能需求。

但是 单线程Acceptor 也可能成为性能瓶颈。客户端连接非常多时,处理客户端请求连接/安全认证等操作也会非常耗性能。

 

 

 

主从 多线程 Reactor

为了解决 单线程Acceptor 的性能问题,又衍生出了第三种模式 —— “主从 多线程 Reactor”。

即,用一个 NIO线程池 专门处理那些与耗时的非业务性操作。

大致过程如下:

开始监听连接:由一个线程作为 Acceptor,绑定监听端口接收客户端连接。

具体实现时,这个Acceptor线程可以是从“主Reactor线程池”中随机选定的一个线程

收到连接 并 分配处理线程:Acceptor 接收到客户端连接,并创建 SocketChannel,并将其注册到 主线程池 的 Reactor 线程上。

执行非业务性操作:主线程池的 Reactor 线程负责 接入认证、IP黑白名单过滤、握手等非业务性操作。

执行业务逻辑:执行完上一步,业务层链路正式建立。SocketChannel 从 主线程池 中线程的多路复用器上摘除,重新注册到 从线程池 的线程上,执行处理后续业务操作。

 

Reator 模式 + Netty 线程模型 + 最佳实践建议_第2张图片

 

Netty 线程模型

Netty 的线程模型与上述三种 Reactor 线程模型相似。

Netty 提供了便捷的API来实现相关线程配置。通过 NioEventLoopGroup 的构造方法 和 ServerBootstrap.group() 方法就可以实现相应的线程配置。

可以简单地理解为:

一个 EventLoop 对应一个 Reactor 线程;

EvenLoopGroup 则对应 线程池(ExecutorService)。

Netty 官方示例

Reator 模式 + Netty 线程模型 + 最佳实践建议_第3张图片

 

Netty 线程开发最佳实践

时间可控的简单业务 直接在 IO线程 上处理

如果业务非常简单,执行时间非常短,不需要访问外部资源(如,网络、数据库、磁盘等)时,可以直接在 ChannelHandler 中执行业务。

这样实现简单,避免线程上下文切换,也不会有线程安全问题。

 

复杂、时间不可控的业务 投递到后端业务线池处理

可以将这类业务封装成 Task,投递到后端的业务线程池处理。

因为过多的 业务ChannelHandler 会降低开发效率,增加维护成本。

不要把 Netty 当作业务容器。

 

业务线程 不要直接操作 ChannelHandler

(从某种角度而言,这算是上一条的延伸。)

业务通常是多线程模型处理的,如果业务线程直接操作 ChannelHandler,就需要处理线程安全问题。

可以参照Netty自身的做法,将操作封装成独立的 Task 由 NioEventLoopGroup 统一调度。

我们可以在Netty源码中找很多类似如下的代码:

Netty ChunkedWriteHandler 中的 resumeTransfer() 方法:

Java代码

 

  1. public void resumeTransfer() {  

  2.   ...  

  3.   if (ctx.executor.inEventLoop()) {  

  4.     resumeTransfer0(ctx);  

  5.   } else {  

  6.     // let the transfer resume on the next event loop round  

  7.     ctx.executor.execute(() -> resumeTransfer0(ctx));  

  8.   }  

  9. }  

 

你可能感兴趣的:(Java)