第一章【基石与起源】—— NGINX 架构设计与工作模式

第三节目录

    • 一、模块体系
    • 二、进程模型
      • [(1)Master 进程模型](#(1)Master 进程模型)
      • [(2)Worker 进程模型](#(2)Worker 进程模型)
    • 三、事件模型
    • [四、NGINX 缓存机制](#四、NGINX 缓存机制)
    • [五、NGINX 内存与资源管理](#五、NGINX 内存与资源管理)

一、模块体系

nginx 的核心设计思想就是模块化,它内部由核心模块和其他功能模块组成。这种结构使 nginx 具有高性能、高可靠性、高扩展性和灵活性的特点。

核心模块:主要负责维护一个运行循环,在这个循环中执行网络请求处理的不同阶段,并且在不同处理阶段都会调用相应的功能模块。

功能模块:主要负责网络读写数据、服务器内部操作等。

将它们按照功能划分,可以分成如下细分模块:

  • 核心模块:这是 nginx 运行的基石,负责最基本的进程管理、配置解析和底层框架等,维护 nginx 的整个生命周期和运行环境。例如 ngx_core_module、ngx_conf_module、ngx_error_log_module 等
  • 事件模块:实现了 nginx 事件驱动、非阻塞的 I/O 模型,负责监听和处理网络事件(如连接建立、数据读/写),是 nginx 高性能和高并发能力的直接来源。它独立于操作系统的事件处理框架,不同操作系统事件驱动模块不同。例如 ngx_event_module、ngx_epoll_module(适用于 Linux)、ngx_kqueue_module(适用于 MacOS) 等。
  • Handler 模块:位于 HTTP 处理链的核心位置,负责根据请求 url 执行具体的业务逻辑,它们种类最丰富,负责生成响应内容,如读取静态文件、转发请求到上游服务器、内部重定向等。如 ngx_http_static_module、ngx_http_fastcgi_module 等。
  • Filter 模块:工作在 handler 处理模块之后,负责对阶段处理模块生成的响应头和响应体进行修改、转换或压缩,即对即将将发送给客户端的响应数据进行优化或格式化,同时它也是链式运行的,可以多个过滤模块顺序执行。例如 ngx_http_gzip_filter_module、ngx_http_handers_filter_module、ngx_http_chunked_filter_module 等。
  • 负载均衡模块:与反向代理配合使用,专门负责选择 upstream 服务器组中的最佳后端服务器,提高系统的伸缩性和可靠性,确保后端服务健康。例如 ngx_http_upstream_module、ngx_http_upstream_least_conn_module 等。
  • 其他辅助模块:这类模块功能多样,主要提供各种辅助、安全、缓存和配置增强功能。例如 ngx_http_access_module(安全与访问控制)、ngx_http_stub_status_module(nginx 状态监控)、ngx_http_log_module(日志访问)、ngx_http_map_module(变量映射)等。

nginx 支持动态加载模块,你可以选择性的将某些模块编译为动态链接库(.so 文件),通过动态模块进行加载,而无需将模块编译到 nginx 可执行文件中。

  • 前一节提到过,开启 --with-compat 选项将允许 nginx 可执行文件加载未来的动态模块,--add-dynamic-module=xxx 选项可以将模块 xxx 编译为动态模块。
  • 在 nginx.conf 文件最外层中使用 load_module 指令加载外部动态模块(只能在最外层中引入动态模块)
  • 使用动态模块非常方便后期维护,需要升级或替换模块时只需要替换对应的 .so 文件即可,然后重载配置即可。
    nginx 存在一个 njs 模块,支持用户通过 JavaScript 编写模块,这显著降低了用户使用 C 语言开发模块的难度和必要性。(后续的 NJS 篇会学习到)


nginx 模块化协作

二、进程模型

(1)Master 进程模型

master 进程是 nginx 启动后第一个运行的进程,它不负责处理任何客户端请求,而是承担起整个系统的管理、协调和守护职责:

系统初始化

  • 加载配置文件:这一阶段负责读取和校验 nginx.conf 配置文件,如果配置存在语法错误,master 进程会立即报错并退出;
  • 创建运行环境:前一步成功后,master 进程开始初始化日志文件、监听套接字等,确保环境就绪;
  • 加载相应模块:最后如果配置了动态模块,则 master 进程继续加载这些共享对象。

worker 进程管理

  • 创建和控制: master 进程会根据配置(worker_processes 指令),通过 fork() 系统调用创建和启动一组 worker 进程;
  • 进程守护:master 进程持续监控 worker 进程的运行状态,如果某个 worker 进程意外退出,则 master 会立即自动重启一个新的 worker 进程来替换它,确保服务的持续可用性和高可靠性;
  • 信号处理:master 进程是 nginx 接收外部信号(如 kill 命令发送的信号)的唯一入口,负责将这些信号转化为对 worker 进程的相应操作。

接受和分发信号 :master 进程拦截操作系统信号,并根据信号类型执行相应的操作,通常涉及对 worker 进程的控制。如 HUP(平滑重载)、QUIT(平滑退出)、USE1(重开日志文件)、USE2(平滑升级)、INT(快速退出)等。
重载与升级

  • 平滑重载:master 接收到 HUP 信号后,会加载新的配置文件并启动一组新的 worker 进程,再通知旧 worker 进程停止连接并处理完当前任务后平滑退出;
  • 平滑升级:master 能够协调新旧的 nginx 可执行文件,确保在一个新的 nginx 版本启动时,旧的 worker 可以继续服务请求,直到所有连接都迁移到新版本。

总结:master 进程就像一个项目经理,它规划环境、招聘(启动 worker)、监督(守护 worker)、处理外部指令(接受并分发信号),并确保项目的平稳过渡(重载和升级)。

(2)Worker 进程模型

worker 进程是 nginx 进程模型中真正处理客户端请求和网络 I/O 的核心执行者。它们与 master 进程协调工作。worker 进程的数量通常等于或略高于 CPU 核心数,它们共同监听 master 进程初始化好的同一个套接字,所有的 worker 地位平等、独立运行、平等竞争资源。

对于 CPU 密集型任务,worker 进程数量应该和 CPU 核心数量相同;对于 I/O 密集型任务,worker 数量可以略高于 CPU 核心数量(如 1.5~2 倍之间)。总的来说,worker 数量的选择都将根据内存大小、内容类型、硬件配置等综合来选择。
nginx 的精明之处就在于,它将内存用在 "刀刃上",不会去创建新进程或线程来徒耗硬件资源,nginx 只会检查内存等硬件状态,初始化新的上下文并添加到队列中等待异步处理。所以即使在系统负载很极端的情况下,nginx 往往也能游刃有余。

下面为每一个 worker 的工作机制:
监听与接受新连接

  • 惊群问题:每个 worker 进程同时监听同一个端口,当新连接到达时,操作系统可能会唤醒多个 worker 进程,这就是所谓的惊群问题。对于这一问题 nginx 则采用了如 epoll 的 EPOLLEXCLUSIVE 的优化连接来接收,确保只有一个 worker 进程能成功接收并处理该新连接;
  • 独占连接:一旦 worker 进程成功接受一个连接,该连接就完全归属这个 worker 进程,直到连接关闭。

事件循环

  • 等待事件:调用 I/O 多路复用接口(如 epoll、kqueue),阻塞等待网络事件(如数据可读、可写、连接关闭)发生;
  • 事件处理:一旦接口返回有事件发生,worker 进程立即处理这些事件。如如读取请求头、执行模块逻辑、发送响应等;
  • 非阻塞:在处理一个连接的 I/O 操作时,如果操作不能立即完成(如等待后端服务器响应),worker 进程不会阻塞,而是将该连接的状态保存起来,然后返回到事件循环,处理下一个就绪的事件。

请求处理

  • 读取请求:接收客户端发送的请求行、请求头和请求体;
  • 模块加载:按照 nginx 的处理阶段顺序,依次调用配置中注册在该阶段的模块函数。如 rewrite 重写模块、access 认证模块等;
  • Handler 执行:执行负责生成内容的 handler 模块;
  • Filter 过滤:响应内容生成后,经过一系列 filter 模块的处理。如 gzip 压缩、header 注入等;
  • 发送响应:将最终的响应数据发送回客户端。

总结: worker 进程是 nginx 中真正的牛马,它通过事件循环和非阻塞 I/O 机制,高效、独立地完成请求的接收、处理和响应工作。


Master 与 Worker 进程架构

三、事件模型

传统服务器(如 apache 的 prefork 模式)采用 进程/线程阻塞 模型,即每个新连接都需要创建一个新的进程或线程来处理,并且在进行 I/O 操作(如磁盘读写、网络等待)时,该进程或线程会被阻塞,这导致资源消耗大、并发能力受限。

而 nginx 采用异步非阻塞 I/O,当 worker 进程发起一个 I/O 操作时,如果数据尚未准备好,则调用会立即返回,进程不会停下来等待;worker 进程发起 I/O 请求后,会继续处理其他连接或任务。当 I/O 操作完成(数据准备好)时,系统会通知 worker 进程,这就称为事件。

nginx 的事件驱动模型主要由以下三部分组成:

I/O 多路复用: 这是实现异步非阻塞的核心技术,它允许一个 worker 进程监视多个文件描述符(每个连接和套接字都是一个 fd),并在任何 fd 准备好进行 I/O 操作时,通知该进程。(系统会选择最优方案:epoll [ 性能最好,适用 Linux ]、kqueue [ 使用 FreeBSD & MacOS ]、/dev/poll [ 适用 Solaris ]、select & poll [ 性能最差,作为兜底方案 ])

事件管理: nginx 抽象了连接和事件的概念

  • 连接对象:代表一个网络连接,存储连接的 FD、对端地址等信息
  • 读写事件对象:每个连接对象包含一个读事件和一个写事件,它们是 nginx 注册到 I/O 多路复用机制中的具体项,事件对象存储了对应的 回调函数,指示事件发生时应该执行哪个模块逻辑。

事件循环: nginx 工作核心,从上到下无限循环

  • 收集事件:worker 进程将所有当前活跃的连接注册到 I/O 多路复用机制中;
  • 等待事件:进程进入阻塞状态,等待操作系统通知是否有 I/O 事件发生;
  • 处理事件:一旦有事件发生(连接、数据读写、缓冲区读写等),进程就被唤醒,并得到一个 "就绪" 事件列表;
  • 分发处理:根据不同事件类型,调用注册好的事件处理函数来处理对应连接,执行相应模块逻辑;
  • 返回循环:处理完成后,进程立即回到第一步,等待新事件,绝不空闲

事件的处理过程不能过长,否则其他事件得不到及时处理,就会严重影响 nginx 事件循环的整体效率。所以对于占用 CPU 事件过长的任务,nginx 会将它拆分,在多个小事件中完成它。


nginx 事件循环机制

四、NGINX 缓存机制

nginx 的缓存机制非常强大和灵活,主要用于缓存后端服务器的响应内容,这可以显著提高网站性能、减轻后端服务器负载。

nginx 缓存机制基于文件系统,它将后端服务器返回的响应内容存储在本地磁盘上或远程存储上,配置指定的键后(没错,缓存是可以配置为键值对的),worker、cache loader 等进程都可以通过键来读取缓存。

缓存流程:

  • 缓存前处理:当前端 nginx 收到响应后,优先检查缓存条件(proxy_cache_valid 指令)确认是否允许被缓存,通过检查后再生成键(通常是根据 url 等哈希值计算出一个唯一键),最后确认临时文件的路径(确保写入过程的原子性);
  • 数据临时写入:上一步完成后,nginx 将为本次响应创建临时文件并同步写入,同一时刻 worker 进程也会将这个响应发回客户端;
  • 正式缓存:上一步完成后,nginx 将正式更新缓存,首先在共享内存中(keys_zone 定义的区域)创建新索引或更新旧索引(记录键、缓存事件等原信息),任何 nginx 将临时文件进行重命名(采用 os 的原子调用 rename())并重命名到最终的缓存地,至此整个缓存流程完成。
  • 后续处理:
    • cache loader 加载器:在 nginx 启动时,它扫描磁盘上的缓存目录,将缓存索引重新加载到共享内存中;
    • cache manager 管理器:在 Nginx 运行时,它会周期性地执行清理任务。(如,清理过期缓存、控制缓存大小等)

总结: 检查条件 → \rightarrow → 写入临时文件 → \rightarrow → 原子性重命名到正式缓存目录 → \rightarrow → 注册到共享内存 → \rightarrow → worker 收到相同请求时直接读缓存返回响应


nginx 缓存决策流程

五、NGINX 内存与资源管理

nginx 之所以能在大并发下保持极低的资源消耗(往往几万并发只占用几十 MB 内存),除了依靠事件驱动外,其独特的内存管理和资源调度机制功不可没。

内存池机制:

在 C 语言下,频繁的 malloc 和 free 不仅代码容易出错导致内存泄漏,还会产生大量内存碎片,降低系统性能。nginx 为了解决这个问题,设计了内存池机制:

  • 按需分配:nginx 不会为每一个细小的对象单独申请内存,它会为每一个连接、每一个请求预先申请一大块连续的内存作为内存池;
  • 统一管理:在处理请求的过程中,所有需要的内存(如存储 HTTP 头、变量等)都直接从这个池子里 "切" 一块拿走,不需要调用系统的内存分配函数;
  • 集中销毁:这是最精髓的地方,当连接关闭或请求结束时,nginx 不需要逐一释放零散内存,而是直接销毁整个内存池。

共享内存:

前文提到 worker 进程是相互独立的、内存隔离的,但在实际业务中它们往往需要共享数据,如限流计数(不让每个 worker 单独计算)、ssl 会话缓存、upstream 负载状态等。为应对上述情况,nginx 使用共享内存来实现 worker 间的通信,并引入了 slab 内存分配器来管理这块公共区域:

  • Slab 分配器:它将共享内存划分为不同大小的 "槽位",并通过红黑树进行快速检索,这使多个 worker 可以高效、"无锁"(细粒度锁,并非完全无锁)的读写共享数据。(当你配置 limit_req_zone 进行限流,或者使用 upstream_zone 共享后端健康状态时,底层都在使用这套机制)

缓冲区管理:

nginx 作为反向代理,夹在客户端和上游服务器中间,但往往这两端的网速是不对等的(例如:后端服务器在内网,速度极快;而客户端在公网,可能是弱网环境)。所以 nginx 又通过缓冲区充当了 "蓄水池" 的角色,实现了全异步的数据流转:

  • 请求缓冲:默认情况下,nginx 会先将客户端的请求体完整读取到内存缓冲区中(如果请求体太大超过了内存限制,nginx 会自动将其写入临时磁盘文件),这么做可以让后端服务器瞬间读取完整的请求并处理,而不需要陪着慢速客户端耗时间,从而提升后端效率;
  • 响应缓冲:当后端服务器快速吐出响应时,nginx 会先将响应存入缓冲区,然后按照客户端的网速慢慢发送给客户端,这使得后端应用服务器(如 Tomcat)处理完请求后能立即脱身去处理下一个请求,极大提高后端服务器效率。
相关推荐
猫豆~2 小时前
Nginx代理负载均衡——3day
运维·nginx·负载均衡
Q的世界2 小时前
nginx反向代理负载均衡tomcat多实例
运维·nginx·负载均衡
ShayneLee82 小时前
Nginx修改请求头响应头
android·运维·nginx
武帝为此1 天前
【NGINX 介绍与安装】
运维·nginx
w_t_y_y1 天前
Nginx Plus
运维·数据库·nginx
老姚---老姚2 天前
Nginx Location 匹配优先级详解
nginx
露临霜2 天前
Docker安装nginx
nginx·docker·容器
AlianNiew2 天前
Nginx 反向代理 403 问题复盘
nginx
早川9192 天前
Nginx反向代理
nginx