跳到主要内容

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 连接处理

新连接到来时:

  1. master 预创建监听 socket,所有 worker 继承
  2. worker 竞争 accept(accept_mutex 或 Linux 内核 reuseport 分配)
  3. worker 把新 fd 注册到 epoll
  4. 数据到达时回调处理
  5. 请求处理完毕关闭 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

前端常用:

  • REWRITErewritereturn
  • ACCESSallow/denyauth_basic
  • CONTENTproxy_passtry_filesfastcgi_pass
  • LOGaccess_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. 延伸阅读