Nginx 反向代理与负载均衡实战:upstream、真实 IP 与四层七层选型
Nginx 反向代理与负载均衡实战:upstream、真实 IP 与四层七层选型
把后端从一台扩到多台只是加两个 IP 的事,真正麻烦的是加完之后冒出来的一堆问题:后端日志里全是负载均衡的 IP、出了 502 不知道该看哪张日志、两个后端”用户登录状态随机丢失”。这篇把反向代理和负载均衡这一层该配的东西整理一遍。
一、正代理和反代理,一句话分清
区别在服务的对象:正向代理代理客户端(客户端主动配置代理,代表是翻墙工具和企业上网代理),反向代理代理服务端(客户端以为在直连,实际请求被 Nginx 转发给后端)。运维日常干的都是后者。
对后端的价值就三条:统一入口(后端扩缩容用户无感知)、分摊压力(多节点负载)、安全缓冲(后端不直接暴露公网,可在入口层做限流、WAF、HTTPS 卸载)。
二、proxy_pass 的参数才是关键
最简配置一行就够,但那只是开始:
1 | location / { |
1. 请求头透传(不配就是给自己挖坑)
1 | proxy_set_header Host $http_host; # 保留用户访问的域名 |
不配 Host 的后果很典型:后端一台服务器挂了多个虚拟主机,Nginx 转发过去都用默认站点匹配,用户访问 A 网站结果出来了 B 网站。不配 X-Forwarded-For 的后果是后端业务日志里所有请求都来自代理 IP,”哪些用户在访问”这个问题直接没法回答。
2. 超时与缓冲
1 | proxy_connect_timeout 30s; # 与后端建立连接的超时 |
经验值:一般接口 60s 够用;导出类接口要单独放宽;proxy_read_timeout 调大之前先想清楚——它只是把”超时失败”变成”用户等更久”,后端真慢还是要治后端。
3. WebSocket 和 HTTPS 的代理
1 | # WebSocket:必须带 Upgrade 头,否则握手失败 |
这套参数我统一放进了 /etc/nginx/proxy_params,业务配置里一行 include proxy_params; 复用,改参数只改一处。
三、upstream:负载均衡核心
1 | upstream web_pool { |
几个参数的生产含义:
| 参数 | 作用 | 建议 |
|---|---|---|
| weight | 权重,按比例分发 | 新机器先给小权重观察 |
| max_fails / fail_timeout | 失败 N 次后摘除,多久后重试 | 默认 1 次太敏感,生产 2~3 次 |
| backup | 备用节点,全挂才启用 | 容灾必备,成本允许就配 |
| down | 手动下线 | 维护时改配置,不删节点 |
| keepalive | 与后端的长连接池 | 每次请求都重连后端时 QPS 上不去,优先调这个 |
调度算法上,轮询和权重能覆盖大多数场景;ip_hash 是会话保持的兜底方案(有副作用,见下文);least_conn 在请求耗时差异大时更均衡。
健康检查
Nginx 自带的只有被动检查(max_fails)——后端挂了,要等真实请求失败才会摘除。想要主动探测得用第三方模块 nginx_upstream_check_module,编译进 Nginx:
1 | upstream web { |
预算够的话更省心的方案是云上负载均衡(SLB/CLB)或 LVS+Keepalived,健康检查是内置的;用 Nginx 就得接受”编译第三方模块”这一步。
四、真实 IP:多级代理下怎么拿到用户地址
只要经过一层代理,后端看到的 $remote_addr 就变成了代理的 IP。分两种情况:
后端是 Nginx:用 realip 模块把 XFF 头里的真实 IP 还原出来。
1 | server { |
之后日志里的 $remote_addr 就是真实客户端 IP 了。
后端是业务程序:程序框架一般有约定,从 X-Forwarded-For 取第一个地址。要提醒开发注意:XFF 是客户端可以伪造的头字段,只有前面每一层代理都追加过才是可信的——所以要配合 set_real_ip_from 限定”只信任已知代理写入的部分”。
五、四层还是七层
- 七层(Nginx http/upstream):能解析 HTTP,按域名、URL、header 分流,能做会话保持、rewrite、缓存——但性能比四层低;
- 四层(Nginx stream 或 LVS):只转发 TCP 包,不解析内容,性能最高,适合数据库、SSH 这类非 HTTP 服务,或者大流量入口。
Nginx 做四层需要单独配置在 http 块外面:
1 | # /etc/nginx/conf.c/lb.conf |
典型用途就是数据库代理:业务连 10.0.8.4:6666,实际打到 MySQL 的 3306,后端数据库不用对外暴露真实端口。
六、502/504/499:入口层的三个高频故障
| 状态码 | 含义 | 排查方向 |
|---|---|---|
| 502 | Nginx 连不上后端 | 后端进程没了/端口不通/防火墙 |
| 504 | 后端响应超时 | 后端慢(慢 SQL、死锁、GC) |
| 499 | 客户端等不及先断了 | 后端处理慢,用户侧超时 |
502 的标准排查路径:
1 | curl -I http://www.example.com # 先确认现象 |
另外有个配置值得加上,能让”单台后端偶发 500”不至于把整个请求打回用户:
1 | proxy_next_upstream error timeout http_500 http_502 http_503 http_504; |
意思是这台后端返回 5xx 时,把请求转给下一台重试——请求成功率立竿见影,但要注意:非幂等操作(下单、支付)不能开,重试可能造成重复提交,这个必须跟开发对齐后再加。
七、会话保持:优先改应用,别硬锁 IP
ip_hash 是最省事的方案,但有两个坑:同一出口 IP 的用户(公司网络、代理)全压到同一台后端;单节点故障时,那部分用户的登录态全丢。
更稳的方案是把会话外置,让任何一台后端都能识别登录态:
- PHP 应用:
session.save_handler = redis,session 存 Redis(后面 LNMP 那篇展开); - Java 应用:Tomcat 集群的 Redis 会话管理器。
选型上的建议:短期救急用 ip_hash,长期一定走统一会话存储——扩容、发版、缩容都不会再被它绊住。
八、小结
反向代理和负载均衡这一层的配置量不大,但每个参数背后都是一次生产教训:Host 不透传会串站、XFF 不透传日志全废、keepalive 不配性能上不去、proxy_next_upstream 乱开会产生重复下单。我的习惯是把 proxy_params 固化成一份模板,新站点直接 include,参数变更走一次压测验证——入口层稳定了,后面的排障压力会小很多。


