8.2.2 事件驱动架构

所谓事件驱动架构,简单来说,就是由一些事件发生源来产生事件,由一个或者多个事件收集器来收集、分发事件,然后许多事件处理器会注册自己感兴趣的事件,同时会“消费”这些事件。

对于Nginx这个Web服务器而言,一般会由网卡、磁盘产生事件,而8.2.1节中提到的事件模块将负责事件的收集、分发操作,而所有的模块都可能是事件消费者,它们首先需要向事件模块注册感兴趣的事件类型,这样,在有事件产生时,事件模块会把事件分发到相应的模块中进行处理。

Nginx采用完全的事件驱动架构来处理业务,这与传统的Web服务器(如Apache)是不同的。对于传统Web服务器而言,采用的所谓事件驱动往往局限在TCP连接建立、关闭事件上,一个连接建立以后,在其关闭之前的所有操作都不再是事件驱动,这时会退化成按序执行每个操作的批处理模式,这样每个请求在连接建立后都将始终占用着系统资源,直到连接关闭才会释放资源。要知道,这段时间可能会非常长,从1毫秒到1分钟都有可能,而且这段时间内占用着内存、CPU等资源也许并没有意义,整个事件消费进程只是在等待某个条件而已,造成了服务器资源的极大浪费,影响了系统可以处理的并发连接数。如图8-3所示,这种传统Web服务器往往把一个进程或线程作为事件消费者,当一个请求产生的事件被该进程处理时,直到这个请求处理结束时进程资源都将被这一个请求所占用。

8.2.2 事件驱动架构 - 图1

图 8-3 传统Web服务器处理事件的简单模型(椭圆代表数据结构,矩形代表进程)

Nginx则不然,它不会使用进程或线程来作为事件消费者,所谓的事件消费者只能是某个模块(在这里没有进程的概念)。只有事件收集、分发器才有资格占用进程资源,它们会在分发某个事件时调用事件消费模块使用当前占用的进程资源,如图8-4所示。

8.2.2 事件驱动架构 - 图2

图 8-4 Nginx处理事件的简单模型

图8-4中列出了5个不同的事件,在事件收集、分发者进程的一次处理过程中,这5个事件按照顺序被收集后,将开始使用当前进程分发事件,从而调用相应的事件消费者模块来处理事件。当然,这种分发、调用也是有序的。

从上面的内容可以看出传统Web服务器与Nginx间的重要差别:前者是每个事件消费者独占一个进程资源,后者的事件消费者只是被事件分发者进程短期调用而已。这种设计使得网络性能、用户感知的请求时延(延时性)都得到了提升,每个用户的请求所产生的事件会及时响应,整个服务器的网络吞吐量都会由于事件的及时响应而增大。但这也会带来一个重要的弊端,即每个事件消费者都不能有阻塞行为,否则将会由于长时间占用事件分发者进程而导致其他事件得不到及时响应。尤其是每个事件消费者不可以让进程转变为休眠状态或等待状态,如在等待一个信号量条件的满足时会使进程进入休眠状态。这加大了事件消费程序的开发者的编程难度,因此,这也导致了Nginx的模块开发相对于Apache来说复杂不少(上文已经提到过)。