前几篇文章主要是从Nginx特性和原理方面做了详细的讲解,让我们知道了Nginx是做什么的以及它为何如此高效,以至于全宇宙拿它来做负载均衡或者说web server。
但是如果你只是了解了使用和知道了原理就认为已经掌握了它,那只能说你肤浅了,原理和使用技能看看大家都知道了,没必要拿出去和别人拽,但凡你和别人说Nginx的epoll我清楚,Master-Worker是如何工作的,初级选手可能觉得你真牛,你真厉害,可是碰到高手了,你那最多只是熟悉了这个组件而已,你并没有多大的成长,而高手通过对Nginx的深入了解,他能发现其中的秘籍,这个秘籍可以帮助他触类旁通其他组件,从而用最短的时间掌握更多的技术,在互联网领域立足不败之地。
那么高手是如何通过对Nginx的学习使自己达到更高的境界呢?
设计模式
别笑,学会了设计模式,你就可以自豪的说出金庸先生在《倚天屠龙记》里九阳真经的口诀:他强由他强,清风拂山岗;他横由他横,明月照大江。他自狠来他自恶,我自一口真气足。懂得人都懂,不懂的人自悟。
原谅我说这么多,请开始正题Reactor模式
1. Reactor模式介绍
前面有写Reactor模式的文章,那篇文章主要是普及下概念,这次是重点介绍。
Reactor 模式
,是指通过一个或多个输入同时传递给服务处理器的服务请求的事件驱动处理模式
。服务端程序处理传入多路请求,并将它们同步分派给请求对应的处理线程
,Reactor 模式也叫 Dispatcher 模式
。
即I/O多路复用统一监听事件,收到事件后分发(Dispatch 给某进程),是编写高性能网络服务器
的必备技术之一。
Reactor 模式中有 2 个关键组成:
1.Reactor:Reactor在一个单独的线程中运行,负责监听和分发事件,分发给适当的处理程序来对 IO 事件做出反应。
它就像公司的电话接线员,它接听来自客户的电话并将线路转移到适当的联系人;
2.Handlers:处理程序执行 I/O 事件要完成的实际事件,类似于客户想要与之交谈的公司中的实际官员。
Reactor 通过调度适当的处理程序来响应 I/O 事件,处理程序执行非阻塞操作。
根据 Reactor 的数量和处理资源池线程的数量不同,有 3 种典型的实现:
1.单 Reactor 单线程
2.单 Reactor 多线程
3.主从 Reactor 多线程
下面详细介绍这 3 种实现方式。
2. 单 Reactor 单线程
其中,Select是前面 I/O 复用模型介绍的标准网络编程API,可以实现应用程序通过一个阻塞对象监听多路连接请求,其他方案示意图类似。
方案说明:
1.Reactor 对象通过 Select 监控客户端请求事件,收到事件后通过 Dispatch 进行分发;
2.如果是建立连接请求事件,则由 Acceptor 通过 Accept 处理连接请求,然后创建一个 Handler 对象处理连接完成后的后续业务处理;
3.如果不是建立连接事件,则 Reactor 会分发调用连接对应的 Handler 来响应;
4.Handler 会完成 Read→业务处理→Send 的完整业务流程。
优点:模型简单,没有多线程、进程通信、竞争的问题,全部都在一个线程中完成(Reactor单线程)。
缺点:性能问题,只有一个线程,无法完全发挥多核 CPU 的性能。Handler 在处理某个连接上的业务时,整个进程无法处理其他连接事件,很容易导致性能瓶颈。
3. 单 Reactor 多线程
方案说明:
1.Reactor 对象通过 Select 监控客户端请求事件,收到事件后通过 Dispatch 进行分发;
2.如果是建立连接请求事件,则由 Acceptor 通过 Accept 处理连接请求,
然后创建一个 Handler 对象处理连接完成后续的各种事件;
3.如果不是建立连接事件,则 Reactor 会分发调用连接对应的 Handler 来响应;
4.Handler 只负责响应事件,不做具体业务处理,通过 Read 读取数据后,
会分发给后面的 Worker 线程池进行业务处理;
5.Worker 线程池会分配独立的线程完成真正的业务处理,最后将响应结果发给 Handler 进行处理;
6.Handler 收到响应结果后通过 Send 将响应结果返回给 Client。
优点:可以充分利用多核 CPU 的处理能力。
缺点:多线程数据共享和访问比较复杂;Reactor 承担所有事件的监听和响应,在单线程中运行,高并发场景下容易成为性能瓶颈。
4. 主从 Reactor 多线程
针对单 Reactor 多线程模型中,Reactor 在单线程中运行,高并发场景下容易成为性能瓶颈,可以让 Reactor 在多线程中运行。
方案说明:
1.Reactor 主线程 MainReactor 对象通过 Select 监控建立连接事件,
收到事件后通过 Acceptor 接收,处理建立连接事件;
2.Acceptor 处理建立连接事件后,MainReactor 将连接分配 Reactor 子线程给 SubReactor 进行处理;
3.SubReactor 将连接加入连接队列进行监听,并创建一个 Handler 用于处理各种连接事件;
4.当有新的事件发生时,SubReactor 会调用连接对应的 Handler 进行响应;
5.Handler 通过 Read 读取数据后,会分发给后面的 Worker 线程池进行业务处理;
6.Worker 线程池会分配独立的线程完成真正的业务处理,最后将响应结果发给 Handler 进行处理;
7.Handler 收到响应结果后通过 Send 将响应结果返回给 Client。
优点:
1.父线程与子线程的数据交互简单职责明确,父线程只需要接收新连接,子线程完成后续的业务处理。
2.父线程与子线程的数据交互简单,Reactor 主线程只需要把新连接传给子线程,子线程无需返回数据。
缺点:代码写起来太复杂了:即编程复杂度较高。
5. Reactor小结
3种模式可以用个比喻来理解:(餐厅常常雇佣接待员负责迎接顾客,当顾客入坐后,侍应生专门为这张桌子服务)
单 Reactor 单线程,接待员和侍应生是同一个人,全程为顾客服务;
单 Reactor 多线程,1 个接待员,多个侍应生,接待员只负责接待;
主从 Reactor 多线程,多个接待员,多个侍应生。
Reactor 模式具有如下的优点:
响应快,不必为单个同步时间所阻塞,虽然 Reactor 本身依然是同步的;
编程相对简单,可以最大程度的避免复杂的多线程及同步问题,并且避免了多线程/进程的切换开销;
可扩展性,可以方便的通过增加 Reactor 实例个数来充分利用 CPU 资源;
可复用性,Reactor 模型本身与具体事件处理逻辑无关,具有很高的复用性。
6. Nginx利用的就是主从Reactor模式
但它和主从reactor模式又有一定的区别,区别主要就是这个master进程,这个master进程不同于一般的主从式reactor(一般的主从式reactor设计会是主reactor负责将连接accept下来,然后再将连接fd挂载到子reactor中),这个master进程的主要任务就是监听信号的,也就是对nginx的一些命令做处理,然后再将这些处理通过sockerpair()或者信号等方式通知给worker进程,master进程同时监控worker进程的运行状态,当worker进程退出后(异常情况下),会自动重新启动新的worker进程。同时,这个master进程负责listen这个整个服务器的监听fd,然后worker进程通过竞争accept_mutex互斥锁来将连接从全连接队列里取出来,然后进行后续的事件循环处理。
也就是说除了Master与主从Reactor中的主线程Reactor不同以外,Worker的处理流程和子线程Reactor的处理流程几乎一摸一样
,用陈硕老师的话来说就是reactors in process
。
7. 小结
知道了Reactor模式之后再回头想想看看哪些你了解的服务或者中间件使用了此模式,要学会触类旁通才能更胜一筹,进而成为高手。这个模式还有一个最精妙的地方在于把复杂的问题通过"中间层"的方式简单化
。计算机界不是有句老话:“凡是服务不能通过现有常规技术手段解决的,就加一个中间层来解决”。此话真的一语点醒梦中人,Reactor主从模式把频繁的accept过程,其他fd的并发IO处理过程以及业务处理逻辑部分分层,通过加层的方式让整个模式快速而高效的运行起来,这就是智慧,人类的智慧。