【反向代理服务器】Nginx的基础架构

330 阅读10分钟

在进入Nginx的结构分析之前,首先介绍一下一个良好的Web服务器需要考虑哪些因素。这样更加有利于我们深入理解Nginx优秀的架构设计。

1 Web服务器设计中的关键约束

1.1 性能

1 网络性能

是指在不同负载下,Web服务在网络通信上的吞吐量。网络性能受制于带宽(指在特定的网络连接上可以达到的最大吞吐量)。

2 单次请求的延迟性

针对一个用户而言,指服务器初次接收到一个用户请求直至返回响应之间持续的时间。

3 网络效率

使用的网络效率,常用的提高网络效率的方法包括使用长连接、使用压缩算法。

1.2 可伸缩性

是指架构可以通过添加组件来提升服务,或者允许组件之间具有交互功能。一般可以通过简化组件、降低组件之间的耦合度、将服务分散到许多组件等方法来改善可伸缩性。

1.3 简单性

是指组件的简单程度。一般我们采用分离关注点原则来设计组件,对于整体结构来说通常使用通用性原则。

1.4 可修改性

简单来讲,可修改性就是在当前架构下对于系统功能做出修改的难易程度。

1 可进化性

表示在修改一个组件时对其他组件产生负面影响的程度。

2 可扩展性

表示将一个新的功能添加到系统中且不影响其他功能的能力。

3 可定制性

是指可以临时性的重新规定一个组件或其他结构元素的特性,从而提供一种常规服务的能力。

4 可配置性

在Web服务部署后,通过对服务提供的配置文件进行修改来提供不同的功能。

5 可重用性

指的是一个应用中的功能组件在不被修改的情况下,可以在其他应用中重用程度。

1.5 可见性

在Web服务器中,可见性通常是指一些关键组件的运行情况可以被监控的程度,如服务中正在交互的网络连接数、缓存的使用情况等。

1.6 可移植性

指服务可以跨平台运行。

1.7 可靠性

可靠性可以看做是在服务出现部分故障时,一个架构容易受到系统层面故障影响的程度。

2 Nginx的架构设计

Nginx的设计格外重视以上7个关键点,Nginx采用了优秀的模块化设计思想。

2.1 模块化设计

Nginx底层有个基本的接口ngx_module_t,所有的模块都遵循着这个接口的规范。ngx_module_t接口有一个type成员,它指明了Nginx允许在设计
模块时定义模块类型。Nginx最底层的模块时配置类型模块(即NGX_CONF_MODULE),它指导着所有模块以配置项为核心来提供功能。Nginx包括五大类型的模块:核心模块、配置模块、事件模块、HTTP模块、mail模块

2.2 事件驱动架构

所谓事件驱动架构是指由一些事件发生源来产生事件,由一个或多个事件收集器来收集、分发事件(收集、分发事件由Nginx的事件模块完成),然后许多事件处理器会注册自己感兴趣的事件,同时会“消费”这些事件。

服务器区别
传统的Apache使用进程或线程作为事件消费者
Nginx事件消费者只能是某个模块,只有事件收集、分发器才能占用进程资源

Nginx处理事件的简单模型如下图所示:图中列出的5个不同的事件,在事件收集、分发者进程的一次处理过程中,这5个事件按照顺序被收集后,将开始使用当前进程分发事件,从而调用相应的事件消费者模块来处理事件。
在这里插入图片描述

2.3 请求的多阶段异步处理

正因Nginx的事件驱动架构,所以才让请求的多阶段异步处理成为现实。

请求的多阶段异步处理就是把一个请求的处理过程按照事件的触发方式划分为多个阶段,每个阶段都可以由事件收集、分发器来触发。

2.4 管理进程、多工作进程设计

Nginx采用一个master管理进程、多个worker工作进程的设计方式,如下图所示。
在这里插入图片描述
该设计的优点在于:多核系统的并发处理能力;多个worker工作进程间通过进程通信来实现负载均衡,也就是说一个请求到来时更容易被分配到负载较轻的worker工作进程中处理、管理进程会负责监控工作进程的状态并负责管理其行为。

2.5 内存池的设计

为了避免出现内存碎片、减少向操作系统申请内存的次数,Nginx设计了简单的内存池:把多次向系统申请内存的操作整合成一次,这大大减少了CPU资源的消耗,同时减少了内存碎片。

3 Nginx启动时框架的处理流程

首先启动流程见如下的流程图:
在这里插入图片描述
接下来对关键步骤进行说明:
1、上图第1步,Nginx是以配置文件作为核心提供服务的,所以第1步最主要的就是确定配置文件nginx.conf的路径。
2、上图第2步,Nginx在不重启服务升级时(也就是所谓的平滑升级),它会不重启master进程而启动新版本的Nginx程序。此时,旧版本的master进程会通过execve系统调用,先fork出子进程再调用exec来运行新程序,进而启动新版本的master进程。
3、上图第3步,调用所有核心模块的create_conf方法,也就是说需要所有的核心模块开始构造用于存储配置项的结构体
4、上图第4步,遍历nginx.conf中的所有配置项并调用配置模块提供的解析配置项方法。
5、上图第5步,调用所有NGINX_CORE_MODULE核心模块的init_conf方法,让所有核心模块在解析完配置项后可以做综合性处理
6、上图第9步,如果nginx.conf中配置为单进程工作模式,这时将会调用ngx_single_process_cycle方法进入单进程工作模式。
7、上图第10步,在单进程工作模式下,调用所有模块的init_process方法,单进程工作模式的启动工作至此全部完成,将进入正常的工作模式。
8、上图第11~16步,进入master、worker工作模式。在启动worker子进程、cache manage子进程、cache loader子进程后就开始进入工作状态。

4 worker进程是如何工作的

首先列出Nginx中的信号及对应的全局标志位变量的含义:

信号对应进程中的全局标志位变量意义
QUITngx_quit优雅的关闭进程
TERM或INTngx_terminate强制关闭进程
USR1ngx_reopen重新打开所有文件
WINCHngx_debug_quit暂无实际意义

另外还有ngx_exiting标志位,仅由方法ngx_worker_process_cycle在退出时作为标志位使用。

worker进程工作流程如下:

  1. 首先检查ngx_exiting标志位,如果为1则进程退出,否则开始检查并分发、处理事件;
  2. 然后判断ngx_terminate标志位是否为1,若是则强制结束进程,否则判断ngx_quit位是否为1,若是则优雅的关闭进程,同时关闭所有监听句柄并设置ngx_exiting标志,否则进入下一步;
  3. 其次,判断ngx_reopen标志位是否为1,是的话重新打开所有文件,回到第一步继续判断ngx_exiting。

5 master进程是如何工作的

首先需要明确master进程是不需要处理网络事件的,它无需负责业务的执行,只会通过管理worker子进程来实现重启服务、平滑升级等功能。同样的列出master进程中各种标志位的含义:

信号对应进程中的全局标志位变量意义
QUITngx_quit优雅的关闭整个服务
TERM或INTngx_terminate强制关闭整个服务
USR1ngx_reopen重新打开服务中的所有文件
WINCHngx_noaccept所有子进程不再接受处理新的连接,相对于对所有子进程发送QUIT信号量
USR2ngx_change_binary平滑升级到新版本的Nginx程序
HUPngx_reconfigure重读配置文件并使服务对新配置项生效
CHLDngx_reap有子进程意外结束,这时需要监控所有的子进程,也就是ngx_reap_children方法所负责的工作

那么master进程如何启动一个子进程呢?

很简单,通过fork系统调用即可实现。然后会从ngx_processes数组中选择一个还未使用的ngx_process_t元素存储这个子进程的相关信息。

既然ngx_processes数组保存了所有子进程的信息,那么这些进程的状态是如何改变的呢?

依靠信号!例如当若干个子进程意外退出时,master父进程会接收到Linux内核发来的CHLD信号,由于Nginx服务器的ngx_signal_handler方法做出处理:将sig_reap标志位置为1,调用ngx_process_get_status方法修改ngx_processes数组中所有子进程的状态(通过waitpid系统调用得到意外结束的子进程的ID,然后遍历ngx_processes数组找到该子进程ID对应的ngx_process_t结构体,将其exited标志位置1)。

master进程的工作流程如下:

  1. 首先判断ngx_reap标志位。如果为1则表示监控所有的子进程:即通过ngx_reap_children方法遍历ngx_processes数组,检查每个子进程的状态;如果为0则进入下一步;
  2. 然后判断live标志位。如果live标志位为0(所有子进程已经退出)、ngx_terminate标志位为1或者ngx_quit标志位为1时,则开始退出master进程,同时会删除存储进程号的pid文件、关闭进程中打开的监听接口、销毁内存池;否则进入下一步;
  3. 其次判断ngx_terminate标志位。如果为1则向所有子进程发送信号TERM,强制子进程退出,然后跳到第1步挂起进程等待再次激活信号;否则执行下一步;
  4. 判断ngx_quit标志位。如果为1,则向所有子进程发送QUIT信号优雅的退出服务,同时关闭所有监听的接口,同样的跳到第1步挂起进程等待信号激活。否则执行下一步。
  5. 判断ngx_reconfigure标志位。如果为1则表示需要重新读取配置文件。注意Nginx不会让原先的worker等子进程重新读取,它的策略是重新初始化结构体ngx_cycle_t,让它来读取新的配置文件并拉起新的worker子进程,销毁旧有进程。如果为0则进入下一步;
  6. 检查ngx_restart标志位,如果为1则拉起新的worker子进程,同时将ngx_restart置0,然后根据缓存模块情况决策是启动cache manager还是cache loader进程,并将live标志位置1。否则进入下一步。
  7. 检查ngx_reopen标志位。如果为1则重新打开所有文件同时将该位置0,并向所有子进程发送USR1信号,通知它们重新打开所有文件。否则进入下一步;
  8. 接着检查ngx_change_binary标志位。如果为1则表示需要平滑升级Nginx,这时将调用ngx_exec_new_binary方法用新的子进程启动新版本的Nginx程序,同时将ngx_change_binary标志位置0;
  9. 最后需要检查的是ngx_noaccept标志位。如果为1则向所有的子进程发送QUIT信号,要求它们优雅的关闭服务同时将ngx_noaccept位置0,ngx_noaccepting置1(表示停止接收新的连接)、如果为0则继续第1步进行下一个循环。

6 小结

本机侧重介绍了Nginx的基础架构,介绍了Nginx框架如何启动、初始化、加载各Nginx模块的代码,以及worker进程和master进程如何在工作中循环运行。事实上这些机制都是基于Nginx的核心数据结构——ngx_cycle_t。