Nginx 反向代理与负载均衡配置实战后端从一个 Node 进程扩到三个,发现流量还是全打在第一个上;或者上线新版本时,直接改端口导致用户请求 502 断了一片。这些问题的答案几乎都指向同一个组件:Nginx。这篇不讲概念堆砌,直接把「反向代理怎么配、负载均衡几种策略怎么选、健康检查和灰度怎么做」一次讲透,配置都能直接抄去改。先分清:正向代理 vs 反向代理一句话区分:正向代理代理的是客户端(你连 VPN 出墙,服务端不知道真实的你);反向代理代理的是服务端(用户访问api.example.com,不知道背后是哪台机器在处理)。我们做后端部署,用的全是反向代理。反向代理帮我们做三件事:隐藏后端拓扑、统一入口(SSL 卸载、限流、鉴权都放这层)、把流量分发到多个后端(负载均衡)。最小可用的反向代理假设后端服务跑在本机127.0.0.1:3000,要让外网通过 80 端口访问: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到这个组: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. 加权轮询——机器配置不均时用。给强的机器更高权重:upstream backend { server 127.0.0.1:3000 weight3; # 这台性能好,分 3 倍流量 server 127.0.0.1:3001 weight1; server 127.0.0.1:3002 weight1; }2. least_conn(最少连接)——请求处理时长差异大时用。谁当前连接少就给谁,避免慢请求堆在一台上:upstream backend { least_conn; server 127.0.0.1:3000; server 127.0.0.1:3001; }3. ip_hash(按客户端 IP 哈希)——需要会话保持时用。同一个 IP 固定打到同一台后端,适合把 session 存在本机内存、又没上 Redis 共享 session 的老系统: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 哈希做缓存亲和:upstream backend { hash $request_uri consistent; # consistent 用一致性哈希,扩缩容时抖动小 server 127.0.0.1:3000; server 127.0.0.1:3001; }健康检查:别把请求发给死掉的后端开源版 Nginx 没有主动健康检查(定时探活),但有被动健康检查,通过max_fails和fail_timeout实现:upstream backend { server 127.0.0.1:3000 max_fails3 fail_timeout30s; server 127.0.0.1:3001 max_fails3 fail_timeout30s; server 127.0.0.1:3002 backup; # 备用机,主力全挂了才启用 }含义:某台后端在fail_timeout30s内连续失败max_fails3次,就把它标记为不可用,30 秒内不再往它发请求,之后再试探性放行。backup标记的机器平时不接流量,只有主力全部不可用时才顶上。配合超时参数一起用,避免一台慢机器拖垮请求: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按比例分流:# 按请求特征(这里用客户端 IPUA)哈希,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; } }同一个用户(同 IPUA)会稳定命中同一版本,不会一会新一会旧。观察 canary 的错误率和延迟没问题,再把比例逐步调大到 100%。改完配置怎么安全生效千万别nginx -s stop nginx重启,那会断掉正在处理的连接。正确流程:nginx-t# 先测试配置语法,报错就别 reloadnginx-sreload# 平滑重载:老 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_failsfail_timeout,配backup备用机;proxy_next_upstream自动重试,但对 POST 要小心重复提交。灰度用split_clients按比例哈希分流,同用户稳定命中同版本。改配置永远先nginx -t再nginx -s reload,平滑重载不断连接。一句话记忆:upstream 管「分给谁」,proxy_set_header 管「怎么转」,-t 再 reload 管「安全生效」——三件事配对了,反向代理就稳了。