反向代理与负载均衡
1. 概念与原理
正向代理代理客户端(VPN、公司出口代理)。反向代理代理服务端:用户连 Nginx,Nginx 转发给后端。后端对用户不可见。
前端用反向代理解决的问题:
- 把 Node SSR 应用、Python API、Java 服务 "套" 在 Nginx 之后,统一域名和 HTTPS
- 多个后端做负载均衡,单实例不挂
- 把
/api/*转发到后端、/服务静态,解决跨域和路由 - 加缓存、限流、压缩、鉴权统一处理
2. proxy_pass 基础
location /api/ {
proxy_pass http://127.0.0.1:3000/;
}
/api/users → http://127.0.0.1:3000/users
2.1 路径改写规则(最容易栽的坑)
# proxy_pass 带斜杠:截断 location 部分
location /api/ {
proxy_pass http://backend/; # /api/users → /users
}
# proxy_pass 不带斜杠:保留完整路径
location /api/ {
proxy_pass http://backend; # /api/users → /api/users
}
# location 用正则时 proxy_pass 不能带 URI 部分
location ~ ^/api/(.*)$ {
proxy_pass http://backend/$1$is_args$args;
}
记忆口诀:两个都带斜杠 = 截断,两个都不带 = 透传。混搭最容易出错。
2.2 必加的请求头
location /api/ {
proxy_pass http://backend;
proxy_http_version 1.1; # 默认 1.0,必须升到 1.1 才能 keep-alive
proxy_set_header 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
proxy_set_header X-Forwarded-Host $host;
proxy_set_header X-Forwarded-Port $server_port;
}
不写 Host 后端可能拿到 backend 而非 example.com,路由错乱。
不写 X-Forwarded-Proto 后端以为是 HTTP,跳转链接拼成 http://。
抽公共片段:
# /etc/nginx/snippets/proxy-params.conf
proxy_http_version 1.1;
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;
proxy_connect_timeout 5s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
# 业务 conf 里
location /api/ {
include snippets/proxy-params.conf;
proxy_pass http://backend;
}
3. upstream 与负载均衡
3.1 多后端
upstream backend {
server 10.0.0.1:8080;
server 10.0.0.2:8080;
server 10.0.0.3:8080;
keepalive 32; # 到上游的长连接池
}
server {
location /api/ {
proxy_pass http://backend;
}
}
3.2 负载均衡算法
upstream backend {
# 默认:轮询
server 10.0.0.1:8080;
# 加权轮询
server 10.0.0.1:8080 weight=3;
server 10.0.0.2:8080 weight=1;
# ip_hash:同 IP 总到同一后端(会话粘性,但 NAT 后所有用户同 IP)
ip_hash;
# least_conn:最少连接(适合长请求)
least_conn;
# hash:按指定 key 哈希(如 hash $cookie_jsessionid consistent;)
hash $request_uri consistent;
}
consistent 用一致性哈希,节点变化时只影响小部分映射,缓存命中率高。
3.3 故障转移
upstream backend {
server 10.0.0.1:8080 max_fails=3 fail_timeout=30s;
server 10.0.0.2:8080 max_fails=3 fail_timeout=30s;
server 10.0.0.3:8080 backup; # 备用,只在主全挂时启用
}
max_fails=3 fail_timeout=30s:30 秒内失败 3 次标记不可用,30 秒后重试。开源 Nginx 没主动健康检查,只在请求转发时被动判断。
3.4 主动健康检查(Nginx Plus / Tengine)
Nginx 开源版没有,需要:
- 商业 Nginx Plus
- Tengine(淘宝维护,免费)
- nginx-upstream-check-module(第三方)
- 或外部健康检查 + 动态改配置
4. keepalive 到上游
upstream backend {
server 10.0.0.1:8080;
keepalive 32; # 每 worker 保持 32 个空闲长连接
keepalive_requests 1000; # 每连接最多服务 1000 个请求后关闭
keepalive_timeout 60s;
}
location /api/ {
proxy_pass http://backend;
proxy_http_version 1.1; # 必须
proxy_set_header Connection ""; # 清空 Connection 头(默认 close)
}
不开 keepalive 到上游 = 每请求建一次 TCP,QPS 高时 TIME_WAIT 堆积、性能差 5-10 倍。
5. WebSocket 代理
location /ws/ {
proxy_pass http://backend;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_set_header Host $host;
proxy_read_timeout 3600s; # 长连接必调
proxy_send_timeout 3600s;
}
Upgrade 头是关键,少一个 WebSocket 升级失败。
6. 超时配置
proxy_connect_timeout 5s; # 与后端建立连接超时
proxy_send_timeout 60s; # 发请求超时
proxy_read_timeout 60s; # 读响应超时
proxy_read_timeout 默认 60s 对大多数 API 够,但长轮询、SSE、大文件下载必须调大:
location /api/stream {
proxy_read_timeout 3600s;
proxy_buffering off; # SSE 必须关 buffer,否则数据被攒着
}
7. proxy_buffering
默认开。Nginx 把上游响应完整读到 buffer 再发给客户端。优点:
- 慢客户端不占着上游连接
- 减少上游压力
缺点:
- 流式响应(SSE、chunked)被缓存,用户体验差
- 大响应可能落盘(
proxy_max_temp_file_size)
流式场景关掉:
proxy_buffering off;
或调小 buffer:
proxy_buffer_size 4k;
proxy_buffers 8 4k;
8. proxy_next_upstream
请求失败时换下一个上游:
proxy_next_upstream error timeout http_502 http_503 http_504;
proxy_next_upstream_tries 3;
proxy_next_upstream_timeout 10s;
慎用 http_500、non_idempotent:POST 重试可能造成业务重复。
9. 七层 vs 四层
| 层级 | 协议 | Nginx 模块 | 能力 |
|---|---|---|---|
| 七层 | HTTP/S | http | 看 URL、header、cookie,能做 path 路由、限流、缓存 |
| 四层 | TCP/UDP | stream | 只看 IP/端口,转发 TCP 流(如 MySQL、Redis) |
四层代理示例:
stream {
upstream mysql_backend {
server 10.0.0.1:3306;
server 10.0.0.2:3306;
}
server {
listen 3306;
proxy_pass mysql_backend;
}
}
前端基本只用七层。
10. 真实生产配置示例(Node SSR)
upstream node_app {
server 127.0.0.1:3000;
server 127.0.0.1:3001;
server 127.0.0.1:3002;
server 127.0.0.1:3003;
keepalive 64;
}
server {
listen 443 ssl http2;
server_name app.example.com;
include snippets/ssl-params.conf;
# 静态资源:直接 Nginx 服务
location /_next/static/ {
alias /var/www/app/.next/static/;
expires 1y;
add_header Cache-Control "public, immutable";
access_log off;
}
location /public/ {
alias /var/www/app/public/;
expires 7d;
}
# API 和 SSR:转发到 Node
location / {
proxy_pass http://node_app;
proxy_http_version 1.1;
proxy_set_header Connection "";
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;
proxy_connect_timeout 5s;
proxy_read_timeout 30s;
}
# WebSocket(HMR / 实时通知)
location /ws/ {
proxy_pass http://node_app;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";
proxy_read_timeout 3600s;
}
}
11. 故障排查
11.1 502 Bad Gateway
最常见:上游不可达。
tail -f /var/log/nginx/error.log
# connect() failed (111: Connection refused) while connecting to upstream
# → 后端没起 / 端口错
# upstream timed out (110: Connection timed out)
# → 后端慢 / 网络问题
# no live upstreams while connecting to upstream
# → 全部后端被标记 down
排查步骤:
# 1. 后端是否监听
ss -tnlp | grep :3000
# 2. Nginx 机器能否连后端
curl -v http://127.0.0.1:3000
# 3. 防火墙
iptables -L
11.2 504 Gateway Timeout
上游响应慢于 proxy_read_timeout。
- 看后端慢日志、性能
- 临时调大
proxy_read_timeout - 看是否后端线程池满了
11.3 上游 IP 拿到 127.0.0.1
后端日志里所有请求都是 127.0.0.1,因为没传 X-Real-IP。后端读这个 header 取真实 IP:
const ip = req.headers['x-forwarded-for']?.split(',')[0].trim()
|| req.headers['x-real-ip']
|| req.socket.remoteAddress
Express 用 app.set('trust proxy', 'loopback') 自动处理。
11.4 偶发 502 但后端没崩
往往是 keepalive 不一致:
- 上游主动断开 keepalive 连接
- Nginx 用该连接发请求,连接已死
- 重试机制不够
修复:
proxy_next_upstream error timeout http_502;
keepalive_requests 1000;
后端 keepalive timeout 应大于 Nginx 这边的,否则后端先断 Nginx 用死连接报 502。
12. 常见反模式
proxy_pass带斜杠混搭:路径错乱难排查- 不写
X-Real-IP:后端拿到 127.0.0.1,限流、审计、风控全错 proxy_http_version默认 1.0:keep-alive 不生效,性能差proxy_set_header Connection ""不写:keepalive 到上游不工作- SSE / 流式响应不关
proxy_buffering:用户看不到流式输出 - 超时一刀切:长查询、大下载用统一 60s 超时直接报 504
proxy_next_upstream含 POST:业务幂等性必须保证- WebSocket 没配 Upgrade 头:1006 错误
- 后端 keepalive timeout 小于 Nginx:经典 502
ip_hash用在 NAT 后:所有用户同 IP,负载严重倾斜