深入理解Nginx(二)—— 架构设计

Posted by 皮皮潘 on 01-10,2022

模块化架构

Nginx的架构基础就是高度模块化的设计,在Nginx中除了少量的核心代码,其他皆为模块,模块化架构具有如下特点:

  1. 高度抽象的模块接口nginx_module_t:每一个模块都要实现nginx_module_t模块接口,该接口中定义了所有模块共性的东西:1. Commands集合——配置文件中的配置项,在Nginx启动以后,只有读取到了对应的配置项才会激活对应的模块 2. 生命周期回调钩子——init_master, init_module, init_process, init_thread, exit_thread, exit_process, exit_master
  2. 多层次、多类别的模块设计:每一个模块除了实现nginx_module_t接口之外,还需要定义对应的类型并实现该类型模块抽象出的方法,目前Nginx共有五大类型的模块——核心模块、配置模块、事件模块、HTTP模块以及mail模块,其层次如下:
    1. Nginx定义了一种最基础类型的模块——核心模块,它实现了nginx_module_t接口的同时对应的类型为NGX_CORE_MODULE
    2. 其他核心模块本身又再次定义了对应的之前提到的其他四大类型,例如nginx_events_module是一个核心模块,它定义了NGX_EVENT_MODULE模块类型——nginx_event_module_t(_t代表了这个结构是一个类型),所有事件类型的模块都要定义为NGX_EVENT_MODULE类型并实现对应的方法,同时nginx_events_module负责管理所有事件类型的模块
    3. 每个模块类型,除了在核心模块中有一个模块作为“代言人”,在所有的实现模块中还会有一个模块作为核心业务与管理功能的模块,例如,事件模块由它的代言人——nginx_events_module这一核心模块定义,但所有的事件模块的加载功能都是由nginx_event_core_module这一事件模块负责

由于Nginx框架本身是非模块化的,如果使用单一层次、单一类别的模块设计就会导致Nginx虽然保证了极强的灵活性,但同时也带来了过高的代码复杂性(不同类型的模块的生命周期不同,如果继续使用单一类别表示,则需要各种强制转换,或者使得模块接口过于复杂)

Nginx通过定义核心模块的概念,并且将五类核心模块内嵌入Nginx框架本身,可以使得Nginx框架仅仅需要控制核心模块的生命周期,再由核心模块控制各自同类型模块的生命周期,从而降低了Nginx框架的复杂性,同时也保留了各类型模块的灵活性

在这里我们可以学到另外一点——不要一昧地追求灵活性,比如使用大量的设计模式,而使得代码复杂性提升,一切都需要Trade Off,适合的才是最好的
Nginx模块化架构.png

事件驱动架构

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

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

Nginx采用完全的事件驱动架构来处理业务(同Netty相似)与传统的Web服务器(如Apache)是不同的。对于传统的Web服务器而言,采用的所谓事件驱动往往局限在TCP连接建立、关闭事件上,一个连接建立以后,在其关闭之前的所有操作都不再是事件驱动的,会退化成按序执行每个操作,每个请求在连接建立后都将始终占用着系统资源,直到连接关闭才会释放对应的资源,也即在IO模型上是事件驱动的,但是在业务模型上是每个连接对应一个线程的

Nginx则不然,它不会使用线程来作为事件消费者,事件消费者只能是某个模块,只有事件收集、分发器才有资格占用进程资源并且通过调用模块的方式来处理事件

从中可以看出传统Web服务器和Nginx的重要差别:前者是每个事件消费者独占一个线程资源,后者的事件消费者永远是固定的事件分发器并且根据事件不同调用不同的模块而不会产生新的线程

通过使用事件驱动架构,可以大大提升Nginx的性能,但是这也带来了一个要求:事件消费者(模块)不能有阻塞行为,否则将会由于长时间占用事件分发者进程而导致其他事件得不到及时的响应,因为只有一个事件分发器以及一个进程,因此Nginx模块的开发难度大大高于传统服务器的开发难度

单管理进程、多工作进程架构

Nginx采用了一个master管理进程,多个worker工作进程的设计方式,这种设计可以带来如下优点:

  1. 利用多核系统的并发处理能力
  2. 负载均衡:多个worker工作进程通过IPC来实现负载均衡
  3. 轻量级master管理进程:管理进程不会占用多少系统资源,它只是用来启动、停止、监控或使用其他行为来控制worker进程。首先,这提高了系统的可靠性,当工作进程出现问题,master进程可以启动新的工作进程。其次,master进程可以支持Nginx服务运行中的程序升级、滚动更新的操作,从而实现动态可扩展性、动态定制性以及动态可进化性

启动时框架处理流程设计

Image.png
Nginx启动时流程如上图所示,其中主要注意如下几点:

  1. 所有模块都存在一个全局的ngx_modules数组中
  2. 在调用每个模块的init_process之前,所有的配置都已经读取好了
  3. 由于第6步和第7步都是在fork出子进程之前执行的,因此所有的worker进程都共享文件句柄、配置信息以及端口监听(多个进程可以监听同一个端口,当端口有连接事件到达时,哪个进程调用了accept方法,则哪个进程负责该连接,内核会解决对应的并发冲突)
  4. 在调用了所有模块的init_process方法之后,会最终调用ngx_worker_process_cycele方法从而进入处理循环(类似Netty):
    1. 等待事件发生
    2. 处理事件
    3. 处理定时事件