原生NIO问题
- NIO的类库的API繁杂,使用麻烦。
- 开发工作量大,需要if判断不同的情况,如网络断开等情况需要重复处理。
- NIO存在Epoll Bug 导致选择器空轮空
Netty
netty是一个异步的基于事件驱动的网络应用框架,快速的开发高性能的service和clients
Netty支撑多种协议
https://netty.io/index.html
建构设计
线程模型
目前存在的线程模型:
- 传统阻塞IO模型
- Reactor模式
根据Reactor的数量和处理资源池线程的数量的不同又有3种典型的实现
- 单Reactor单线程
- 单Reactor多线程
- 主从Reactor多线程(Netty对此级模型改进)
传统阻塞IO模型
黄色表示对象,蓝色表示线程,白色的框表示方法,
模型特点
- 采用租的IO获取模型输入的数据
- 每个连接都需要独立的线程完成数据的输入及数据的业务处理及返回
缺点
- 当并发很大时,每个线程都需要新的线程,会占用大量的系统的资源
- 连接创建后,如果当前线程没有数据可读,该线程会阻塞在read操作上,造成线程资源的浪费
Reactor模式
对于传统阻塞IO的改进:
- 基于IO复用模型:多个连接公用一个阻塞对象,应用程序只需要在一个阻塞对象中等待,不需要阻塞所有的连接。操作系统通知的时候,线程从阻塞状态开始进行业务处理。
请求首先请求到ServiceHandler进行集中的阻塞,当收到执行通知的时候,不再阻塞。
- 基于线程池复用:不需要未每一个连接创建线程,将连接分配给线程池进行处理
通过一个或者多个输入同时传递给服务器的模式,服务器处理传入的多个请求,并将它们同步分配到相应的处理线程。使用了IO复用监听事件,收到事件后分发给某个线程,这就是网络服务高并发处理的关键
主从Reactor多线程
对任务分解,主Reactor只负责连接,将IO的读取和业务的分配交给子Reactor负责,将worker的处理,再细分给每个Reactor的线程池。
详解
- Reactor主线程MainReactor对象通过select监听连接事件,收到事件后,通过Acceptor处理连接事件
- 当Acceptor处理连接事件后MainReactor将连接分配给SubReactor
- SubReactor将连接介入到连接队列进行监听,并创建handler进行各种事件的处理
- 当有新的事件发生时,SubReactor调用对应的handler进行处理
- handlder通过read读取数据,分发给后面的worker线程池
- 由worker线程池分配worker线程对业务级进行处理
- 将响应的结果通过send方法返回给client
Netty的工作原理
Netty改进了主从Reactor多线程模式
最粗略的Netty模型
- 客户端的连接请求全部到达BossGroup,BossGroup维护Selector,只关注Accept
- 当监听到Accept事件后,获取到对应的SocketChannel,封装成NIOSocketChannel并注册到WorkerGroup
- WorkerGroup中的worker线程监听到selector发生了设定事件后,就分配一个Handler处理。
真实的Netty模型
抽象出两组线程池 1、BossGroup专门负责接受客户端的连接;2、WorkerGroup专门负责网络的读写。
BossGroup和WorkerGroup的类型都是NIOEventLoopGroup,相当于事件循环组。含有多个事件循环,每个事件循环组都是一个NIOEventLoop,表示一个循环处理任务的线程,每一个Loop都有一个Selector用于监听绑定的事件。
如上图,每个BossNIOEventLoop执行的步骤有3步
- 轮询Accept事件
- 发现Accept事件后建立连接,生成NIOSocketChannel 将NIOSocketChannel注册到某个WorkerNIOEventLoop上的selector
- 处理任务队列的任务,即runAllTanks
每个Worker NIOEventLoop循环执行的步骤有3步
- 轮询Read/Write事件
- 处理IO事件,在对应的NIOSocketChannel处理
- 处理任务队列上的任务,即runAllTanks
Pipeline
每个Worker NIOEventLoop 处理业务时,会使用pipeline,包含了channel,通过pipeline可获得对应的通道,管道中维护了很多的处理器