最近整理 Nginx 学习笔记,顺手记录一下。Nginx 这东西——装上就能跑,但跑明白不容易。本篇聚焦三件事:为什么快、怎么配、负载均衡怎么玩。
一、Nginx 为什么性能这么高
1.1 解决惊群效应
惊群效应(Thundering Herd):早期的 Web 服务器,多个 worker 进程同时监听同一个 socket。一个连接进来,所有 worker 都被唤醒去
accept,但只有一个能成功,其余的被白白唤醒——并发越高,浪费越大。
Nginx 的解法:不让多个进程在同一时间监听接受连接的 socket,而是让每个进程轮流监听。具体做法:
- 利用一把进程间锁(
ngx_accept_mutex) - 每个 worker 尝试获得这把锁
- 获取成功的进程:将监听 socket 加入 wait 集合,并设置超时等待连接到来
- 没获取锁的进程:将监听 socket 从 wait 集合移除,处理已有连接
这样连接过来时,只有一个 worker 在监听——惊群问题从根上消除。
1.2 异步非阻塞的事件模型
传统服务器的事件处理:
- 一个 client 阻塞一个 worker
- 新 client 到来时,需要 fork 新的 worker
- 并发数极高时,会创建大量 worker 进程,占用大量资源
Nginx 的事件处理使用异步非阻塞模型:
use epoll(Linux 的 epoll 模型)- 用少量 worker 处理大量 client
epoll 的关键优势:相比 select/poll 的「每次都遍历所有 fd」,epoll 用红黑树 + 就绪链表——只有真正就绪的 fd 才会被处理,时间复杂度 O(就绪 fd 数) 而不是 O(总 fd 数)。单 worker 扛几万并发连接就是这么来的。
1.3 master-worker 架构
master 进程(管理)
├── worker 1 ──► 处理连接(epoll)
├── worker 2 ──► 处理连接(epoll)
├── worker N ──► 处理连接(epoll)
└── cache manager / loader(可选)
- master:管理 worker、读取配置、平滑升级、绑定端口
- worker:实际处理请求的进程,数量 = CPU 核心数
为什么 worker 数 = CPU 核心数?——避免进程间切换的开销。每个 worker 绑一个 CPU 核(配合
worker_cpu_affinity),减少上下文切换。
二、基础配置详解
2.1 主配置骨架
# worker 数量,一般来说 CPU 有几个就设置几个,也可以设置成 N-1
worker_processes 1;
events {
# 默认使用 epoll(Linux 下不用显式写,nginx 会自动选)
use epoll;
# 单个 worker 允许的最大连接数
worker_connections 1024;
}
http {
# 引入外部文件(mime 类型映射)
include mime.types;
default_type application/octet-stream;
# 开启零拷贝(与 Kafka 的 sendfile 同源——见 Kafka 系列中篇)
sendfile on;
# 长连接超时时间(秒)
keepalive_timeout 65;
server {
listen 80;
server_name localhost;
location / {
root /usr/local/foodie-shop;
index index.html index.htm;
}
error_page 500 502 503 504 /50x.html;
location = /50x.html {
root html;
}
}
}
worker_processes现代写法:直接写auto,nginx 会自动按 CPU 核心数设置。worker_processes auto; worker_rlimit_nofile 65535; # 提升 worker 的最大 fd 数,配合高并发
2.2 Gzip 压缩
# 开启 gzip 压缩
gzip on;
# 小于 1 字节的文件不再压缩(太小压了反而更大)
gzip_min_length 1;
# 压缩级别(1~9),值越大压缩比越大,越耗费 CPU
gzip_comp_level 3;
# 需要压缩的响应类型(这条原文档没写完,必须补全)
gzip_types text/plain text/css application/javascript application/json
application/xml text/xml image/svg+xml;
gzip_types 必须显式声明——默认只压缩
text/html。生产环境至少要加上 css / js / json / svg。
2.3 location 中 root 与 alias 的区别
这是个经典坑——两者看起来差不多,行为完全不同:
server {
listen 90;
server_name localhost;
# 访问 localhost:90/test1/a.html
# root:把完整 URI 拼到 root 路径后 → /usr/test1/a.html
location /test1 {
root /usr/;
}
# 访问 localhost:90/test2/a.html
# alias:用 alias 路径**替换** location 匹配前缀 → /usr/local/foodie-shop/a.html
location /test2 {
alias /usr/local/foodie-shop;
index index.html;
}
}
| 关键字 | 行为 | 是否能用于正则 location |
|---|---|---|
root | 拼接:root + 完整 URI | ✅ |
alias | 替换:alias + (URI - location 前缀) | ❌(只能用于 prefix location) |
口诀:
root是「追加」,alias是「替换」。alias后面的路径不要带结尾/(带不带行为不同,是另一个经典坑)。
三、反向代理与负载均衡
3.1 什么是反向代理
先理解正向代理:客户端知道真实服务器在哪,主动通过代理访问(如 VPN、爬虫 IP 池)。代理的是客户端。
反向代理:客户端不知道真实服务器在哪,只看到代理服务器,由代理把请求转发给后端真实服务器(如 Nginx → Tomcat 集群)。代理的是服务端。
Nginx 最核心的生产场景就是反向代理 + 负载均衡。
3.2 upstream 负载均衡配置
# ----------- 配置负载均衡,开始 -----------
upstream tomcats {
# max_conns:限制每台 server 的连接数,用于保护避免过载,可起到限流作用
# weight:权重,值越大被分配到的概率越高
# backup:备用机,只有在其他服务器都宕机以后,自己才会加入集群被访问到
# 注意 backup 参数不能使用在 hash 和 random load balancing 中
server 192.168.1.173:8080 max_conns=2 weight=1;
server 192.168.1.174:8080 max_conns=2 weight=10;
server 192.168.1.175:8080 backup;
# 长连接处理的数量(与后端 Tomcat 的 keepalive 配合)
keepalive 32;
}
server {
listen 9999;
server_name www.test.com;
location / {
proxy_pass http://tomcats;
}
}
# ----------- 配置负载均衡,结束 -----------
3.3 负载均衡调度算法
| 算法 | 关键字 | 适用场景 |
|---|---|---|
| 轮询(默认) | 不写 | 后端机器性能相近 |
| 权重轮询 | weight=N | 后端机器性能不均 |
| ip_hash | ip_hash; | 会话保持——同一 IP 固定打到同一台 |
| 最少连接数 | least_conn; | 后端处理时间差异大 |
| hash(url/arg) | hash $request_uri; | 缓存命中——同一 URL 固定到同一台 |
会话保持的代价:
ip_hash解决了 session 问题,但节点宕机后该 IP 的 session 全部丢失。生产环境更好的方案是 session 集中存储(Redis)+ 无状态化部署,而不是依赖 ip_hash。
3.4 upstream 参数速查
| 参数 | 作用 |
|---|---|
weight=N | 权重,默认 1 |
max_conns=N | 最大并发连接数(限流) |
max_fails=N | 健康检查失败阈值,默认 1 |
fail_timeout=T | 失败 N 次后暂停时间,默认 10s |
backup | 备用机,主力都挂了才上 |
down | 标记永久不可用(维护中) |
keepalive=N | 到后端的长连接池大小 |
四、CORS 跨域支持
配置在
server标签内,与listen标签平级。
# 允许跨域请求的域,* 代表所有
add_header 'Access-Control-Allow-Origin' *;
# 允许带上 cookie 请求
add_header 'Access-Control-Allow-Credentials' 'true';
# 允许请求的方法,比如 GET/POST/PUT/DELETE
add_header 'Access-Control-Allow-Methods' *;
# 允许请求的 header
add_header 'Access-Control-Allow-Headers' *;
重要警告:
Access-Control-Allow-Origin: *和Access-Control-Allow-Credentials: true不能同时使用——浏览器规范禁止。要带 cookie 就必须把*替换成具体的 origin(如https://example.com)。生产推荐:用
$http_origin变量做白名单回写,而不是裸写*。
五、防盗链
防盗链(防止其它站点非法引用自己系统的静态资源)。配置在跨域相关位置同级。
# 对源站点验证(只允许 imooc.com 的子域引用)
valid_referers *.imooc.com;
# 非法引入会进入下方判断
if ($invalid_referer) {
return 404;
}
valid_referers 支持的语法:
| 写法 | 含义 |
|---|---|
none | 允许 Referer 为空(直接访问) |
blocked | 允许 Referer 不带 http/https 前缀 |
*.imooc.com | 允许 imooc.com 的所有子域 |
server_names | 允许与 server_name 匹配的 Referer |
~regex | 正则匹配 |
现代视角(2026)补一句:防盗链在 2024 年后已经不是最优解——更推荐用签名 URL(带过期时间的 token)或 CDN 鉴权。Referer 头是可以伪造的,只能防君子防不了小人。
六、常用运维命令速查
# 检查配置语法(修改后必须先检查)
nginx -t
# 平滑重载配置(不重启服务,请求零丢失)
nginx -s reload
# 重新打开日志文件(日志切割用)
nginx -s reopen
# 快速停止
nginx -s stop
# 优雅停止(处理完当前请求再退)
nginx -s quit
nginx -s reload的原理:master 进程收到信号 → 启动新 worker 处理新请求 → 通知老 worker 处理完手上请求后退出。全程不丢请求——这是 Nginx 平滑发布的基石。
七、小结
| 主题 | 核心要点 |
|---|---|
| 高性能 | 惊群消除 + epoll 异步 + master-worker |
| 基础配置 | worker_processes / worker_connections / sendfile / gzip |
| 路径匹配 | root 是追加,alias 是替换 |
| 负载均衡 | upstream + 5 种调度算法 |
| 跨域 | CORS 四件套(注意 * + Credentials 互斥) |
| 防盗链 | valid_referers + $invalid_referer |
Nginx 这东西——配置文件就是文档,多敲几遍
nginx -t比看十篇博客都管用。
现代视角补一句(2026):本篇涵盖的命令、配置到 2026 年仍然适用——Nginx 的核心模型没动过。变的只是:HTTP/3(QUIC)支持、动态模块化、OpenResty/Lua 生态的成熟。如果是新建项目,可以考虑用 Caddy(自动 HTTPS)做入门,但 Nginx 依然是生产事实标准。