排查 Nginx 性能问题或异常退出,第一步通常是搞清楚它内部的进程都在干什么。把 Nginx 的进程结构想成一家餐厅就好懂了:经理(master)不炒菜,负责排班、验菜单、招人辞人;后厨多位厨师(worker)各守一灶,客人点的每一道菜都出自他们之手;还有两位兼职的仓储员,只在启用缓存时才出现。Nginx 的正式定义是:一个 master 进程加一组 worker 进程的多进程模型,master 管理与调度,worker 承接全部客户端请求。

三类进程,三种职责

master 进程是唯一的管理者。 它负责读取和验证配置、绑定端口、拉起并监控 worker、处理信号——日常操作里的 nginx -s reload 和 nginx -s stop,接信号的始终是 master。它自己不碰任何客户端请求,只保证”干活的人”在岗。

worker 进程是全部请求的承接者。 每个 worker 独立跑一个事件循环,管理成百上千的连接;worker 数量默认与 CPU 核心数对齐,让每个核心跑一个 worker,互不抢占。实际处理请求、代理转发、返回响应的都是 worker。事件循环用的多路复用器在启动日志里可见——Linux 上会打出 using the "epoll" event method,macOS/BSD 上是 kqueue,一个 worker 靠它同时盯住所有连接。

cache manager 和 cache loader 是缓存时代的辅助进程。 只有配置了代理缓存时才会看到它们:loader 在启动时把缓存索引从磁盘读进共享内存,manager 周期性检查缓存条目、清理过期对象。没配缓存就没有这两个进程,属按需出现。

实测看最直观。用 --cpuset-cpus=0-3 把容器限定在 4 个核心里跑一个 nginx:alpine,ps aux 一眼可辨:

多进程模型实测

这张图里的三段输出,分别对应多进程模型的三个关键行为:worker 数严格等于核心数;kill 掉一个 worker,master 一秒内就拉起新进程(PID 30 消失、43 出现)补齐编制;nginx -s reload 时 master 的 PID 纹丝不动(始终是 1),worker 整体换代(30-33 → 48-51)——老 worker 处理完存量请求后退出,新 worker 接盘新配置,服务不中断。

它解决什么矛盾

进程结构的设计要同时满足两个互相拉扯的目标:并发能力要吃满多核,单点故障又不能殃及全体。

单进程结构两头都占不全:所有任务挤在一个进程里,多核硬件利用不起来,高负载时一个环节卡住全线排队;好处是简单、内存占用低,小工具适用,Web 服务器不行。多线程模型并发能力强,但线程共享同一份内存——一个线程的内存泄漏污染整个进程,一个线程崩溃往往带崩全体。

Nginx 的多进程在这对矛盾里取了第三条路:worker 之间内存完全隔离,一个 worker 崩溃只损失它手里的连接,master 立即补位;十几个 worker 摊在十个核心上,并发能力一个核不浪费。

单进程 多线程 Nginx 多进程
多核利用 差 好 好
故障隔离 无 弱(共享内存) 强(进程独立)
崩溃恢复 服务终止 整进程重启 master 秒级补位
内存开销 最低 中 每进程独立一份,略高

适用边界

多进程不是无条件的优解,三个边界要知道:

  • 内存开销:每个 worker 独立一份内存,进程数开太多内存占用线性上涨——worker_processes auto 对齐核心数即可,盲目调大不会带来更高吞吐。
  • 共享数据要走共享内存:worker 隔离的另一面是它们之间不共享状态,限速(limit_req_zone)、缓存索引这类跨 worker 的数据,Nginx 用共享内存段实现,这也是 cache manager 存在的前提。
  • 进程内仍是单线程事件循环:worker 内部不许有阻塞调用,第三方模块若有阻塞实现,会卡住整个 worker 的所有连接——这是”一个 worker 卡住、这个 worker 上的全部请求跟着卡”的根源。

小结

回到开场的餐厅:master 是不炒菜的经理,管配置、管信号、管补位;worker 是全部请求的厨师,数量与核心数对齐;两位仓储员只在开缓存时上岗。下次排查时先 ps aux 分清 master 和 worker,kill 一个 worker 观察自动补位、reload 观察平滑换代,这两个动作也是验证进程模型是否健康的常规体检。

本文由作者 2020-2024 年间的 CSDN 博客文章重构而来,原发布于 CSDN。