Nginx 反向代理与负载均衡配置实战
后端从一个 Node 进程扩到三个,发现流量还是全打在第一个上;或者上线新版本时,直接改端口导致用户请求 502 断了一片。这些问题的答案几乎都指向同一个组件:Nginx。这篇不讲概念堆砌,直接把「反向代理怎么配、负载均衡几种策略怎么选、健康检查和灰度怎么做」一次讲透,配置都能直接抄去改。
先分清:正向代理 vs 反向代理
一句话区分:正向代理代理的是客户端 (你连 VPN 出墙,服务端不知道真实的你);反向代理代理的是服务端 (用户访问 api.example.com,不知道背后是哪台机器在处理)。我们做后端部署,用的全是反向代理。
反向代理帮我们做三件事:隐藏后端拓扑、统一入口(SSL 卸载、限流、鉴权都放这层)、把流量分发到多个后端(负载均衡)。
最小可用的反向代理
假设后端服务跑在本机 127.0.0.1:3000,要让外网通过 80 端口访问:
nginx
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://127.0.0.1:3000;
# 下面这几行不是可选项,是必配
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_set_header。不配会踩什么坑?
- 不传
Host:后端按域名做路由/多租户时会拿到错的 Host,甚至默认变成127.0.0.1。 - 不传
X-Real-IP/X-Forwarded-For:后端拿到的客户端 IP 全是 Nginx 的127.0.0.1,你的访问日志、限流、风控全废。 - 不传
X-Forwarded-Proto:后端不知道用户走的是 HTTPS,生成的重定向/绝对 URL 会变成 http,造成死循环或混合内容。
$proxy_add_x_forwarded_for 会自动把当前的 X-Forwarded-For 追加上游 IP,形成一条 IP 链,比手写 $remote_addr 更正确。
负载均衡:upstream 是核心
要把流量分到多台后端,用 upstream 块定义一组服务器,再 proxy_pass 到这个组:
nginx
upstream backend {
server 127.0.0.1:3000;
server 127.0.0.1:3001;
server 127.0.0.1:3002;
}
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://backend; # 注意这里是 upstream 的名字
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
}
}
默认策略是轮询(round-robin),请求依次分给三台。这也是开头那个「流量全打第一个」问题的解法------之前没写 upstream,自然不会分发。
四种负载均衡策略怎么选
轮询不是唯一选择,Nginx 内置几种,按场景选:
1. 加权轮询------机器配置不均时用。给强的机器更高权重:
nginx
upstream backend {
server 127.0.0.1:3000 weight=3; # 这台性能好,分 3 倍流量
server 127.0.0.1:3001 weight=1;
server 127.0.0.1:3002 weight=1;
}
2. least_conn(最少连接)------请求处理时长差异大时用。谁当前连接少就给谁,避免慢请求堆在一台上:
nginx
upstream backend {
least_conn;
server 127.0.0.1:3000;
server 127.0.0.1:3001;
}
3. ip_hash(按客户端 IP 哈希)------需要会话保持时用。同一个 IP 固定打到同一台后端,适合把 session 存在本机内存、又没上 Redis 共享 session 的老系统:
nginx
upstream backend {
ip_hash;
server 127.0.0.1:3000;
server 127.0.0.1:3001;
}
注意 ip_hash 有代价:一旦某台后端下线,落在它上面的所有会话会被重新哈希到别的机器,session 丢失。更推荐的做法还是把 session 外置(Redis),让后端无状态,这样就能自由用轮询了。
4. hash 指定 key------比如按 URL 哈希做缓存亲和:
nginx
upstream backend {
hash $request_uri consistent; # consistent 用一致性哈希,扩缩容时抖动小
server 127.0.0.1:3000;
server 127.0.0.1:3001;
}
健康检查:别把请求发给死掉的后端
开源版 Nginx 没有主动健康检查(定时探活),但有被动健康检查 ,通过 max_fails 和 fail_timeout 实现:
nginx
upstream backend {
server 127.0.0.1:3000 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3001 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3002 backup; # 备用机,主力全挂了才启用
}
含义:某台后端在 fail_timeout=30s 内连续失败 max_fails=3 次,就把它标记为不可用,30 秒内不再往它发请求,之后再试探性放行。backup 标记的机器平时不接流量,只有主力全部不可用时才顶上。
配合超时参数一起用,避免一台慢机器拖垮请求:
nginx
location / {
proxy_pass http://backend;
proxy_connect_timeout 2s; # 建连超过 2s 判失败,快速切下一台
proxy_read_timeout 30s; # 读响应超时
proxy_next_upstream error timeout http_502 http_503; # 遇到这些自动重试下一台
}
proxy_next_upstream 很关键:当前后端返回 502/503 或超时,Nginx 会自动把这个请求转给 upstream 里的下一台,用户无感知。但要注意------非幂等请求(POST)默认也会重试 ,可能导致重复下单,生产上通常要 proxy_next_upstream error timeout; 去掉对 http_5xx 的重试,或明确排除 POST。
灰度发布:用 upstream 做流量切分
上线新版本想先放 10% 流量试水,可以用 split_clients 按比例分流:
nginx
# 按请求特征(这里用客户端 IP+UA)哈希,10% 打到新版本
split_clients "${remote_addr}${http_user_agent}" $pool {
10% canary;
* stable;
}
upstream stable { server 127.0.0.1:3000; server 127.0.0.1:3001; }
upstream canary { server 127.0.0.1:3010; }
server {
listen 80;
location / {
proxy_pass http://$pool; # 变量决定打到哪个 upstream
proxy_set_header Host $host;
}
}
同一个用户(同 IP+UA)会稳定命中同一版本,不会一会新一会旧。观察 canary 的错误率和延迟没问题,再把比例逐步调大到 100%。
改完配置怎么安全生效
千万别 nginx -s stop && nginx 重启,那会断掉正在处理的连接。正确流程:
bash
nginx -t # 先测试配置语法,报错就别 reload
nginx -s reload # 平滑重载:老 worker 处理完存量请求再退出,不断连接
nginx -t 是上线前的保命符------一个花括号没闭合,reload 会失败但老配置还在跑,而如果你直接重启就真挂了。养成先 -t 再 reload 的肌肉记忆。
小结
- 反向代理必配
proxy_set_header:Host、X-Real-IP、X-Forwarded-For、X-Forwarded-Proto,漏了会导致后端拿错 IP、错协议。 - 负载均衡用
upstream:默认轮询;机器不均用weight,请求时长差异大用least_conn,要会话保持用ip_hash(但更推荐 session 外置做无状态)。 - 被动健康检查 用
max_fails+fail_timeout,配backup备用机;proxy_next_upstream自动重试,但对 POST 要小心重复提交。 - 灰度用
split_clients按比例哈希分流,同用户稳定命中同版本。 - 改配置永远先
nginx -t再nginx -s reload,平滑重载不断连接。
一句话记忆:upstream 管「分给谁」,proxy_set_header 管「怎么转」,-t 再 reload 管「安全生效」------三件事配对了,反向代理就稳了。