📖 先搞懂概念
反向代理是什么?
你(浏览器)不直接访问后端服务器,而是先访问 Nginx(守门人),Nginx 再帮你去后端拿数据回来给你。
1 2 3 4 5
| ❌ 不用反向代理:你 → 某台后端 → 只能打这一台
✅ 用反向代理: 你 → Nginx → 自动帮你挑一台后端
|
好处:
轮询(Round-Robin)是什么?
就像发扑克牌一样,每人一张轮流来:
1 2 3 4 5 6 7 8 9
| 请求1 → 服务器A
请求2 → 服务器B
请求3 → 服务器C
请求4 → 服务器A ← 循环
|
Nginx 默认就是这种分配方式,无需额外配置,属于开箱即用。
🏗️ 架构总览
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25
| 用户访问 http://你的域名/
│
▼
┌─ Nginx (80端口) ─┐
│ upstream │
│ round-robin │
└──┬───┬───┬───┬───┘
│ │ │ │
▼ ▼ ▼ ▼
:3001 :3002 :3003 :3004 ← 后端服务(Node.js / Python / Go / Java 任意)
🟢 🔵 🟠 🟣
节点A 节点B 节点C 节点D
|
一台 Nginx 挡在前面,后面挂 N 台完全一样的后端服务。用户只看到 Nginx,不知道后面有几台机器。
🪜 操作步骤
步骤 1️⃣:准备后端服务
先用你最熟悉的语言启动几个后端。以 Node.js 为例,创建四个目录,各放一个最简单的服务器:
1 2 3
| mkdir -p ~/demo/node1 ~/demo/node2 ~/demo/node3 ~/demo/node4
|
每个目录下放一个 server.js,唯一的区别是端口和返回的标识文字:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
|
const http = require('http');
const port = 3001;
http.createServer((req, res) => {
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end(`
<h1>🟢 节点 A — 端口 ${port}</h1>
<p>这台服务器处理了你的请求</p>
`);
}).listen(port, () => console.log(`节点 A 启动在 ${port}`));
|
另外 node2/server.js、node3/server.js、node4/server.js 同理,只需要改端口号(3002/3003/3004)和显示文字(节点 B/C/D)即可。
Python 就更简单了,一行搞定(后面会讲)。
步骤 2️⃣:启动后端服务
开四个终端窗口,分别运行:
1 2 3 4 5 6 7 8
| cd ~/demo/node1 && node server.js
cd ~/demo/node2 && node server.js
cd ~/demo/node3 && node server.js
cd ~/demo/node4 && node server.js
|
验证是否启动成功:
1 2 3 4 5 6 7 8 9
|
netstat -an | grep "3001\|3002\|3003\|3004"
lsof -i :3001
netstat -an | findstr "3001 3002 3003 3004"
|
1 2 3 4 5 6 7 8 9
| TCP 0.0.0.0:3001 LISTENING
TCP 0.0.0.0:3002 LISTENING
TCP 0.0.0.0:3003 LISTENING
TCP 0.0.0.0:3004 LISTENING
|
浏览器访问 http://localhost:3001 能看到页面就说明单台后端正常。
💡 如果不想写 Node.js,用 Python 自带的简易服务器一行就行:
1 2 3 4 5 6 7
| cd ~/demo/node1 && python3 -m http.server 3001
cd ~/demo/node2 && python3 -m http.server 3002
|
python -m http.server 不需要装任何东西,把当前目录当网站根目录,访问 / 返回 index.html。
步骤 3️⃣:编写 Nginx 配置(核心!)
Linux 下配置文件通常在 /etc/nginx/nginx.conf 或 /etc/nginx/conf.d/ 下。Windows 则在 Nginx 安装目录的 conf/ 下。
3.1 定义上游服务器组(upstream 块)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
|
upstream my_backend {
server 127.0.0.1:3001 weight=1;
server 127.0.0.1:3002 weight=1;
server 127.0.0.1:3003 weight=1;
server 127.0.0.1:3004 weight=1;
}
|
| 关键字 | 含义 |
|——–|——|
| upstream my_backend | 定义一个”上游服务器组”,名字随便起,后面 proxy_pass 要用 |
| server IP:端口 | 组里的每台服务器 |
| weight=1 | 权重。默认轮询下,权重越大分到的请求越多。全是 1 就是平均分 |
3.2 配置反向代理(server 块)
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| server {
listen 80;
server_name your-domain.com;
location / {
proxy_pass http://my_backend;
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;
add_header Cache-Control "no-store, no-cache, must-revalidate";
add_header X-Upstream-Server $upstream_addr;
}
}
|
执行流程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| 用户访问 http://your-domain.com/
→ Nginx 收到请求
→ 查 upstream 列表,内部计数器决定这次轮到谁
→ 第1次:127.0.0.1:3001 🟢 节点A
→ 第2次:127.0.0.1:3002 🔵 节点B
→ 第3次:127.0.0.1:3003 🟠 节点C
→ 第4次:127.0.0.1:3004 🟣 节点D
→ 第5次:127.0.0.1:3001 🟢 又回到节点A — 循环!
|
⚠️ 关键:演示轮询时必须加 Cache-Control: no-store,否则浏览器会缓存返回内容,你刷新永远看到同一台服务器。
步骤 4️⃣:测试配置语法
改完配置先验证,有语法错误 Nginx 会启动失败:
正确输出:
1 2 3 4 5
| nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful
|
Windows 下需要加 -p 指定安装目录:
1 2 3
| nginx.exe -t -p "C:\nginx"
|
步骤 5️⃣:启动 / 重载 Nginx
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
|
sudo systemctl start nginx
sudo nginx
sudo nginx -s reload
nginx.exe -s reload -p "C:\nginx"
|
验证 80 端口是否在监听:
1 2 3 4 5 6 7 8 9 10 11
|
sudo lsof -i :80
netstat -an | findstr ":80 "
|
步骤 6️⃣:验证轮询效果
命令行(curl)
1 2 3 4 5 6 7 8 9 10 11
|
for i in 1 2 3 4; do
echo "=== 第 ${i} 次 ==="
curl -s http://localhost/ | grep "<title>"
done
|
输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| === 第 1 次 ===
<title>🟢 节点 A — 端口 3001</title>
=== 第 2 次 ===
<title>🔵 节点 B — 端口 3002</title>
=== 第 3 次 ===
<title>🟠 节点 C — 端口 3003</title>
=== 第 4 次 ===
<title>🟣 节点 D — 端口 3004</title>
|
浏览器
访问 http://localhost(或你的域名),每次按 F5 刷新,返回的服务器标识轮着变:
🟢 A → 🔵 B → 🟠 C → 🟣 D → 🟢 循环…
🔧 Nginx 常用命令速查
| 操作 | Linux | Windows |
|——|——-|———|
| 启动 | sudo nginx | nginx.exe -p "C:\nginx" |
| 测试配置 | nginx -t | nginx.exe -t -p "C:\nginx" |
| 热重载 | nginx -s reload | nginx.exe -s reload -p "C:\nginx" |
| 优雅停止 | nginx -s quit | nginx.exe -s quit -p "C:\nginx" |
| 立即停止 | nginx -s stop | nginx.exe -s stop -p "C:\nginx" |
| 查看日志 | tail -f /var/log/nginx/access.log | type logs\access.log |
💡 Nginx 最大的优势:修改配置只需 reload,不中断服务,零停机更新。
🧪 进阶:四种负载均衡策略
在 upstream 块里加一行就能切换策略:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| upstream my_backend {
server 127.0.0.1:3001 weight=3;
server 127.0.0.1:3002 weight=1;
server 127.0.0.1:3003 weight=1;
server 127.0.0.1:3004 weight=1;
least_conn; ip_hash; }
|
| 策略 | 关键字 | 适合场景 |
|——|——–|———-|
| 轮询 | 默认 | 通用无状态服务,服务器配置一样 |
| 加权轮询 | 改 weight | 服务器配置不同(好的多扛点) |
| 最少连接 | least_conn; | 长连接、WebSocket、文件上传 |
| IP 哈希 | ip_hash; | 需要 Session,用户不能跳到别的机器 |
📊 一条请求的完整旅程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35
| 你按 F5 刷新 http://your-domain.com/
│
├─ ① 浏览器 → DNS 解析域名 → 得到服务器 IP
│
├─ ② 浏览器 → 服务器 80 端口: "GET / HTTP/1.1"
│
├─ ③ Nginx 收到请求,查看 upstream 内部计数器
│ 当前计数器 = 3 → 这次轮到 127.0.0.1:3003
│
├─ ④ Nginx → 127.0.0.1:3003: "有人找你要首页"
│
├─ ⑤ 3003 的 Node 服务返回页面内容
│
└─ ⑥ Nginx 加上 Cache-Control 头,返回给浏览器
你看到 🟠 节点C 的页面
再按 F5 → 计数器变 4 → 打到 3004 → 🟣 节点D
再按 F5 → 计数器重置 → 回到 3001 → 🟢 节点A
|
核心三要素
| 要素 | 作用 |
|——|——|
| upstream | 告诉 Nginx 后端有哪些机器 |
| proxy_pass | 把请求转发到 upstream 里的某台 |
| Cache-Control: no-store | 演示时必须加,否则浏览器缓存了看不清轮询 |
⚠️ 常见问题
| 问题 | 可能原因 | 解决 |
|——|———-|——|
| 刷新页面不变 | 浏览器缓存了 | Ctrl+F5 强制刷新;检查有没有配 Cache-Control: no-store |
| 80 端口被占用 | Apache / IIS / 另一个 Nginx 在跑 | sudo lsof -i :80 找到进程并停掉 |
| connect() failed | 某台后端挂了 | 确保对应端口的进程在运行 |
| nginx -t 报错 | 配置语法错误 | 检查分号 ; 是否漏了、花括号 {} 是否配对 |
| 某台后端总是收不到请求 | weight 设太小或防火墙问题 | 检查 weight 值和 iptables 规则 |
🏨 延伸:Nginx 还能做什么
本文讲的是反向代理 + 负载均衡,但 Nginx 的能力远不止这些:
虚拟主机 — 一个端口跑多个网站
1 2 3 4 5 6 7 8 9 10 11 12 13
|
server { listen 80; server_name blog.example.com; root /www/blog; }
server { listen 80; server_name shop.example.com; root /www/shop; }
server { listen 80; server_name api.example.com;
location / { proxy_pass http://127.0.0.1:8080; }
}
|
路径分流 — 同一域名下不同模块
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| server {
server_name example.com;
location / { root /www/home; }
location /blog/ { proxy_pass http://127.0.0.1:8080/; }
location /api/ { proxy_pass http://127.0.0.1:4000/; }
location /static/ { root /www/assets; expires 30d; }
}
|
SSL/HTTPS 终结
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/ssl/certs/example.crt;
ssl_certificate_key /etc/ssl/private/example.key;
location / { proxy_pass http://my_backend; }
}
server {
listen 80;
server_name example.com;
return 301 https://$host$request_uri;
}
|
反向代理典型场景汇总
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21
| ┌─────────────┐
┌────────────────────┤ 静态文件直返 │ ← root /www
│ ├─────────────┤
用户 ──→ Nginx(80/443) ──────────┤ 博客 /blog/ │ ← proxy_pass :8080
│ ├─────────────┤
│ │ 商城 /shop/ │ ← proxy_pass :3000
│ ├─────────────┤
│ │ API /api/ │ ← proxy_pass upstream_pool
└────────────────────┤ 管理后台 │ ← allow 内网IP + proxy_pass
└─────────────┘
|
🎯 总结
本文覆盖了 Nginx 最实用的三个功能:
反向代理 — 你不需要知道后端在哪,Nginx 帮你去拿
负载均衡 — 多台后端轮流扛,配置不行还能加权
热重载 — 改配置无需停机,生产环境友好
学完这些,一台服务器上跑多个项目、横向扩容都不在话下。下一步可以深入的方向:
proxy_cache — 反向代理缓存,减轻后端压力
proxy_buffer — 缓冲调优,应对大文件场景
health_check — 自动检测后端健康状态,挂了自动踢掉
- Nginx + Docker Compose — 容器编排,一键拉起整张拓扑
limit_req / limit_conn — 限流防刷