传统IO模型主要的问题在于性能低、在单线程模型下一个Socket 只能处理一个请求连接、而如果多个请求连接访问的时候、只有等待、串行一个一个处理效率极低。
即使使用了多线程来创建Socket来并行处理连接请求、那么在高并发的情况下需要创建很多线程来处理、而每个线程的创建是非常昂贵的,系统内存开销巨大、性价比不高。
那如何优化这个问题;咱们可以通过NIO 中的IO复用Reactor来解决这个问题、也就是可以做到一个线程、一个Reactor能够同一时间处理大量连接请求、大大降低了线程创建的开销。
Reactor单线程流程图
image.png1)当一个request发送过来、Reactor通过accept接收连接通道请求,并且为当前通道;设置并注册可读的参数;再通过dispatch绑定Handler处理器。
2)当一个request发送过来、select负责查询IO事件,当检测到一个IO事件,将其发送给相应的Handler处理器去处理。这里的IO事件,就是NIO中选择器监控的通道IO事件(比如Client发送一个数据报到服务端 就是一个事件)。
存在的问题:
3)由于是单线程;如果Handler处理器一旦阻塞、可能会导致其他的Handler都执行不到,因为是串行执行,要是导致AcceptorHandler处理器执行不到;会导致后面所有的通道连接都有问题。
4)单线程Reactor模型不能够充分利用多核CPU资源、在高性能服务器应用场景中,单线程Reactor使用很少。
我们演化下 来看看单Reactor多线程能不能处理这些问题。
image.png之前单Reactor存在的问题是不管是接收通道连接、dispatch分发任务、处理任务都是绑在一起处理的、中间那个环节存在问题都会相互影响、而这种把执行任务版块拆分处理;并且通过线程池来处理。
1、直接提升了Handler任务处理能力,充分利用了多核CPU资源,毕竟消耗资源的版块都在Handler之中。
2、即使其中有Handler存在问题对于Acceptor接收连接请求也是没有直接影响的,因为已经不是在一个线程中串行执行了。
存在的问题:
Ractor存在单点问题、如果Reactor真的挂了怎么办?
多线程之间数据共享;处理复杂!
高并发的情况下单节点Reactor的担忧!
我们看看主从Reactor多线程能不能处理这些问题、咱们继续演变。
image.png这种玩法其实就是把任务拆分的更细、之前我们是有 接收通道连接、dispatch分发任务、Handler处理任务。那么拆分后;我们的主Reactor处理的任务工作就更小了、只需处理通道连接、以及接收到可用的通道连接;再转发到子Reactor。
而子Reactor(可以多个子Reactor) 再通过Dispatch分发到Handler去处理、处理完直接返回Client;不需要通过主Reactor来返回、也就是说主Reactor只需要接收通道连接、转发给子Reactor就完了。
优点:主从Reactor交互简单、分工明确。
优点:响应快、扩展性好、可复用性高。
缺点:开发编码难度困难、极其容易造成死循环、不信你试试。
我们来看看Netty模型、Netty模型主要是基于主从Reactor改进的。
image.pngBoss Group相当于主Reactor、而Worker Group 相当于从Reactor 。流程还是相对简单;Boss 接收处理请求通道事件、转发到Work Group去处理、通过ServerBootstrap组装启动、再通过Pipeline去处理Handler 处理器(一个Pipeline可自由组装多个Handler 、每个通道都会一一对应一个Channel连接通道)。
一般代码配置上Boss Group 设置的线程数是1、 也就是一个Reactor来处理、因为Boss处理的事情很少一般一个线程就够了。
image.png而Worker Group使用的是默认的、也就是当前操作系统CPU的核数*2 、比如8核CPU 相当于16个Worker Reactor 来进行处理分发任务。
image.pngNetty优点:
- 封装简化了Reactor网络编程模式、更简单、开发人员只需要关注Handler业务即可;
2. pipeline自由组装业务所需功能;
3. 网络IO高性能(zero copy);
PS:Reactor 封装了NIO 而它为什么性能好?基本原理理解及供参考:
1、NIO首先他能够【一个线程处理多个连接】、他的思路是;你来一个连接事件;我就丢到一个链表集合进行管理;你同一时间片来一千个连接事件、我也丢到链表集合进行管理;再通过调用底层select 来轮训 链表中的【就绪事件】,也就是已经准备好的事件;循环并处理;相当于做了一个缓冲区;但这个缓冲区链表能够支持管理多少个事件连接数;受限于系统句柄数、一般系统默认是1024、可以通过手动去调优;句柄数大小受限于系统物理内存;所以只要内存够用;支持百万并发也不是问题。这就是NIO多路复用的特点。
2、NIO底层调用操作系统:分别select/poll/epoll这三个函数(看操作系统版本) selelct是轮训准备就绪事件、如果并发非常大的情况下;性能会逐步降低;而epoll在连接事件上加了个回调事件、当已经有准备就绪的连接事件、通过【回调事件】就把就绪事件放到另外一个【就绪链表】中、然后再轮训这个【就绪链表】、判断是否为null就好了、这种方式减少了大量的CPU开销、这就是回调机制的特点、而select/poll需要不断轮训所有fd集合、消耗性能巨大。
网友评论