nginx之回头补课

    最近买了苗泽的那本《Nginx高性能Web服务器详解》,准备系统地学习下Nginx相关的知识。于是在二次科普的过程中发现之前对Nginx的一些理解和认识被刷新细化了。特此发文总结补课。

一. Web请求处理机制

1.1 多进程方式

    传统的多进程方式是指:每接到一个客户端请求,就由服务器主进程生成一个子进程来负责与客户端建立连接并进行交互。连接断开子进程即销毁。
    优点:
    ①设计实现比较简单,许多细节被OS管理;
    ②由于进程间独立,资源独立分配使用,运行时出错风险小,稳定性好;
    ③OS的进程资源回收机制比较完备,垃圾不会积累。
    缺点:
    动态生成子进程涉及内存复制等操作,资源、时间开销比较大。发生大并发时对OS资源造成较大压力,将导致OS性能下降。
    Apache最初的设计架构就采用了多进程方式,但是为了适应大并发的要求,对多进程方式进行了改进。
    多进程方式的短板在于动态生成进程时的开销,于是Apache采用了“预生成进程”的工作方式。顾名思义,在还没有接到客户端请求的空闲时间,预先生成若干工作进程。当接到客户端请求,主进程分配子进程与客户端进行交互。连接断开后子进程保留,做开销很小的适当清理后被主进程管理起来等待下一个客户端请求。“预生成进程”在一定程度上缓解了大并发时Web服务器对OS资源造成的压力。

1.2 多线程方式

    总体来说多线程方式与多进程方式十分类似,它们的区别在于,Web服务器接到客户端请求后,多线程方式下,主进程将动态派生一个工作线程来与客户端进行交互。
    同样是动态生成,OS产生一个线程的开销要远远小于产生一个进程的开销(在实验验证之前,我对此表示怀疑)。
    优点:
    OS资源压力小于多进程方式。
    缺点:
    OS对线程管理的支持甚少,线程调度规则、内存空间管理、线程资源管理都需要开发者自己考虑,增大了开发难度和出错风险。错误也容易在Web服务器持续运行的过程中发生堆积。
    IIS服务器采用了多线程方式处理Web请求,它的稳定性相对不错。但是IIS Web服务器管理员仍然倾向于定期检查和重启服务器,以消除不可预知的错误堆积或其他隐患。

二. 异步/同步、阻塞/非阻塞

    异步与非阻塞,同步与阻塞的概念常被人混淆。事实上,异步/同步与阻塞/非阻塞发生在网络通信的不同阶段,前者发生在客户端与服务端的通信阶段,与请求处理过程无关;后者发生在请求处理阶段。

2.1 异步/同步

    在网络通信中同步机制指:发送方发送请求后,等到接收方发回响应后才发出下一个请求。发送端与接收端步调一致。异步机制:发送方是否发送请求不受接收方是否作出响应的影响。在接收方,发送方的请求形成队列,依次处理完成后依次进行响应。

2.2 阻塞/非阻塞

    阻塞与非阻塞的对象,通常是网络套接字Socket,其实质是IO操作。阻塞方式中,IO结果返回前,当前线程从运行状态被挂起,直到IO结果返回,才进入就绪状态等待调度以继续运行;非阻塞方式中,线程不会被挂起,如果当前调用无法马上返回结果,一个失败消息将被立即返回,并继续执行下一个调用。

2.3 同步阻塞方式

    发送方发送请求后一直等待响应;接收方处理请求时的IO操作如果不能马上返回结果则一直等待,返回结果后响应发送方。一个请求周期期间,发送方和接收方不能进行其他工作。比如,超市排队付账时,客户(发送方)向收款员(接收方)付款(发送请求)后需等待找零,期间不能做其他事;收款员要等待收款机返回结果(IO操作)后才能找零(响应请求),期间也只能等待。这种方式实现简单,效率不高。

2.4 同步非阻塞方式

    付款后顾客只能等着,收款员可以先干点给商品打包、玩手机之类的杂事。实际中不使用。

2.5 异步阻塞方式

    付款后顾客可以干点跟后面顾客聊天、玩手机之类的杂事,收款员只能等着。实际中不使用。

2.6 异步非阻塞方式

    发送方发送请求后无需忙等,可以继续其他操作,有响应时再做出对应反应;接收方如果不能马上获得IO结果,将请求挂入请求队列,继续其他工作,返回IO结果时触发一个事件,接收方向发送方作出对应响应。比如,付款后顾客可以干点跟后面顾客聊天、玩手机之类的杂事;收款员完成商品扫码和收款金额输入后可以先干点给商品打包、玩手机之类的杂事,当收款机发出警报声,收款员告诉顾客机器坏了没法收款,当收款机刷新出找零金额,收款员开始找零。

三. Nginx的请求处理机制

    Nginx使用“预生成进程”或“带进程池”的多进程方式作为请求处理方式,使用异步非阻塞方式作为网络通信方式。Nginx服务器在启动时就由主进程fork(分支)若干固定数目的工作进程(工作进程数通常与CPU核心数线性相关,有助于提高CPU利用率),这些工作进程就是Nginx的进程池。使用多进程方式保证大并发情景下不增加OS资源压力,使用异步非阻塞方式保证大并发情景下不降低请求处理能力。

四. Nginx事件驱动模型

4.1 事件驱动模型概述

    事件驱动模型一般由事件收集器、事件发送器和事件处理器三个基本单元组成:
    ①事件收集器:事件收集器一般由一个事件循环程序构成,即无线循环监听各种预设类型的事件;
    ②事件收集器:事件发送器实际上只是一个事件映射集;
    ③时间处理器:通常,事件处理器的并发实现有以下三种方式:为请求创建新进程、为请求创建新线程、将请求放入请求队列。第一种方式OS资源开销大;第二种方式开发者需要自己动手完成线程同步,编码复杂;第三种方式编码逻辑复杂,但大多数Web服务器采用第三种方式,基于第三种方式的事件驱动模型逐渐形成了一系列“事件驱动处理库”,又被称为“多路IO复用方法”。

4.2 select库

    各个版本的Linux和Windows平台都支持select库,接口基本相同,参数含义略有差异。首先它需要创建3个事件描述符集合(事件映射集),分别可以关注并收集Read、Write、Exception事件描述符(事件收集)。其次调用OS核心的select()函数。然后轮询每个事件描述符集中的每个事件描述符,如有事件发生则进行处理。Nginx服务器在编译过程中如未指定其他高性能事件驱动模型库,将自动编译 select 库。

4.3 poll库

    限于Linux平台的基本事件驱动模型。poll与select的区别在于,它只创建一个事件描述符集,而每个事件描述符结构上分别设置Read、Write、Exception事件,轮询时可同时检查三种事件是否发生。同样的,如未指定其他高性能事件驱动模型库,Nginx将自动编译 poll 库。

4.4 epoll

    epoll是公认优秀的高性能事件驱动模型,Linux 2.6及以上版本可以使用它。epoll是对poll的改进,epoll可以看成是event poll,即基于事件的poll。epoll利用OS调用,让内核来创建事件描述符集,然后监听内核的反馈事件。当内核通过一个事件反馈发生事件的描述符列表再处理这些事件。
    epoll库在Linux平台上是高效的(因为Linux内核的高效性),它支持一个进程打开大数目的事件描述符,上限是OS支持的最大打开文件数。同时因为它只处理内核上报的“活跃”描述符,epoll的IO效率不随描述符数目的增加线性下降(类似哈希查找之于遍历查找),即它支持大描述符集。

4.5 rtsig模型

    rtsig即real time signal的缩写,即实时信号。事实上,rtsig调用内核创建一个固定长度上限的事件信号发生队列,等待工作进程依次处理。

4.6 其他事件驱动模型

    kqueue模型,是用于支持BSD系列平台(FreeBSD、OpenBSD、NetBSD、Mac OS X等)的高效事件驱动模型。也是poll的变种,仰仗OS内核支持条件触发(LT即level-triggered即满足条件触发)和边缘触发(ET即edge-triggered,即状态变换触发)。

    /dev/poll模型,是用于支持Unix衍生平台(Solaris、HP/UX、IRIX、Tru64 UNIX),该模型是Sun公司开发Solaris系列平台时提出的事件驱动机制方案,使用了虚拟的/dev/poll设备,开发人员监听设备消息获取事件通知。

    eventport模型,是用于支持Solaris10及以上版本平台的高效事件驱动模型。也是Sun公司开发Solaris是提出的方案,可以有效防止内核崩溃等情况。

4.7 IOCP模型

    苗泽在书中没有提到,但现在Nginx在windows平台下还支持IOCP模型,虽然我对它不甚了解,但圈内提及Windows平台下的大并发网络,一定会提起IOCP模型,估计也是利用内核优化的事件驱动模型。

Fork me on GitHub