跳到主要内容

反向代理与负载均衡

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/usershttp://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_500non_idempotent:POST 重试可能造成业务重复。

9. 七层 vs 四层

层级协议Nginx 模块能力
七层HTTP/Shttp看 URL、header、cookie,能做 path 路由、限流、缓存
四层TCP/UDPstream只看 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,负载严重倾斜

13. 延伸阅读