Nginx的架构设计
Nginx架构概述
传统基于进程或线程的模型(Apache
就采用这种模型)在处理并发连接时会为每一个连接建立一个单独的进程或线程,且在网络或者输入/输出操作时阻塞。这将导致内存和CPU
的大量消耗,因为新起一个单独的进程或线程需要准备新的运行时环境,包括堆和栈内存的分配,以及新的执行上下文,当然,这些也会导致多余的CPU
开销。最终,会由于过多的上下文切换而导致服务器性能变差。
反过来,我们来看Nginx的架构设计,总结起来,它是模块化的、基于事件驱动、异步、单线程且非阻塞。
Nginx大量使用多路复用和事件通知,Nginx会创建几个数量有限(比如worker
的数量和CPU
的核数相同)的worker
进程,每个worker
进程包含一个高效的Run-loop,来处理多个网络连接,每个worker
进程每秒能处理成千的并发连接。
代码结构
worker
进程负责维护Run-loop,并且在请求处理的每个阶段执行相应模块代码,Nginx包括很多模块,如事件模块,状态处理模块,协议模块,负载均衡等。Nginx并不支持动态加载模块,模块只能在Nginx编译时进行添加进来。
在接收,处理和管理网络请求时,Nginx会根据操作系统的不同,采用特定的事件模型和磁盘I/O
,以达到更高的性能,如Linux
,Solaris
以及基于BSD
的操作系统,采用epoll
,kqueue
,event 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 loader
、cache manager
、worker
进程可以对其进行访问。
将内容存入缓存的过程如下:
当Nginx从上游服务器读取响应时,响应内容首先会被写入到一个缓存文件中,当请求处理完成后,重命名缓存文件,并将它移到缓存的目录中。
Nginx配置
Nginx的主配置文件名默认为nginx.conf
,配置文件由master
进程读取和诊断,读取完之后以一定的形式保存在内存中,当worker
进程从master
进程中fork
时(fork
之后,子进程是父进程的副本,子进程获得父进程数据空间、堆和栈的拷贝),worker
进程就能够访问到这些配置数据。