Nginx 反向代理与负载均衡:从配置文件到生产实践
Nginx 是程序员绕不开的一台”瑞士军刀”。它既能当静态资源服务器,又能做反向代理、负载均衡,还常常兼任 API 网关。本文不讲泛泛的概念,只讲配置怎么写、参数怎么调,以及踩坑之后怎么办。
一、反向代理到底解决了什么
先搞清楚一件事:用户访问 https://api.example.com,背后可能站着好几台后端服务。反向代理就是站在用户和后端之间的那个”前台”,它对用户冒充服务端,对后端冒充客户端。
具体好处有四个:
- 统一入口:域名只有一个,端口不需要暴露一堆
- 负载分摊:请求按策略分流到多台机器
- 安全隔离:后端只监听内网,
/admin这种路径可以单独挡掉 - SSL 卸载:证书在 Nginx 这层解密,后端专心处理业务
这些都是”为什么”。更关键的是”怎么写”。
二、最基础的反向代理
一个最小可用的反向代理长这样:
# /etc/nginx/conf.d/api.conf
server {
listen 80;
server_name api.example.com;
location / {
proxy_pass http://127.0.0.1:8000;
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 是 Nginx 的地址,而不是用户原始域名;X-Forwarded-For 记录了真实客户端 IP,否则后端日志里全是 127.0.0.1。
有一个特别容易忽略的坑在 proxy_pass 的结尾斜杠上:
location /app/ {
proxy_pass http://backend:8000/; # 带斜杠:/app/user -> /user
}
location /app/ {
proxy_pass http://backend:8000; # 不带斜杠:/app/user -> /app/user
}
proxy_pass 后面有没有那个 /,决定了请求路径是”替换掉匹配前缀”还是”原样透传”。多一个斜杠,路径就变了,排查起来相当费神。我的习惯是永远写明确,并且用一段注释说明意图。
三、负载均衡:三种策略的取舍
有了多台后端,就要决定请求往哪儿发。upstream 块就是干这个的:
upstream backend {
server 10.0.0.1:8000 weight=3;
server 10.0.0.2:8000 weight=1;
server 10.0.0.3:8000 backup;
}
server {
listen 80;
location / {
proxy_pass http://backend;
}
}
默认是轮询(round-robin),配合 weight 可以做加权。backup 标记的机器平时不接流量,只有主机器全部挂掉才顶上。
第二种是最少连接(least_conn),适合后端处理耗时差异大的场景:
upstream backend {
least_conn;
server 10.0.0.1:8000;
server 10.0.0.2:8000;
}
第三种是 IP 哈希(ip_hash),保证同一个用户始终落到同一台机器,适用于有本地会话状态的旧系统:
upstream backend {
ip_hash;
server 10.0.0.1:8000;
server 10.0.0.2:8000;
}
说句实在话:现在新项目大多是无状态服务,ip_hash 用得越来越少,因为它一旦遇到某台机器宕机,哈希到它的用户全部要重新分配,还可能造成热点不均。能用无状态 + 集中式会话(Redis),就别依赖 ip_hash。
四、健康检查与故障转移
Nginx 对 upstream 里的机器有被动健康检查:请求连续失败多次,就把这台标记为不可用,过一段时间再试探。默认参数比较保守,可以根据业务调:
upstream backend {
server 10.0.0.1:8000 max_fails=3 fail_timeout=30s;
server 10.0.0.2:8000 max_fails=3 fail_timeout=30s;
}
max_fails=3 fail_timeout=30s 的意思是:30 秒内失败 3 次,就摘掉这台机器,等 30 秒后重新尝试。这个数值要贴合实际——设太严,偶发抖动就摘机器;设太松,故障机器会拖慢整个集群。
需要注意的是,被动检查只能在下一次有流量进来时才发现故障。如果要求毫秒级的快速摘除,得用 nginx-plus 或者第三方的主动健康检查模块。对绝大多数团队来说,被动检查 + 合理的超时配置已经够用。
五、几个让人半夜报警的参数
反向代理默认有几个超时值,是线上事故的常见源头:
location / {
proxy_pass http://backend;
proxy_connect_timeout 5s; # 连后端超时
proxy_read_timeout 60s; # 读后端响应超时
proxy_send_timeout 60s; # 发请求体超时
}
proxy_connect_timeout默认 75 秒,太长会导致抖动时请求堆积。proxy_read_timeout默认 60 秒,如果你的接口有长耗时任务(导出报表、AI 生成),一定要调大,否则会提前504 Gateway Timeout。- 反过来,如果后端是纯 API,希望快速失败,就把它调小。
再补两个高频配置:
location / {
proxy_pass http://backend;
proxy_buffering off; # 关闭缓冲,适合 SSE / 流式响应
proxy_http_version 1.1; # 长连接
proxy_set_header Connection "";
}
如果你在做 Server-Sent Events(SSE)或者流式大模型输出,proxy_buffering 默认开启会把响应攒在缓冲区,导致客户端收不到逐字的输出。关掉它,再配合 X-Accel-Buffering: no 响应头,流式体验才会顺畅。
六、负载均衡下的会话一致性
很多业务要求”同一个用户尽量访问同一台机器”。前面提过 ip_hash,但它有明显的副作用。更灵活的办法是用 cookie 粘滞(需要 nginx-plus 或 sticky 模块):
# 需编译 sticky 模块,非开源默认支持
upstream backend {
sticky cookie srv_id expires=1h;
server 10.0.0.1:8000;
server 10.0.0.2:8000;
}
不过在动手之前先问自己一句:真的需要粘滞吗? 大多数时候,把会话状态放到 Redis、把文件放到对象存储,让应用真正无状态,才是更省心的解法。粘滞是一颗”看起来好用”的药,长期看反而会掩盖架构上的状态耦合。
七、一个完整的生产配置示例
把上面这些串起来,一个相对完整的 api.example.com 配置大致是这样:
upstream backend_pool {
least_conn;
server 10.0.0.1:8000 max_fails=3 fail_timeout=30s;
server 10.0.0.2:8000 max_fails=3 fail_timeout=30s;
server 10.0.0.3:8000 backup;
keepalive 32; # 到后端的空闲长连接数
}
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/nginx/ssl/api.crt;
ssl_certificate_key /etc/nginx/ssl/api.key;
location / {
proxy_pass http://backend_pool;
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 60s;
# 流式接口(如 SSE)单独关缓冲
}
location /stream {
proxy_pass http://backend_pool;
proxy_buffering off;
proxy_read_timeout 3600s;
proxy_set_header Connection "";
proxy_http_version 1.1;
}
}
keepalive 32 这行常被忽略,它告诉 Nginx 维持多少个到后端的空闲连接,能显著减少 TCP 握手开销,对高并发短请求的 API 尤其有效。
八、小结
Nginx 反向代理和负载均衡,本质上就是三件事:把请求转发出去、把请求分发均匀、把故障机器摘掉。配置本身不难,难的是那几个决定”成不成功”的细节:
| 关注点 | 关键配置 | 常见坑 |
|---|---|---|
| 路径转发 | proxy_pass 结尾斜杠 |
路径被意外替换或透传 |
| 真实 IP | X-Forwarded-For |
后端日志全是 127.0.0.1 |
| 会话一致 | ip_hash / sticky |
掩盖状态耦合,热点不均 |
| 超时控制 | connect/read/send_timeout |
长任务被 504 截断 |
| 流式响应 | proxy_buffering off |
SSE 输出被缓冲 |
记住一句话:反向代理的配置要围着”超时”和”缓冲”这两个词打转。搞清楚请求在每一跳会不会超时、响应在每一层会不会被缓冲,Nginx 的问题就解决了一大半。
本文基于 Nginx 1.24+ 稳定版编写,配置在 Ubuntu 20.04/22.04 下验证可用。