一、事件驱动模型
事件驱动模型,简单来说就是由一些事件的发生源来产生事件,事件收集器来收集、分发事件,然后由事件处理器来处理这些事件(事件处理器要事先在事件收集器里注册自己想/可以处理的事件)。
对于Nginx服务器而言,一般由网卡、磁盘产生事件,Nginx中的事件模块将负责事件的收集、分发操作;而所有的模块都可能是事件消费者,它们首先需要向事件模块注册感兴趣的事件类型,这样在有事件产生时,事件模块会把事件分发到相应的模块中进行处理。
对于传统的web服务器(如Apache)而言,采用的所谓事件驱动往往局限在TCP连接建立、关闭事件上,一个连接建立以后,在其关闭之前的所有操作都不再是事件驱动,这时会退化成顺序执行每个操作的批处理模式,这样每个请求在连接建立后都将始终占用着系统资源,直到关闭才会释放资源。
这种请求占用着服务器资源等待处理的模式会造成服务器资源极大的浪费。如下图所示,传统的web服务器往往把一个进程/线程作为时间消费者,当一个请求产生的事件被该进程处理时,直到这个请求处理结束,进程资源都将被这一请求占用着。比较典型的例子如apache同步阻塞的多进程模式就是这样的。
传统web服务器处理事件的简单模型(矩阵标示进程):
Nginx采用的事件驱动结构处理业务的方式与传统的web服务器是不同的。它使用不同进程或者线程来作为事件消费者,所谓的事件消费者只能是某个模块。只有事件收集、分发器才有资格占用进程资源,它们会在分发某个事件时调用事件消费模块使用当前占用的进程资源。
如下图所示,列出了5个不同的事件,在事件收集、分发者进程的一次处理中,这5个事件按顺序被收集之后,将开始使用当前进程分发事件,从而调用相应的事件消费者来处理事件。当然,这种分发、调用也是有序的。
由上图可知,处理请求事件时,Nginx的事件消费者只是被事件分发者进程短期调用而已,这种设计使得网络性能、用户感知的请求时延都得到了提升,每个用户的请求所产生的事件会及时响应,整个服务器的吞吐量都会由于事件的及时响应而增大。
当然,这也带来一定的要求,即每个事件消费者都不能有阻塞行为,否则将会由于长时间占用事件分发者进程而导致其他事件得不到及时响应,Nginx的非阻塞特性就是由于它的模块都是满足这个要求的。
二、请求的多阶段异步处理
多阶段异步处理请求与事件驱动架构是密切相关的,也就是说,请求的多阶段异步处理只能基于事件驱动架构实现。
多阶段异步处理就是把一个请求的处理过程按照事件触发的方式划分为多个阶段,每个阶段都可以由事件收集、分发器来触发。
处理获取静态文件的http请求时切分的阶段及各阶段的触发事件如下所示:
阶段意义 |
触发事件 |
建立TCP连接 |
接收到TCP中的SYN包 |
开始接收用户请求 |
接收到TCP中的ACK包表示连接建立成功 |
接收到用户请求并分析已接收的请求是否完整 |
接收到用户的数据包 |
接收到完整的用户请求后开始处理用户请求 |
接收到用户的数据包 |
由目标静态文件中读取各部分内容(避免长时间阻塞事件分发者进程)并直接发送给用户 |
接收到用户的数据包:或者接收到TCP中的ACK包表示用户已接收到上次发送的数据包,TCP滑动窗口向前滑动。 |
对于非keep-alive请求,在发送完静态文件之后主动关闭连接 |
接收到TCP中的ACK包表示用户已接收到之前发送的所有数据 |
由于用户关闭连接而结束请求 |
接收到TCP中的FIN包 |
这个例子中,该请求大致分为7个阶段,这些阶段是可以重复发生的,因此一个下载资源的请求可能会由于请求数据过大,网速不稳定等因素而被分解为成百上千个上图所列出的阶段。
异步处理和多阶段是相辅相成的,只有把请求分为多个阶段,才有所谓的异步处理。当一个事件被分发到事件消费者中进行处理时,事件消费者处理完这个事件只相当于处理完1个请求的阶段。
什么时候可以处理下一阶段呢?这只能等待内核的通知,即当下一次事件出现时,epoll等事件分发器将会获取到通知,然后去调用事件消费者进行处理。