Nginx 是一款轻量级的 Web 服务器/反向代理服务器及电子邮件(IMAP/POP3)代理服务器,在 BSD-like 协议下发行。Nginx是为性能而生,从发布以来一直侧重于高性能、高并发、低 CPU 内存消耗;在功能方面:负载均衡、反向代理、访问控制、热部署、高扩展性等特性又十分适合现代的网络架构。更可贵的是配置简单文档丰富,大大降低了学习的门槛。
Web 调度员 Nginx(Nginx应用之一)
当 web 应用发展到一定程度时,单台服务器不足以支撑业务的正常运行,为增大吞吐量往往会使用多台服务器一起提供服务,如何充分利用多台服务器的资源,就需要一个‘调度员’,这个调度员要求能高效的接收并分发请求,知道后端的服务器健康状态,要能方便的扩展和移除,这就是 Nginx 又一常见应用架构,此架构充分利用了 Nginx 的反向代理和负载均衡的优势,Nginx 本身不提供 web 服务,而是在前端接受 Web 请求并分发到后端服务器处理,后端服务器可以是 Apache、Tomcat、IIS 等。
Nginx 的代码是由一个 核心 和一系列的 模块 组成。
核心的功能如下:
- 主要用于提供 WebServer 的基本功能;
- 实现 Web 和 Mail 反向代理的功能;
- 还用于启用网络协议;
- 创建必要的运行时环境以及确保不同的模块之间平滑地进行交互。
大多跟协议相关的功能和应用特有的功能都是由 Nginx 的模块实现的。这些功能模块大致可以分为:事件模块、阶段性处理器、输出过滤器、变量处理器、协议、upstream 和负载均衡几个类别,这些功能模块共同组成了 Nginx 的 http 功能。
其中:
- 事件模块主要用于提供 OS 独立的(不同操作系统的事件机制有所不同)事件通知机制,如 kqueue 或 epoll 等。
- 协议模块则负责实现 Nginx 通过 HTTP、TLS/SSL、SMTP、POP3 以及 IMAP 与对应的客户端建立会话。
在 Nginx 内部,进程间的通信是通过模块的
pipeline
或chain
实现的。
每一个功能或操作都由一个模块来实现。例如:压缩、通过 FastCGI 或 uwsgi 协议与 upstream 服务器通信、以及与 memcached 建立会话等。Nginx 启动后,在 Unix 系统中会以 daemon (守护进程)的方式在后台运行,后台进程包含一个 master 进程和多个 worker 进程(你可以理解为工人和管理员)。这里就主要讲解 Nginx 的多进程模式。
Nginx 处理连接的过程
Nginx 不会为每个连接派生进程或线程,而是由 worker 进程通过监听共享套接字接收新请求,并且使用高效的循环来处理数千个连接。Nginx 不使用仲裁器或分发器来分发连接,这个工作由操作系统内核机制完成。监听套接字在启动时就完成初始化,worker 进程通过这些套接字接收、读取请求和输出响应。
master 进程
当 Nginx 在启动后,会有一个 master
进程和多个 worker
进程。master 进程主要用来管理 worker 进程,master 要做的就是:接收来自外界的信号,向各 worker 进程发送信号,监控 worker 进程的运行状态,当 worker 进程退出后(异常情况下),会自动重新启动新的 worker 进程。
worker 进程
对于基本的网络事件,Nginx 则是放在 worker 进程中来处理。多个 worker 进程之间是对等的,他们同等竞争来自客户端的请求,各进程互相之间是独立的。一个请求,只可能在一个 worker 进程中处理,一个 worker 进程,不可能处理其它进程的请求(一对一)。然而 Nginx 没有专门地仲裁或连接分布的 worker,这项工作是由操作系统内核机制完成的。在启动时,创建一组初始的监听套接字,HTTP 请求和响应之时,worker 连续接收、读取和写入套接字。
我们来看一看一个完整的请求是怎样通过互相的协作来实现的:
(1)首先,每个 worker 进程都是从 master 进程 fork
过来,在 master 进程里面,先建立好需要 listen
的 socket
(listenfd
)之后,然后再 fork
出多个 worker 进程。
(2)所有 worker 进程的 listenfd
会在新连接到来时变得可读,为保证只有一个进程处理该连接,所有 worker 进程会在注册 listenfd
读事件前抢 accept_mutex
,抢到互斥锁的那个进程注册 listenfd
读事件,然后在读事件里调用 accept
接受该连接。
(3)当一个 worker 进程在 accept
这个连接之后,就开始读取请求、解析请求、处理请求。产生数据后,再返回给客户端,最后才断开连接,这样一个完整的请求就是这样的了。
我们可以看到:一个请求,完全由 worker 进程来处理,而且只在一个 worker 进程中处理。
Nginx 采用的是多 Worker 进程加异步非阻塞的处理模型。这种模型的高明之处在于,即使每个 Worker 进程只有一个主线程,它也能高效地处理大量并发请求。关键在于“异步非阻塞”这四个字。
异步非阻塞的工作原理
-
事件驱动:Nginx 的每个 Worker 进程使用事件驱动的方式来处理请求。它通过操作系统提供的高效 I/O 复用机制(如
epoll
、kqueue
等)来监控多个文件描述符(即客户端连接)的状态变化。 -
非阻塞 I/O:在非阻塞 I/O 模型中,当一个 I/O 操作(如读取数据)不能立即完成时,操作系统不会让该操作阻塞(等待)当前线程,而是立即返回一个标记(如
EAGAIN
),表示操作未完成。然后,Nginx 可以继续处理其他请求。 -
事件循环:Nginx 的 Worker 进程会进入一个事件循环,不断地检查哪些请求有事件发生(如有数据可读或可写)。当检测到事件发生时,才去处理相应的请求。这种方式避免了等待某个 I/O 操作完成的时间浪费。
Nginx 真正的魅力在于它的模块,整个应用程序建立在一个模块化系统之上,在编译时,可以对每一个模块进行启用或者禁用,需要什么就定制什么。
对 Nginx 模块的基本原理总结一下,基本就是:在特定地方调用函数。(Nginx 本身支持多种模块,如 HTTP
模块、EVENTS
模块和 MAIL
模块,这里只简单讲述 HTTP 的模块及其中的命令)
下面是配置文件结构图:
Nginx 本身做的工作实际很少,当它接到一个 HTTP
请求时,它仅仅是通过查找配置文件将此次请求映射到一个 location block
,而此 location
中所配置的各个指令则会启动不同的模块去完成工作,因此模块可以看做 Nginx 真正的劳动工作者。
- 通常一个
location
中的指令会涉及一个handler
模块和多个filter
模块(当然,多个location
可以复用同一个模块)。handler
模块负责处理请求,完成响应内容的生成;filter
模块对响应内容进行处理。
因此 Nginx
模块开发分为 handler
开发和 filter
开发(本文不考虑 load-balancer 模块)。
下图展示了一次常规请求和响应的过程。
参考链接