Netty笔记1:线程模型
Netty笔记1:线程模型
Netty笔记2:零拷贝
Netty笔记3:NIO编程
Netty笔记4:Epoll
Netty笔记5:Netty开发实例
Netty笔记6:Netty组件
Netty笔记7:ChannelPromise通知处理
Netty笔记8:ByteBuf使用介绍
Netty笔记9:粘包半包
Netty笔记10:LengthFieldBasedFrameDecoder
Netty笔记11:编解码器
Netty笔记12:模拟Web服务器
Netty笔记13:序列化
文章目录
- 前言
- 单线程Reactor模型
- 单线程Reactor工作者线程池模型
- 多线程Reactor模型
- 与BIO对比
前言
该篇纯理论,说明NIO
线程模型,为后续原理和运用打下基础,是知识和运用易于理解。
单线程Reactor模型
Reactor
接收到请求,交由Acceptor
事件处理器(accept
事件的处理器),建立socket
连接,加入队列,并监听着这个连接,当监听到读取事件,就交由对应空闲的处理器处理,一些业务操作如解码,响应等,从连接到响应都是由一个线程完成;
这里的操作就是事件,他们耗时长短不一,多个请求来时,就可以根据操作由不同的处理器完成;
流程如下:
-
服务端
reactor
线程对象,会启动一个事件循环,并使用selector
来实现IO的多路复用。注册一个Accept
的事件处理器Acceptor
到Reactor
中,Reactor
就会监听客户端向服务端发起的连接请求事件(Accept
事件); -
客户端向服务端发起一个连接请求,
Reactro
监听到了该事件(Accept
事件),并将该事件派发给对应的Acceptor
处理器。Acceptor
处理器通过accept()
方法就可以得到对应客户端的连接(SocketChannel
),之后注册一个Read
事件处理器到Reactor
中,让它监听Read
事件; -
当
Reactor
监听到读或写的事件,就会加将事件派发给对应的处理器,不会阻塞等待; -
每当处理完事件后,
Reactor
会再次执行select()
阻塞等待新事件就绪并将其派发给对应的处理器;
注意:
-
Reactor
的单线程模式主要针对IO操作而已,也就是所有IO的accept()、read()、wriete()、connect()
都在一个线程上完成。 -
每个事件对应一个处理器,在
Reactor
模式中,需要先进行注册处理器,才能对事件监听和分派任务; -
在连接后,需要注册
read
事件处理器,而wirte
事件处理器,这个也可以注册,但是相对来说,注册写事件后要的代码量更大,而且也没必要监听,向写的时候主动些就行,而读是被动的,再一点是再注册write
事件后,没有数据要写时,要取消write
的注册;为什么?
OP_WRITE
:当操作系统写缓冲区有空闲空间是就绪,一般情况下写缓冲都是空的,小数据直接写就行,没必要注册write
事件,而大数据,很容易占满缓冲区,就很有必要注册,不管小数据还是大数据,写完后要注销这个write
事件;OP_READ
:当读缓冲区有数据可读时就绪,不是时刻都有数据,所以注册该操作,仅当有数据时进行读操作,避免CPU浪费。
单线程Reactor工作者线程池模型
单线程模式下,很多非I0的处理也是单线程的业务操作,将这些业务操作就交由线程池,如解码,计算。
优势:
- 重用现有的线程而不是创建新线程,可以在处理多个请求时分摊在线程创建和销毁过程产生巨大开销;
- 请求到达时,工作线程通常已经存在,因此不会由于等待创建线程而延迟任务的执行,从而提高了响应性;
- 通过适当调整线程池的大小,可以创建足够多的线程一边使处理器保持忙碌状态,同时还可以防止过多线程相互竞争资源而使应用程序耗尽内存或失败;
虽说线程池是增加了线程,可以提高工作性能,但是对于高负载、大并发或大数据量的应用场景却不合适:
- 要给NIO线程同时处理成百上千链路,性能上无法支持,即便NIO线程的CPU负荷打到100%,也无法满足海量消息的读取和发送;
- 当NIO线程负载过重后,处理速度变慢,会导致大量客户端连接超时,超时往往会进行重发,这更加重了NIO线程的负载,最终导致大量消息积压和处理超时,成为系统性能的瓶颈。
多线程Reactor模型
大数据量下,单个线程的reactor忙于处理读取和发送,有些请求就无法处理;所以reactor就分出了mainReactor,和subReactor.
mainReactor处理请求的连接,subReactor可以有多个,处理业务与响应;
流程:
- 注册一个
Accept
的事件处理器到mainReactor
中,让其关注Accept
事件,这样mainReactor
会监听客户端向服务器端发起的连接请求(accept
事件); - 客户端向服务器端发起连接请求,
mainReactor
监听到了该accept
事件并将该事件派发给Acceptor
处理器处理,Acceptor
处理器通过accept()
获取到客户端对应的连接(SocketChannel
),然后将channel给subReactor
线程池; subReactor
线程池分配一个subReactor
线程给SocketChannel
,然后注册READ
事件到这个channel
上,让它监听;- 当有IO事件就绪时,
subReactor
就将事件派发给对应的处理器;
注意:
mainReactor、subReactor
还是处理accept()、read()、write()、connect()
这些操作。
mainReactor
只处理accept
处理;
与BIO对比
我们看下面两种IO的流程对比:
阻塞式IO就是BIO,复用IO比阻塞式IO多了一个通知,所以selector是可以处理多个连接,但是如果处理量不高的话,BIO还要比NIO快,NIO的优势是能处理更多的连接。