Nginx的架构设计

Nginx架构概述


传统基于进程或线程的模型(Apache就采用这种模型)在处理并发连接时会为每一个连接建立一个单独的进程或线程,且在网络或者输入/输出操作时阻塞。这将导致内存和CPU的大量消耗,因为新起一个单独的进程或线程需要准备新的运行时环境,包括堆和栈内存的分配,以及新的执行上下文,当然,这些也会导致多余的CPU开销。最终,会由于过多的上下文切换而导致服务器性能变差。
反过来,我们来看Nginx的架构设计,总结起来,它是模块化的、基于事件驱动、异步、单线程且非阻塞。
Nginx大量使用多路复用和事件通知,Nginx会创建几个数量有限(比如worker的数量和CPU的核数相同)的worker进程,每个worker进程包含一个高效的Run-loop,来处理多个网络连接,每个worker进程每秒能处理成千的并发连接。

代码结构

worker进程负责维护Run-loop,并且在请求处理的每个阶段执行相应模块代码,Nginx包括很多模块,如事件模块,状态处理模块,协议模块,负载均衡等。Nginx并不支持动态加载模块,模块只能在Nginx编译时进行添加进来。
在接收,处理和管理网络请求时,Nginx会根据操作系统的不同,采用特定的事件模型和磁盘I/O,以达到更高的性能,如LinuxSolaris以及基于BSD的操作系统,采用epollkqueueevent ports
Nginx的架构图如下:

Worker模型

前面提到,Nginx并不会为每一个连接新建一个进程来进行处理,相反,worker会从一个共享的监听套接字中获取新的请求,并在worker管理的Run-loop中处理请求。Nginx启动时,将创建初始的监听套接字,接下来,当worker处理HTTP请求和响应时,会持续的接收、读取、以及写入套接字。
Run-loop是worker的核心,它的主要思想是异步任务处理,实现方式包括模块化、事件通知、回调函数、定时器等。总的原则就是尽可能的非阻塞。
关于worker的数量问题,通常的建议是:

  • CPU密集型:比如处理大量的TCP/IP,SSL,或压缩时,Nginx worker进程的数量应当和CPU核的数量一致。
  • 磁盘I/O密集型:提供文件内容,或者大量的代理,这种情况下,worker进程的数量可以是CPU核数的1.5或2倍。

Nginx进程职责

Nginx运行时会有多个进程,包括一个master进程和多个worker进程,除此之外,还包括一对特定作用的进程,cache loader进程和cache manager进程。所有的进程都是单线程(即只有一个主线程)的,且进程间通信主要使用共享内存的方式。master进程以root用户权限运行,其它进程则以非root方式运行。
master进程主要负责如下的任务:

  • 读取和诊断配置文件
  • 创建、绑定以及关闭套接字
  • 启动、终止和管理worker进程
  • 无须重启即可动态更新配置
  • 平滑升级
  • 重新打开log文件
  • 编译内嵌的Perl脚本

worker进程接收、处理连接请求,提供反向代理和过滤以及其它的功能。
cache loader进程负责检测磁盘的缓存,且向内存数据库提供缓存元数据,cache loader在Nginx准备启动时,以一定的目录结构,遍历缓存内容的元数据,更新共享内存中的相关条目,当准备完成后退出。
cache manager进程主要负责缓存的过期管理和诊断。

Nginx缓存简单介绍

缓存的键和元数据存储在共享内存段中,cache loadercache managerworker进程可以对其进行访问。
将内容存入缓存的过程如下:
当Nginx从上游服务器读取响应时,响应内容首先会被写入到一个缓存文件中,当请求处理完成后,重命名缓存文件,并将它移到缓存的目录中。

Nginx配置


Nginx的主配置文件名默认为nginx.conf,配置文件由master进程读取和诊断,读取完之后以一定的形式保存在内存中,当worker进程从master进程中fork时(fork之后,子进程是父进程的副本,子进程获得父进程数据空间、堆和栈的拷贝),worker进程就能够访问到这些配置数据。