Netty线程模型

背景

1. Java线程模型演进

单线程、多线程、线程池.

2. Reactor模型

无论是c++还是Java、nodejs 编写的网络框架, 大多数都是基于Reactor模型进行设计和开发.

Reactor模式基于事件驱动,特别适合处理海量的 IO事件.

(1) Reactor单线程模型.

由于reactor模式是 异步非阻塞IO, 所有的IO操作都不会导致阻塞.

理论上: 一个线程可以处理所有 IO相关的操作. 从架构层面看,一个 NIO线程 确实可以完成其承担的职责.

问题:

  • 一个NIO线程同时处理成百上前的链路,性能上无法支撑。 瓶颈在于 海量消息的编码、解码、读取和发送(即便是NIO线程CPU负载100%).
  • NIO线程负载过重之后,处理速度变慢,导致大量客户端连接超时。重发,更加重了NIO线程的负载,最终会导致大量消息积压和处理超时;
  • 可靠性问题: 一旦NIO线程意外跑飞,或者进入死循环,系统通信模块直接不可用,直接故障。

(2) Reactor多线程模型

一组NIO线程处理IO操作【NIO操作指的是什么? read\write?】.

  • 一个专门的NIO线程-Acceptor线程用于监听服务端,接受客户端的TCP连接请求
  • 网络IO操作 - (读、写 等) 由一个NIO线程池负责【线程池可采用标准的JDK线程池实现,包含 一个任务队列和N个可用的线程, 这些NIO线程负责 消息的读取、解码、编码、发送】
  • 一个NIO线程可以同时处理N条链路,但是1个链路只对应1个NIO线程,防止并发操作问题.

重点: 大多数场景下, 此模型可以满足性能要求。 但是,极个别特殊场景, 一个Acceptor(NIO)线程 负责监听和处理所有的客户端连接可能存在性能问题。 例如: 并发百万客户端连接,或者服务端需要对客户端进行安全认证, 单独一个Acceptor线程 可能存在性能不足问题。

这时候,引入新的方式:Reactor 主从多线程模型。

Acceptor 接收到客户端 TCP 连接请求处理完成后(可能包含接入认证等),将新创建的 SocketChannel 注册到 IO 线程池(sub reactor 线程池)的某个 IO 线程上,由它负责 SocketChannel 的读写和编解码工作。Acceptor 线程池仅仅只用于客户端的登陆、握手和安全认证,一旦链路建立成功,就将链路注册到后端 subReactor 线程池的 IO 线程上,由 IO 线程负责后续的 IO 操作。

解决了 1 个服务端监听线程无法有效处理所有客户端连接的性能不足问题。

1
2
3
4
5
它的工作流程总结如下:

从主线程池中随机选择一个 Reactor 线程作为 Acceptor 线程,用于绑定监听端口,接收客户端连接;
Acceptor 线程接收客户端连接请求之后创建新的 SocketChannel,将其注册到主线程池的其它 Reactor 线程上,由其负责接入认证、IP 黑白名单过滤、握手等操作;
步骤 2 完成之后,业务层的链路正式建立,将 SocketChannel 从主线程池的 Reactor 线程的多路复用器上摘除,重新注册到 Sub 线程池的线程上,用于处理 I/O 的读写操作。

Netty 线程模型

bossGroup 线程组实际就是 Acceptor 线程池,负责处理客户端的 TCP 连接请求,如果系统只有一个服务端端口需要监听,则建议 bossGroup 线程组线程数设置为 1。

workerGroup 是真正负责 I/O 读写操作的线程组,通过 ServerBootstrap 的 group 方法进行设置,用于后续的 Channel 绑定。

IO操作有哪些。

  • OP_READ
  • OP_WRITE
  • OP_CONNECT
  • OP_ACCEPT

Reactor是针对这些 事件 分发到不同的线程来进行处理.

问题:

作为服务端 Acceptor 线程,负责处理客户端的请求接入

boss线程和work线程的代码是一致的。 怎么区分? 是走的逻辑不同??

NioEventLoop设计原理

  • 串行化设计避免线程竞争:串行执行Handler链.
  • 定时任务与时间轮算法
  • 聚焦而不是膨胀:只负责提供和管理NIO线程

1. 串行化设计避免线程竞争

2. 定时任务与时间轮算法

Netty中,很多功能依赖定时任务,比较典型的有2种:

  • 客户端连接超时控制;
  • 链路空闲检测

常见的设计理念是在 NioEventLoop中聚合JDK的 定时任务线程池 ScheduledExecutorService.

从性能角度看不是最优:原因:

  • 线程上下文切换,打破了串行化设计理念
  • 存在多线程并发问题. 定时Task和IO线程NioEventLoop可能同时访问并修改一份数据
  • Jdk的sc..本身存在性能优化空间.

最早面临上述问题的是操作系统和协议栈,例如 TCP 协议栈,其可靠传输依赖超时重传机制,因此每个通过 TCP 传输的 packet 都需要一个 timer 来调度 timeout 事件。这类超时可能是海量的,如果为每个超时都创建一个定时器,从性能和资源消耗角度看都是不合理的。

时间轮算法调度: 根据 George Varghese 和 Tony Lauck 1996 年的论文《Hashed and Hierarchical Timing Wheels: data structures to efficiently implement a timer facility》提出了一种定时轮的方式来管理和维护大量的 timer 调度。Netty 的定时任务调度就是基于时间轮算法调度,下面我们一起来看下 Netty 的实现。

Netty 系列之 Netty 线程模型