Nginx架构与进程模型
1. 概念与原理
Nginx 能扛 10 万+ 并发的核心原因:事件驱动 + 异步非阻塞 + 多 worker 进程。对比 Apache 的"每连接一线程"模型,Nginx 的一个 worker 进程就能服务上万并发连接。
1.1 进程模型
┌─── master 进程(root 启动)─────────────────────────┐
│ - 读取配置、绑定端口 │
│ - fork worker 进程 │
│ - 管理 worker 生命周期(信号转发、平滑重启) │
└──────────────────────────────────────────────────────┘
│
├─── worker 进程 1(uid: nginx)
│ └─ 事件循环(epoll / kqueue)
│ ├─ 处理请求
│ ├─ 代理转发
│ └─ 返回响应
├─── worker 进程 2
├─── worker 进程 3
└─── worker 进程 N(= CPU 核数)
- master:特权进程,root。负责配置解析、端口绑定、worker 生命周期。不处理请求。
- worker:非特权(
user nginx;),真正处理请求。每 worker 独立事件循环,互相不通信。 - cache manager / cache loader:缓存辅助进程,按需启动。
1.2 为什么多进程而非多线程
- 进程崩溃互不影响,worker 崩了 master 立即 fork 新的
- 无锁:worker 之间不共享内存(除了共享缓存区域),不需要锁
- 利用 CPU 多核:每 worker 绑定一个核(worker_cpu_affinity)
1.3 事件循环
每个 worker 进程内部是事件循环:
while (true) {
events = epoll_wait(epfd, ..., timeout) // 等事件就绪
for (event in events) {
if (event.type === READ) handle_read(event.fd)
if (event.type === WRITE) handle_write(event.fd)
if (event.type === TIMER) handle_timeout(event.timer)
}
}
- Linux 用 epoll(O(1) 事件通知)
- macOS 用 kqueue
- 一个 worker 能同时注册上万 fd,有数据时才处理
1.4 连接处理
新连接到来时:
- master 预创建监听 socket,所有 worker 继承
- worker 竞争 accept(
accept_mutex或 Linux 内核 reuseport 分配) - worker 把新 fd 注册到 epoll
- 数据到达时回调处理
- 请求处理完毕关闭 fd(或 keepalive 保持)
reuseport(1.9.1+)让内核分配连接到 worker,消除 accept 锁竞争:
listen 80 reuseport;
2. 核心配置
2.1 worker 数量
worker_processes auto; # 自动 = CPU 核数(推荐)
worker_cpu_affinity auto; # 自动绑核
auto 即可。手动调只在极致调优或混部时需要。
2.2 worker 连接数
events {
worker_connections 65535; # 每 worker 最大同时连接
use epoll; # Linux 默认就是,写不写都行
multi_accept on; # 一次 accept 多个连接
}
总并发数 = worker_processes × worker_connections。理论值,实际受内存和 fd 限制。
2.3 全局参数
worker_rlimit_nofile 65535; # worker 最大 fd 数(需内核配合 ulimit)
pid /run/nginx.pid;
error_log /var/log/nginx/error.log warn;
3. 信号与平滑重启
| 信号 | 作用 |
|---|---|
nginx -s reload / SIGHUP | 平滑重载配置(老 worker 处理完当前请求后退出) |
nginx -s stop / SIGTERM | 快速停止 |
nginx -s quit / SIGQUIT | 优雅停止(等老请求完成) |
nginx -s reopen / SIGUSR1 | 重新打开日志文件(logrotate 后用) |
| SIGUSR2 | 热升级二进制 |
3.1 热升级流程
# 1. 编译新版 nginx,替换二进制
cp /usr/sbin/nginx /usr/sbin/nginx.old
cp ./new-nginx /usr/sbin/nginx
# 2. 发信号让 master fork 新 master
kill -USR2 $(cat /run/nginx.pid)
# 此时新旧 master 并存
# 3. 优雅关闭旧 worker
kill -WINCH $(cat /run/nginx.pid.oldbin)
# 4. 确认新版正常后关闭旧 master
kill -QUIT $(cat /run/nginx.pid.oldbin)
4. 模块与处理阶段
Nginx 请求处理分 11 个阶段:
POST_READ → SERVER_REWRITE → FIND_CONFIG → REWRITE →
POST_REWRITE → PREACCESS → ACCESS → POST_ACCESS →
PRECONTENT → CONTENT → LOG
前端常用:
- REWRITE:
rewrite、return - ACCESS:
allow/deny、auth_basic - CONTENT:
proxy_pass、try_files、fastcgi_pass - LOG:
access_log
同阶段多模块按注册顺序执行,理解这个能解释"为什么 rewrite 在 proxy_pass 前面"。
5. 配置文件组织
/etc/nginx/
├── nginx.conf # 全局(worker、events、http 顶层)
├── conf.d/
│ ├── default.conf # 默认 server
│ └── myapp.conf # 业务 server
├── sites-available/ # Debian 系
├── sites-enabled/ # 软链接
├── snippets/
│ ├── ssl-params.conf # TLS 片段复用
│ └── proxy-params.conf # 代理公共头
└── mime.types # MIME 映射
5.1 最佳实践
- 一个 server 一个文件
- 公共片段抽 snippets
include - 用
include conf.d/*.conf;自动加载 - 修改前
nginx -t,通过后才 reload
6. 常见反模式
worker_processes 1:浪费多核,生产必须 auto- 不测试就 reload:语法错整个 Nginx 拒绝 reload,老配置继续跑但你以为生效了
kill -9 nginx:worker 直接死,正在处理的请求全断。用nginx -s quit- 不配
worker_rlimit_nofile:并发上来后 "Too many open files" - 多个 server_name 一样:配置冲突,行为不确定
- root 放 location 里而非 server 里:重复写 + 容易漏
7. 延伸阅读
- Nginx 官方文档: Inside NGINX
- Nginx Architecture — 《开源应用架构》Nginx 章
- Linux epoll 机制详解
- Nginx 源码阅读 — 淘宝 Tengine 团队注释版