Netty——Reactor模式及Netty模型
文章目录
一、Netty概述
1.1 NIO的问题
-
NIO 的类库和 API 繁杂,使用麻烦。
-
对多线程和网络编程需要非常熟悉,才能编写出高质量的 NIO 程序。
-
容易遇到客户端面临断连重连、网络闪断、半包读写、失败缓存、网络拥塞和异常流的处理等问题。
-
JDK NIO 的 Epoll Bug,它会导致 Selector 空轮询,最终导致 CPU 100%。
1.2 Netty简介
Netty框架:
二、线程模型
线程模型分类:
-
传统阻塞I/O模型
-
Reactor模式
2.1 传统阻塞I/O模型
-
每个连接需要独立的线程处理
-
在read()处阻塞,造成资源浪费
-
高并发时占用大量系统资源
2.2 Reactor模式
Reactor模式是通过一个或多个输入同时传递给服务处理器的模式(基于事件驱动)。服务端程序会处理传入的多个请求,并将它们同步分派到相应的处理线程,因此Reactor模式也叫Dispatcher模式。Reactor模式使用IO多路复用监听事件,在监听到事件后,会分发给某个线程 / 进程。
Reactor:1. 反应器模式;2. 分发者模式;3. 通知者模式。根据Reactor的数量和处理资源池线程的数量不同,分为三种类型:
- 单Reactor + 单线程
- 单Reactor + 多线程
- 主从Reactor + 多线程
Netty线程模型主要基于主从Reactor + 多线程模型做了一定的改进,主从Reactor多线程模型中有多个Reactor。
Reactor模式针对传统阻塞I/O模型的2个缺点进行了优化:
- 基于 I/O 多路复用模型:多个连接共用一个阻塞对象,应用程序只需在一个阻塞对象处等待,无需阻塞等待所有连接。当某个连接有新的数据可以处理时,操作系统通知应用程序,线程从阻塞状态返回,开始进行业务处理。
- 基于线程池复用线程资源:不必再为每个连接创建线程,将连接完成后的业务处理任务分配给线程进行处理,一个线程可以处理多个连接的业务。
Reactor模式中的核心组成部分:
- Reactor
Reactor在一个单独的线程中运行,负责监听和分发事件,分发给适当的处理程序来对IO事件作出反应。
- Handlers
处理程序执行I/O事件要完成的实际事件,Reactor通过调度适当的处理程序来响应I/O事件,处理程序执行非阻塞操作。
2.2.1 单Reactor + 单线程
- Select 是前面 I/O 多路复用模型介绍的标准网络编程 API,可以实现应用程序通过一个阻塞对象监听多路连接请求。
- Reactor 对象通过 Select 监控客户端请求事件,收到事件后通过 Dispatch 进行分发。
- 如果是建立连接请求事件,由 Acceptor 通过 Accept 处理连接请求,然后创建一个 Handler 对象处理连接完成后的后续业务处理。
- 如果不是建立连接事件,则 Reactor 会分发调用连接对应的 Handler 来响应。
- Handler 会完成 Read + 业务处理 + Send 的完整业务流程。
优点:
模型简单,没有多线程、进程通信、竞争的问题,全部都在一个线程中完成。
缺点:
只有一个线程,无法完全发挥多核 CPU 的性能,Handler 在处理某个连接上的业务时,容易导致性能瓶颈。同时线程意外终止,或者进入死循环,会导致整个系统通信模块不可用,不能接收和处理外部消息。
2.2.2 单Reactor + 多线程
- Reactor 对象通过select 监控客户端请求事件,收到事件后,通过Dispatch进行分发。
- Handler 只负责响应事件,不做具体的业务处理,通过read 读取数据后,会分发给后面的Worker线程池的某个线程处理业务。
- Worker 线程池会分配独立线程完成真正的业务,并将结果返回给Handler。
- Handler收到响应后,通过send 将结果返回给Client。
2.2.3 主从Reactor + 多线程
- Reactor主线程 MainReactor 只注册一个用于监听连接请求的ServerSocketChannel,通过select 监听连接事件, 收到事件后,通过Acceptor 处理连接事件。
- 当 Acceptor 处理连接事件后,MainReactor 通过accept获取新的连接,并将连接注册到SubReactor。
- Subreactor 将连接加入到连接队列进行监听,并创建Handler进行各种事件处理。
- Worker 线程池分配独立的worker 线程进行业务处理,并返回结果给SubReactor的Handler。
- Reactor 主线程可以对应多个Reactor 子线程,即MainRecator 可以关联多个SubReactor。
三、Netty模型
3.1 示例模型
Netty主要是基于主从Reactor + 多线程模型做了一定的改进,其中主从Reactor多线程模型有多个Reactor。
- BossGroup维护多个主Reactor,主Reactor还是只关注连接的Accept。
- WorkGroup来维护多个从Reactor,从Reactor将接收到的请求交给Handler进行处理。
- 在主Reactor中接收到Accept事件,获取到对应的SocketChannel,Netty会将它进一步封装成NIOSocketChannel对象,包含有该Channel对应的SelectionKey、通信地址等。
- Netty会将封装后的Channel对象注册到WorkerGroup中的从Reactor中。
- 当WorkerGroup中的从Reactor监听到事件后,就会将之交给与此Reactor对应的Handler进行处理。
3.2 详细模型
-
Netty抽象出两组线程池,BossGroup专门负责接收客户端的连接,WorkerGroup专门负责网络的读写。
-
BossGroup和WorkerGroup类型的本质都是NioEventLoopGroup类型。
-
NioEventLoopGroup相当于一个线程管理器(类似于ExecutorServevice),即事件循环组,维护多个NioEventLoop线程,每个事件即对应一个NioEventLoop线程。
-
NioEventLoop表示一个不断循环地执行处理任务的线程,它维护了一个Selector和任务队列,内部采用串行化设计。
-
每个NioEventLoop都包含一个Selector,用于监听绑定在它上面的Socket通讯,负责处理多个NioChannel上的事件。
-
每个NioChannel只会绑定在唯一的NioEventLoop上,并且都绑定有一个Channel Pipeline。
-
每增加一个请求连接,NioEventLoopGroup就将这个请求依次分发给它下面的NioEventLoop处理。
-
处理业务时会使用Pipeline,Pipeline中维护了一个ChannelHandlerContext链表,ChannelHandlerContext保存了Channel相关的所有上下文信息,同时关联一个ChannelHandler对象。Channel和Pipeline一一对应,ChannelHandler和ChannelHandlerContext一一对应。
-
ChannelHandler是一个接口,负责处理或拦截I/O操作,并将其转发到Pipeline中的下一个Handler进行处理。
- Netty模型
- Pipeline管道模型
Boss NioEventLoop循环执行:
- 轮询accept事件;
- 处理accept事件,与client建立连接,生成NioSocketChannel,并将其注册到某个Worker NioEventLoop的selector上;
- 处理任务队列到任务,即runAllTasks。
Worker NioEventLoop循环执行:
- 轮询read、write事件;
- 处理I/O事件,即read、write事件,在对应的NioSocketChannel中进行处理;
- 处理任务队列的任务,即runAllTasks。
NioEventLoopGroup补充:
- 在初始化两个Group线程组时,默认会在每个Group中生成CPU * 2个NioEventLoop线程。
- 当有n个连接时,Group默认会按照连接请求的顺序分别将这些连接分发给各个NioEventLoop去处理。
- Group还负责管理EventLoop的生命周期。