上两篇从I/O模型讲到了I/O多路复用器。这一篇主要总结下I/O多路复用器的主要应用——Reactor模式。
Reactor模式又称为反应器模式、分发者模式(Dispathcher)或者通知者模式(nitifier)。它是一种事件处理模式,用于处理由一个或者多个客户端并发交付给服务器的请求。对服务器来讲,CPU的速度远远高于I/O处理操作,如果因为I/O处理而阻塞CPU显然是不划算的,这促使了Reactor的诞生。
下图是Douglas C. Schmidt先生对Reactor的架构解析
Handles: 句柄,是一个标识符,只要获得对象的句柄,我们就可以对对象进行任意的操作
Synchronous Event Demultiplexer: 同步事件复用器,其实就是我们上一篇所写到的I/O多路复用器,它会阻塞等待Handler上一组事件的发生。
Initiation Dispatcher: 调度程序,它定义了注册、删除和分派事件处理程序的接口。I/O多路复用器负责等待新事件的发生,当它检测到新的事件时,会通知调度程序回调应用程序特定的事件处理程序。常见事件包括连接事件、数据输入和输出事件以及超时事件。
Event Handler: 事件处理接口,拥有一个回调方法;
Concrete Event Handler: 具体事件处理程序,实现了Event Handler接口;
具体的操作步骤可以用下图来表示:
根据Reactor的数量和处理资源池线程的数量不同,Reactor有3种典型的实现方式:
如图,单Reactor单线程是指所有的I/O操作都是在同一个NIO线程上面完成的。
优点:模型简单,没有多线程、进程通信、竞争的问题,全部都在一个线程中完成;
缺点:性能问题,只有一个线程,无法发挥多核CPU的性能。Handler在处理某个连接上的任务时,整个进程阻塞;另外可靠性上也存在问题,一旦该线程意外终止,或者进入死循环,将导致整个应用不可用。
使用场景:客户端的数量有限、业务处理非常快速,比如Redis在业务处理的时间复杂度为O(1)的情况。
如图,单Reactor多线程模型与单Reactor单线程最大的区别是有一组NIO线程来处理I/O操作。
优点:可以充分的利用多核CPU的处理能力;
缺点:多线程数据共享和访问比较复杂,且Reactor承担和处理所有事件的监听和相应请求,单线程运行在高并发场景容易出现性能瓶颈;
这个模式的特点是:服务端用于接收客户端连接的不再是一个单独的NIO线程,而是一个独立的NIO线程池。acceptor接收到了客户端TCP连接请求并处理完后,将新创建的SocketChannel注册到subReactor的某个I/O线程上,由它负责SocketChannel的读写和编解码工作。
Netty的线程模型并非是一成不变的。Netty服务端在启动的时候,会同时配置bossEventLoopGroup和workerEventLoopGroup,其实就是分别指代了mainReactor和subReactor,通过对这两个EventLoopGroup的线程个数以及是否共享线程池等基础设置,Netty可以很轻松实现以上三种线程模型。
不过,Netty的实现不止于此,它通过很多细节上的处理使其性能达到最优。例如无锁化设计、暴露TCP参数、零拷贝等等。。我会在接下来的文章一一总结。