Nginx 是什么
Nginx(发音 “engine-x”)是一个开源的高性能 Web 服务器和反向代理服务器。
核心功能
- HTTP 服务器 — 直接提供静态文件(HTML、CSS、JS、图片等),性能极高,采用事件驱动、异步非阻塞架构,能以少量内存处理大量并发连接
- 反向代理 — 接收客户端请求,转发给后端应用服务器(如 Node.js、Python、Java),再把响应返回给客户端。客户端不知道后端是谁
- 负载均衡 — 将请求分发到多台后端服务器,支持轮询、加权、最少连接等策略,实现水平扩展
- 邮件代理 — 支持 IMAP/POP3/SMTP 代理(较少使用)
典型使用场景
graph LR
Client["客户端"] --> Nginx["Nginx (80/443)"]
Nginx --> A["后端服务 A (3000)"]
Nginx --> B["后端服务 B (3001)"]
Nginx --> SF["静态文件 (/var/www)"]
为什么用它而不是直接暴露应用服务器
- 应用服务器(如 Express、Django)擅长处理业务逻辑,不擅长处理高并发静态资源
- Nginx 在前端挡一层,处理 SSL 终止、压缩、缓存、限流等
- 单机可以同时代理多个不同服务
关键配置示例
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:3000; # 反向代理到 Node.js
}
location /static/ {
root /var/www; # 直接返回静态文件
}
}
Nginx 是互联网基础设施中最常见的”前台接待”——它接客、分流、挡活,让后端应用专注于业务逻辑。全球约 1/3 的网站使用 Nginx。
Nginx 是网关吗
在大多数 Web 架构里,Nginx 扮演的就是网关的角色,但不完全等同。
Nginx 作为网关做的事
graph LR
Client["客户端"] --> GW["Nginx 网关"]
GW --> A["内部服务 A"]
GW --> B["内部服务 B"]
GW --> C["内部服务 C"]
- 统一入口,外部只暴露 Nginx 的端口
- 路由分发(按路径/域名转发到不同服务)
- SSL 终止(HTTPS → HTTP)
- 限流、IP 黑白名单
- 负载均衡
和 API 网关的区别
| 能力 | Nginx | API 网关 |
|---|---|---|
| 路由/代理 | ✅ | ✅ |
| 负载均衡 | ✅ | ✅ |
| SSL 终止 | ✅ | ✅ |
| 认证鉴权(JWT、OAuth) | 需要插件/Lua | ✅ 内置 |
| 限流/熔断 | 基础能力 | ✅ 更精细 |
| 服务发现 | ❌ | ✅ |
| API 生命周期管理 | ❌ | ✅ |
| 动态配置(不改配置文件) | ❌(需要 reload) | ✅ 热更新 |
| 插件生态 | 有限 | 丰富 |
简单说:
- Nginx 是网络层网关——处理”请求往哪转”的问题
- API 网关(Kong、APISIX 等)是业务层网关——在 Nginx 之上加了认证、限流、服务发现等微服务治理能力
- APISIX 底层其实就是基于 Nginx(OpenResty)的
Nginx 可以理解为网关的基础款,够用但功能有限。需要更复杂的微服务治理时,就在它上面加一层 API 网关。
Nginx 核心原理
Nginx 的核心原理可以用一句话概括:一个主进程管理多个工作进程,每个工作进程用事件驱动的方式同时处理成千上万个连接,全程不阻塞。
进程模型
graph TD
Master["Master 进程(1个)<br/>— 管理、读配置、监控"] --> W1["Worker 进程 1<br/>— 实际处理请求"]
Master --> W2["Worker 进程 2"]
Master --> W3["Worker 进程 3"]
Master --> WN["Worker 进程 N<br/>— 通常 = CPU 核心数"]
- Master 不处理业务,只负责启动 Worker、热加载配置
- 每个 Worker 是独立进程,互不干扰,一个挂了不影响其他的
- Worker 数量通常设为 CPU 核心数,避免上下文切换开销
事件驱动(核心中的核心)
传统服务器(如 Apache)用一个线程处理一个连接:
graph LR
C1["连接1"] --> T1["线程1(等着读数据...阻塞中...)"]
C2["连接2"] --> T2["线程2(等着读数据...阻塞中...)"]
C3["连接3"] --> T3["线程3(等着读数据...阻塞中...)"]
T1 --> X["1万个连接 = 1万个线程,内存炸了"]
Nginx 用 epoll(Linux)实现事件驱动:
Worker 进程(单个):
epoll 告诉我哪些连接有数据了?
→ 连接3有数据 → 处理
→ 连接7有数据 → 处理
→ 连接2有数据 → 处理
→ 没有数据的连接?不管它,不阻塞等
- 一个 Worker 用一个线程就能处理几万个连接
- 没有数据可读/可写时,不会阻塞等待,去处理其他连接
- 这是 Nginx 高并发的根本原因
一次请求的完整生命周期
以反向代理为例:
graph TD
A["客户端发请求"] --> B["① Nginx Worker 接收连接<br/>(epoll 通知有新连接)"]
B --> C["② 解析 HTTP 请求<br/>(读取 method、path、headers)"]
C --> D["③ 匹配 location 规则<br/>(比如 location /api/ → proxy_pass)"]
D --> E["④ 建立到后端的连接<br/>把请求转发给后端服务<br/>注意:这也是非阻塞的!"]
E --> F["⑤ 后端返回响应,epoll 通知 Worker"]
F --> G["⑥ Worker 把响应写回客户端(非阻塞写入)"]
G --> H["⑦ 关闭/复用连接"]
关键点:步骤 ④ 等后端响应时,Worker 不会傻等,而是去处理其他连接。后端响应到了,epoll 会再次通知它。
零拷贝技术(高效传输静态文件)
普通方式:文件从磁盘 → 内核空间 → 用户空间 → 内核空间 → 网卡
Nginx 用 sendfile():
graph LR
Disk["磁盘"] --> Kernel["内核空间"]
Kernel --> NIC["网卡"]
Disk -. "(跳过了用户空间的拷贝,速度快一倍)" .-> NIC
每个机制的贡献
| 机制 | 解决什么问题 |
|---|---|
| 多 Worker 进程 | 多核利用 + 进程隔离 |
| epoll 事件驱动 | 一个线程处理几万连接,不阻塞 |
| 非阻塞 I/O | 等待时不占 CPU,去处理别的 |
| 零拷贝 sendfile | 静态文件传输省去多余内存拷贝 |
| 内存池 | 小块内存复用,减少 malloc 开销 |
核心思想就八个字:事件驱动,非阻塞 I/O。这让它用极少的资源扛住巨大的并发。
底层原理深入
epoll 的真正原理
Nginx 高性能的根基是 Linux 的 epoll 系统调用。
select/poll 的问题(传统方式):
// 每次调用都要把所有 fd 传给内核
select(all_fds, &readable_fds, NULL, NULL, &timeout);
// 内核遍历全部 10000 个 fd → O(n)
// 返回后,用户态再遍历一次找出哪些就绪 → 又 O(n)
每次调用都是 O(n) 遍历,1 万个连接就要扫描 1 万次。
epoll 的做法:三步分离
// 第1步:创建 epoll 实例(只做一次)
int epfd = epoll_create1(0);
// 第2步:注册感兴趣的 fd(每个连接只做一次)
epoll_ctl(epfd, EPOLL_CTL_ADD, client_fd, &event);
// 第3步:等待事件(只返回就绪的 fd)
epoll_wait(epfd, events, max_events, timeout);
关键区别:内核维护了一个红黑树(存放所有被监听的 fd)和一个就绪链表(存放已有数据的 fd)。
红黑树(所有被监听的 fd) 就绪链表(有数据到达的 fd)
fd=3 fd=7 ✅
fd=5 fd=12 ✅
fd=7 ←── 网卡中断 ──→
fd=12 ←── 网卡中断 ──→
fd=15
...(共 10000 个)
- 网卡收到数据 → 触发硬件中断 → 内核中断处理程序找到对应 fd → 把它放进就绪链表
epoll_wait只需从就绪链表取数据,O(1),不管你监听了多少个 fd- 注册/删除 fd 是 O(log n)(红黑树)
这就是 Nginx 能轻松处理 10 万并发连接的底层原因。
请求处理流水线(11 个阶段)
Nginx 把一个 HTTP 请求拆成了 11 个阶段(phase),每个阶段挂载不同的 handler 模块:
graph TD
A["请求进来"] --> P1["① NGX_HTTP_POST_READ_PHASE<br/>— 刚读完请求头"]
P1 --> P2["② NGX_HTTP_SERVER_REWRITE_PHASE<br/>— server 块内的 rewrite"]
P2 --> P3["③ NGX_HTTP_FIND_CONFIG_PHASE<br/>— 查找匹配的 location"]
P3 --> P4["④ NGX_HTTP_REWRITE_PHASE<br/>— location 块内的 rewrite"]
P4 --> P5["⑤ NGX_HTTP_POST_REWRITE_PHASE<br/>— rewrite 后处理"]
P5 --> P6["⑥ NGX_HTTP_PREACCESS_PHASE<br/>— 访问前(限流、连接数限制)"]
P6 --> P7["⑦ NGX_HTTP_ACCESS_PHASE<br/>— 访问控制(IP黑白名单、认证)"]
P7 --> P8["⑧ NGX_HTTP_POST_ACCESS_PHASE<br/>— 访问控制后"]
P8 --> P9["⑨ NGX_HTTP_PRECONTENT_PHASE<br/>— 内容生成前(try_files)"]
P9 --> P10["⑩ NGX_HTTP_CONTENT_PHASE<br/>— 生成内容(proxy_pass、静态文件)"]
P10 --> P11["⑪ NGX_HTTP_LOG_PHASE<br/>— 记录日志"]
每个阶段可以挂多个 handler,按顺序执行。任何一个 handler 返回 NGX_DONE 就中断流水线。
这就是 Nginx 模块化的核心——不是一大坨 if-else,而是一条流水线,每个模块只管自己的阶段。
内存池(ngx_pool_t)
Nginx 不用 malloc/free 管理请求内存,而是用自己的内存池:
graph TD
Pool["ngx_pool_t(一个请求一个)"]
Pool --> Small["小块内存区(分配 <= 4096 字节)<br/>已分配 / 已分配 / 剩余空间<br/>分配 = 移动指针,O(1)"]
Pool --> Large["大块内存区(分配 > 4096 字节)<br/>用 malloc 单独分配,挂在链表上"]
Pool --> Destroy["请求结束时:一次性销毁整个 pool,全部释放"]
// 不需要 free 每个小块,请求结束时统一销毁
ngx_destroy_pool(request->pool);
好处:
- 分配:移动指针,O(1),没有 malloc 的碎片和锁开销
- 释放:不需要逐个 free,整块回收
- 没有内存泄漏——请求结束,池子销毁,全部归还
连接的内核级数据流
一次 proxy_pass 请求,数据在内核中怎么流动的:
graph TD
W["Nginx Worker 进程(用户空间)"]
W --"read() / write()"--> S["socket 内核缓冲区<br/>接收缓冲区(客户端 → Nginx)<br/>发送缓冲区(Nginx → 客户端)"]
S --> TCP["TCP/IP 协议栈"]
TCP --> NIC["网卡"]
NIC <--> E["客户端 ↔ 后端服务器"]
Nginx 的 Worker 在用户空间维护了两个 socket:
- client socket:和客户端的连接
- upstream socket:和后端服务器的连接
数据流:client socket → 读到用户空间 → 写到 upstream socket
这是两次系统调用 + 两次数据拷贝。
sendfile 零拷贝(静态文件场景)
返回静态文件时,Nginx 用了 sendfile() 系统调用,数据完全不经过用户空间:
graph LR
subgraph 普通["普通 read() + write()"]
direction LR
A1["磁盘"] -->|"拷贝1"| A2["内核页缓存"]
A2 -->|"拷贝2"| A3["用户空间 buffer"]
A3 -->|"拷贝3"| A4["内核 socket 缓冲区"]
A4 --> A5["网卡"]
end
subgraph sendfile["sendfile()"]
direction LR
B1["磁盘"] --> B2["内核页缓存"]
B2 --> B3["网卡"]
B1 -. "只1次拷贝,用户空间完全不参与" .-> B3
end
location /static/ {
sendfile on; # 开启零拷贝
tcp_nopush on; # 包满才发,减少小包
tcp_nodely on; # 最后的数据包不延迟
}
Worker 之间的通信
Worker 进程之间不需要共享连接状态,但有些场景需要通信(比如热升级、缓存管理),Nginx 用 共享内存 + 进程间信号:
graph LR
W1["Worker 1"] <-->|"共享内存(缓存、限流计数器)"| W2["Worker 2"]
W1 -->|"signal(SIGTERM、SIGHUP 等)"| Master["Master"]
W2 -->|"signal(SIGTERM、SIGHUP 等)"| Master
- 限流:
limit_req模块的计数器放在共享内存里,所有 Worker 共用 - 缓存:
proxy_cache的缓存索引在共享内存,缓存文件在磁盘 - 热加载:Master 收到
SIGHUP→ 加载新配置 → 启动新 Worker → 通知旧 Worker 优雅退出
优雅关闭(Graceful Shutdown)
graph TD
A["收到 SIGTERM / reload"] --> B["① Worker 停止接受新连接"]
B --> C["② 继续处理已建立的连接上的请求"]
C --> D["③ 所有请求处理完毕 → 关闭连接"]
D --> E["④ Worker 进程退出"]
worker_shutdown_timeout 可以设超时,防止某个长连接卡住不退出。
Nginx 快的底层原因总结
| 层级 | 技术 | 效果 |
|---|---|---|
| 系统调用 | epoll | O(1) 事件通知,不遍历 |
| I/O 模型 | 非阻塞 + 异步 | 一个线程处理几万连接 |
| 数据传输 | sendfile 零拷贝 | 静态文件少 2 次内存拷贝 |
| 内存管理 | 内存池 | O(1) 分配,无碎片,无泄漏 |
| 进程模型 | 多 Worker 无锁 | 无锁竞争,CPU 亲和 |
| 架构设计 | 11 阶段流水线 | 模块解耦,灵活可扩展 |
本质上 Nginx 就是把操作系统提供的高性能原语(epoll、sendfile、共享内存)用到了极致,再加上精心的内存管理和无锁架构。
安装与配置
安装方式
# Ubuntu/Debian
sudo apt update && sudo apt install nginx
# CentOS/RHEL
sudo yum install nginx
# macOS
brew install nginx
# Docker(推荐开发用)
docker run -d -p 80:80 --name nginx nginx:latest
安装后:
sudo systemctl start nginx # 启动
sudo systemctl enable nginx # 开机自启
sudo systemctl status nginx # 查看状态
配置文件结构
/etc/nginx/
├── nginx.conf ← 主配置文件
├── conf.d/ ← 自定义配置(推荐放这里)
│ └── mysite.conf
├── sites-available/ ← 可用的站点配置
├── sites-enabled/ ← 已启用的(软链接)
└── mime.types ← 文件类型映射
主配置文件 nginx.conf 全貌
# ===== 全局块 =====
worker_processes auto; # Worker 数量,auto = CPU 核心数
worker_rlimit_nofile 65535; # 每个 Worker 最大打开文件数
error_log /var/log/nginx/error.log warn; # 错误日志级别
pid /run/nginx.pid;
events {
worker_connections 4096; # 每个 Worker 最大连接数
use epoll; # Linux 用 epoll
multi_accept on; # 一次接受所有新连接
}
http {
# ----- 基础设置 -----
include mime.types;
default_type application/octet-stream;
# 日志格式
log_format main '$remote_addr - $remote_user [$time_local] '
'"$request" $status $body_bytes_sent '
'"$http_referer" "$http_user_agent"';
access_log /var/log/nginx/access.log main;
# 性能优化
sendfile on; # 零拷贝
tcp_nopush on; # 包满再发
tcp_nodelay on; # 不延迟
keepalive_timeout 65; # 长连接超时
client_max_body_size 50m; # 上传文件大小限制
# Gzip 压缩
gzip on;
gzip_types text/plain text/css application/json
application/javascript text/xml;
gzip_min_length 1024; # 小于 1KB 不压缩
# ===== 包含其他配置 =====
include /etc/nginx/conf.d/*.conf;
include /etc/nginx/sites-enabled/*;
}
常见场景配置
静态网站
server {
listen 80;
server_name example.com www.example.com;
root /var/www/html;
index index.html;
location / {
try_files $uri $uri/ =404;
}
# 静态资源缓存
location ~* \.(jpg|png|css|js|ico)$ {
expires 30d;
add_header Cache-Control "public, no-transform";
}
}
反向代理
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://127.0.0.1:3000; # 转发到后端
proxy_set_header Host $host; # 传递原始域名
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# WebSocket 支持
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 超时设置
proxy_connect_timeout 10s;
proxy_read_timeout 60s;
proxy_send_timeout 60s;
}
}
负载均衡
# 定义后端服务器组
upstream backend {
# 默认轮询(round-robin)
server 192.168.1.10:3000 weight=3; # 权重 3,分到更多请求
server 192.168.1.11:3000 weight=1;
server 192.168.1.12:3000 backup; # 备用,其他挂了才用
# 保持会话一致(同一用户总是打到同一台)
ip_hash;
# 健康检查(商业版有主动检查,开源版用被动检查)
# max_fails=3 fail_timeout=30s → 30秒内失败3次就标记不可用
}
server {
listen 80;
server_name app.example.com;
location / {
proxy_pass http://backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
}
}
其他负载均衡策略:
upstream backend {
# 最少连接数
least_conn;
server 192.168.1.10:3000;
server 192.168.1.11:3000;
}
upstream backend {
# IP 哈希(会话保持)
ip_hash;
server 192.168.1.10:3000;
server 192.168.1.11:3000;
}
HTTPS(SSL)
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/nginx/ssl/example.com.pem;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_prefer_server_ciphers on;
# SSL 会话复用,减少握手开销
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
location / {
proxy_pass http://127.0.0.1:3000;
}
}
# HTTP 自动跳转 HTTPS
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
用 Let’s Encrypt 免费证书:
sudo apt install certbot python3-certbot-nginx
sudo certbot --nginx -d example.com # 自动配置 SSL
多个服务共存
# 前端
server {
listen 80;
server_name www.example.com;
root /var/www/frontend;
location / {
try_files $uri $uri/ /index.html; # SPA 路由兜底
}
}
# API 后端
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
}
}
# 同一个域名,按路径分流
server {
listen 80;
server_name example.com;
location / {
proxy_pass http://127.0.0.1:8080; # 前端
}
location /api/ {
proxy_pass http://127.0.0.1:3000; # 后端 API
}
location /ws/ {
proxy_pass http://127.0.0.1:4000; # WebSocket
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
}
}
常用运维命令
# 测试配置是否正确(改完配置必须先测)
sudo nginx -t
# 重新加载配置(不中断服务)
sudo nginx -s reload
# 停止
sudo nginx -s stop # 立即停止
sudo nginx -s quit # 优雅停止(处理完当前请求)
# 查看版本和编译参数
nginx -V
# 查看连接数
ss -tunlp | grep nginx
# 实时监控日志
tail -f /var/log/nginx/access.log
常见问题排查
# 配置测试报错
sudo nginx -t
# → 看具体哪一行有语法错误
# 502 Bad Gateway → 后端服务没启动或挂了
# 检查后端是否在跑
curl http://127.0.0.1:3000/health
# 504 Gateway Timeout → 后端太慢
# 调大超时
proxy_read_timeout 120s;
# 权限问题
sudo chmod -R 755 /var/www/html
sudo chown -R www-data:www-data /var/www/html
# 端口被占用
sudo lsof -i :80
开发时的工作流
# 1. 写配置
sudo vim /etc/nginx/conf.d/myapp.conf
# 2. 测试
sudo nginx -t
# 3. 生效
sudo nginx -s reload
# 4. 验证
curl -I http://localhost
改配置只需要这三步:编辑 → 测试 → 重载,全程不中断线上服务。