反向代理与负载均衡
反向代理是 Nginx 在生产环境用得最多的功能——无论后端是 Node.js、Python、Java 还是 Go,前面挡一层 Nginx 几乎成了标配。这一章我们系统讲解:什么是反向代理、怎么用 proxy_pass 转发请求、如何透传客户端信息、以及用 upstream 实现负载均衡。
1. 正向代理 vs 反向代理:先理清概念
这俩词很容易混,记住一句话就行:
- 正向代理:代理的是客户端。例:科学上网、VPN、公司翻墙代理。客户端知道要访问的目标,代理替你请求。
- 反向代理:代理的是服务器。例:Nginx。客户端不知道真正的后端是谁,只看到 Nginx,由 Nginx 决定转发给谁。
对用户来说,访问 api.example.com 时只知道这个域名,背后是哪台机器、跑的什么语言,完全是黑盒——这就是反向代理。
2. proxy_pass:最基础的反向代理
反向代理的核心指令是 proxy_pass,它告诉 Nginx 把请求转发到哪个后端:
# 最基础的反向代理:把 /api 转发到本地后端
server {
listen 80;
server_name api.example.com;
location /api/ {
proxy_pass http://localhost:3000/;
# 注意结尾的 / 很关键(详见下文 URI 改写规则)
}
}
# 访问 http://api.example.com/api/users
# Nginx 会把请求转发到 http://localhost:3000/users
# (因为 proxy_pass 带 /,会把 /api/ 部分替换掉)这一段配置让 Nginx 把所有以 /api/ 开头的请求,转发到本机 3000 端口的应用(比如你启动的 Node.js Express 服务)。
3. proxy_pass 的 URI 改写规则(高频坑点)
proxy_pass 末尾带不带斜杠,行为完全不同。这是新手最容易踩的坑:
# proxy_pass 带 URI 与不带 URI 的行为完全不同!
# === 情况 A:proxy_pass 不带 URI(不带路径或只有 host:port)===
location /api/ {
proxy_pass http://backend;
# 请求 /api/users → 后端收到 /api/users(完整路径透传)
}
# === 情况 B:proxy_pass 带 URI ===
location /api/ {
proxy_pass http://backend/;
# 请求 /api/users → 后端收到 /users(替换 /api/ 为 /)
}
location /api/ {
proxy_pass http://backend/v2/;
# 请求 /api/users → 后端收到 /v2/users
}
# === 情况 C:用正则 location 时,proxy_pass 不能带 URI ===
location ~ ^/api/(.+)$ {
proxy_pass http://backend/$1; # 用捕获组手动拼装
# 请求 /api/users → 后端收到 /users
}
# 记忆口诀:带斜杠会替换,不带斜杠会拼接。记忆口诀:带斜杠会替换,不带斜杠会拼接。要保证后端收到的路径符合预期,务必搞清楚这点。
4. proxy_set_header:透传客户端信息
用了反向代理后,后端看到的"客户端 IP"永远是 Nginx 的 IP(比如 127.0.0.1),真实客户端 IP 丢失了。解决方法:通过 HTTP 头透传:
location /api/ {
proxy_pass http://localhost:3000;
# === 1. 透传客户端真实信息(最重要!)===
proxy_set_header Host $host; # 原始 Host 头
proxy_set_header X-Real-IP $remote_addr; # 客户端真实 IP
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; # 代理链
proxy_set_header X-Forwarded-Proto $scheme; # 客户端用的是 http 还是 https
# === 2. 超时控制 ===
proxy_connect_timeout 5s; # 连接后端超时
proxy_send_timeout 60s; # 发请求给后端超时
proxy_read_timeout 60s; # 读后端响应超时
# === 3. 缓冲区(影响大响应)===
proxy_buffering on; # 默认 on,开启后 Nginx 缓存完整响应再发给客户端
proxy_buffer_size 4k; # 第一块缓冲区大小(响应头)
proxy_buffers 8 4k; # 后续缓冲区数量和大小
# === 4. 上传文件大小 ===
client_max_body_size 20m; # 客户端请求体最大大小(默认只有 1m)
}这几个头的含义:
X-Real-IP:客户端真实 IP(后端日志/鉴权用它)。X-Forwarded-For:代理链路,每过一层代理追加一个 IP,逗号分隔。X-Forwarded-Proto:客户端用的协议(让后端知道原本是 HTTP 还是 HTTPS)。
后端代码要主动读取这些头才有用。Express/Koa 用 app.set('trust proxy', true),Django 配置 SECURE_PROXY_SSL_HEADER。
5. upstream:负载均衡的基础
当一台后端扛不住时,多搞几台机器分摊流量。upstream 指令定义一组后端服务器:
# === upstream:定义一组后端服务器(负载均衡的基础)===
upstream backend {
server 10.0.0.1:3000;
server 10.0.0.2:3000;
server 10.0.0.3:3000;
}
server {
listen 80;
location / {
proxy_pass http://backend; # 直接用 upstream 名字
}
}
# === 四种负载均衡算法 ===
# 1. 轮询(默认):依次循环
upstream backend {
server 10.0.0.1:3000;
server 10.0.0.2:3000;
}
# 2. 加权轮询:weight 越大,分配的请求越多(机器配置好的权重大)
upstream backend {
server 10.0.0.1:3000 weight=3; # 收到 3/4 的请求
server 10.0.0.2:3000 weight=1; # 收到 1/4 的请求
}
# 3. ip_hash:同一客户端 IP 始终落到同一台后端(解决 session 问题)
upstream backend {
ip_hash;
server 10.0.0.1:3000;
server 10.0.0.2:3000;
}
# 4. least_conn:把请求发给当前连接数最少的那台
upstream backend {
least_conn;
server 10.0.0.1:3000;
server 10.0.0.2:3000;
}6. 四种负载均衡算法对比
| 算法 | 命令 | 适用场景 | 优缺点 |
|---|---|---|---|
| 轮询 | (默认) | 后端机器配置相同 | 简单,不考虑负载差异 |
| 加权轮询 | weight=N | 后端机器配置不同 | 灵活,但权重需手动调 |
| IP 哈希 | ip_hash | 需保持 session 一致 | 解决 session 问题,但 IP 集中时负载不均 |
| 最少连接 | least_conn | 请求处理时间差异大 | 负载更均衡,但计算开销略大 |
现代建议:除非真的有 session 粘性问题,否则优先用 least_conn + 加权。Session 粘性更好的方案是把 session 存到 Redis,而不是依赖 ip_hash。
7. 健康检查:自动剔除宕机节点
负载均衡必须有健康检查,否则一台后端挂了,请求还会被分给它。Nginx 的健康检查:
upstream backend {
# 被动健康检查(开源版自带)
server 10.0.0.1:3000 max_fails=3 fail_timeout=30s;
server 10.0.0.2:3000 max_fails=3 fail_timeout=30s;
# max_fails=3 在 fail_timeout 时间内失败 3 次,标记为宕机
# fail_timeout=30s 宕机后 30 秒内不再发送请求,30s 后重试
# backup 备用服务器(只有主服务器全挂了才启用)
# down 永久下线(手动标记)
}
upstream backend {
server 10.0.0.1:3000;
server 10.0.0.2:3000;
server 10.0.0.3:3000 backup; # 仅当上面两台都挂掉时启用
}
# 注意:开源版 Nginx 只支持"被动检查"——
# 真正的"主动健康检查"(定期发请求探测后端)需要 Nginx Plus 商业版,
# 或用 lua / 第三方模块(如 nginx_upstream_check_module)。开源版只有被动健康检查——即"请求失败了才标记宕机"。主动探测(定期发 HTTP 请求检查)是 Plus 版功能,社区可以用 nginx_upstream_check_module 或 Lua 扩展。
8. 反向代理 WebSocket
WebSocket(如 Socket.io)的反向代理需要额外配置——它不是普通 HTTP,要先做协议升级:
# 反向代理 WebSocket(如 Socket.io、聊天室、实时推送)
server {
listen 80;
server_name ws.example.com;
location / {
proxy_pass http://localhost:3000;
proxy_http_version 1.1; # 必须 1.1
# WebSocket 升级头(关键!)
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
# 通用头
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
# WebSocket 长连接超时调长(默认 60s 会断开)
proxy_read_timeout 300s;
}
}关键三步:HTTP/1.1 → Upgrade 头 → 长超时。漏掉任何一个,WebSocket 都会连不上或频繁断开。
9. 一个完整的反向代理模板
整合上面所有内容,下面是一个可直接用的生产配置模板,把它存到团队 wiki 里:
- upstream 定义三台后端,加权轮询;
- 被动健康检查 max_fails=3 / fail_timeout=30s;
- 透传客户端 IP/Host/协议;
- 超时 60s,上传限制 20m;
- 静态资源(css/js/png)走本地 Nginx,不进后端;
- WebSocket 路径升级 HTTP/1.1。
改一改 IP 和域名,复制粘贴到 /etc/nginx/conf.d/,nginx -s reload 即可生效。
小结
反向代理 + 负载均衡是 Nginx 在云原生时代最核心的用法。这一章你学会了 proxy_pass、URI 改写规则、客户端信息透传、upstream 四种算法、健康检查、WebSocket 代理——这是后端工程师的核心竞争力。下一篇我们讲 HTTPS:怎么给站点装上小绿锁。
← 上一篇 location 匹配规则
下一篇 HTTPS 与 SSL →