nginx upstream 配置速查:负载均衡算法、server 指令参数与 keepalive 三件套
第一次配置 nginx upstream 的时候,我对着官方文档里那一堆参数看了 20 分钟,还是不知道该配几个。轮询就够了,还是得考虑权重?max_fails 到底配几秒?keepalive 明明加了,为什么 ss -tanp 里还是每次新建连接?
这三个问题问得越多的人越多。写这篇速查的时候我也翻了 nginx101、CSDN 上各种总结——但都是讲一个点,缺一份"从零到生产"的完整模板。
先想清楚:upstream 要解决哪三个问题
upstream 块不是简单的"列几个后端 IP"。它同时解决三件事:
- 怎么分流量(负载均衡算法)
- 每个后端配多少"预算"(server 指令参数)
- 怎么跟后端复用 TCP 连接(keepalive 三件套)
三件事任何一件没配好,都会在生产环境里出怪事。
负载均衡算法:6 种,先选一种再谈优化
nginx 支持 6 种算法,但生产环境 90% 用前两种。先选对算法,比调参数重要 10 倍。
| 算法 | 语法 | 适用场景 | 不适用场景 |
|---|---|---|---|
| 轮询(默认) | 无 | 后端无状态,性能差不多 | 有 session 的场景 |
| 加权轮询 | weight=N |
后端性能差异大 | 后端性能差异 <20% 时意义不大 |
| IP 哈希 | hash $remote_addr consistent |
会话保持,同一用户固定后端 | 后端节点少会不均 |
| 最少连接 | least_conn |
后端处理时间差异大(长任务) | 短请求、突发流量 |
| 一致性哈希 | hash $key consistent |
缓存命中率高优先 | 无缓存场景 |
| URL 哈希 | hash $request_uri |
静态资源缓存到固定后端 | 用户会话保持 |
决策树:
- 后端是无状态的短请求(大部分 API)→ 加权轮询(默认)
- 有 session,用户登录态不能丢 → IP 哈希 加
consistent关键字 - 后端是长短任务混合(比如既有查询又有导出)→ least_conn
- 后端有本地缓存 → 一致性哈希 加
hash $request_uri consistent - 其他 → 别折腾,用默认轮询
server 指令:10 个参数逐个说
server 指令是 upstream 里最复杂的一行。10 个参数按"日常必配"和"进阶"分两档。
日常必配(4 个)
weight=N(默认 1):流量权重,weight=3表示这台机器分 3/4 的流量max_conns=N(默认 1024):这台机器最大并发连接数,超过就不接新连接。生产建议显式配,防单台被打爆backup:这台机器当备份,只有前面所有 server 都 down 时才接流量down:这台机器不参与流量分配,但配置保留(用于灰度下线)
进阶参数(6 个)
server 10.0.0.11:8080
weight=3
max_conns=200
max_fails=3 # 连续失败 3 次就判定 down
fail_timeout=30s # 判定 down 后 30 秒才重新接流量
resolve # 域名支持动态解析(配合 upstream 变量用)
proxy_protocol # 透传客户端真实 IP(云 LB 前面常用)
max_fails + fail_timeout 是最重要的健康检查。nginx 不做主动健康检查(要用 nginx plus 才有),全靠被动的连接失败次数判断。默认值 max_fails=1 fail_timeout=10s 太激进,一次连接失败就屏蔽 10 秒。生产建议:
- 敏感接口:
max_fails=3 fail_timeout=30s - 后台任务:
max_fails=5 fail_timeout=60s - 短生命周期服务:
max_fails=1 fail_timeout=5s(快速失败快速恢复)
keepalive 三件套:最容易漏配的一件事
这是本文最核心的一段。90% 的 "nginx upstream keepalive 不生效" 问题都出在这里。
keepalive 需要三行配置同时出现,缺一行就白配:
# ① upstream 块里声明连接池大小
upstream backend {
server 10.0.0.11:8080;
server 10.0.0.12:8080;
keepalive 32; # 每 worker 保留 32 条空闲连接
keepalive_timeout 65s; # 空闲连接保持 65 秒
keepalive_requests 1000; # 单条连接最多处理 1000 请求
}
# ② location 里强制 HTTP/1.1
location / {
proxy_http_version 1.1; # 必须!HTTP/1.0 不支持长连接
proxy_set_header Connection ""; # 必须!清空 Connection 头
proxy_pass http://backend;
}
三行少任何一行都会导致 keepalive 无效:
- 少
proxy_http_version 1.1→ Nginx 用 HTTP/1.0,长连接直接不支持 - 少
proxy_set_header Connection ""→ 客户端的Connection: close头会透传到后端,后端主动关连接 - 少
keepalive 32→ 没有连接池,每次请求都新建 TCP
keepalive 数值怎么定?
keepalive 32 这个 32 不是随手写的。每 worker 每后端进程保留 N 条空闲连接,实际总连接数 = worker_processes × upstream server 数 × keepalive N。
估算公式:
例:1000 QPS、后端 200ms、4 个 worker:
- keepalive N ≈ 1000 × 0.2 / 4 = 50
- 保守一点配 keepalive 64
别贪心配 512:keepalive 越大越可能耗尽 upstream 连接数,导致真正需要新建连接时反而被拒。
怎么验证 keepalive 真的生效了?
配置后 3 秒验证命令:
# 1. 用 ab 打 100 个请求,看请求耗时是不是明显下降
ab -c 10 -n 100 http://nginx.example.com/
# 2. 看后端连接池,ESTAB 数应该是几十条而不是每次都是新连接
ss -tanp | grep ':8080' | wc -l
# 3. 打开 nginx access_log 看 upstream_response_time 是不是变短
tail -f /var/log/nginx/access.log
你应该看到什么:
- ab 里 Time per request 从几十毫秒降到几毫秒(keepalive 命中)
- ss 里同一后端 IP 只有几十条 ESTAB 连接(复用成功)
- 如果不生效,ab 前后耗时差不多,ss 里连接数会短暂飙升到几百再降下来
3 个真实踩坑案例
踩坑 1:keepalive 加了但 ab 前后没差别
症状:upstream 里配了 keepalive 32,location 里也配了 proxy_http_version 1.1,但 ab -c 20 -n 1000 前后请求耗时没差别,后端 ss 里连接数依然飙升。
排查:nginx -T | grep -E 'keepalive|proxy_http_version|proxy_set_header' 看合并后的配置。
根因:少配了 proxy_set_header Connection ""。客户端发的 Connection: close 头被透传到后端,后端直接 close(fd)。
修复:加第三行 proxy_set_header Connection "";。
注意:这行是清空,不是删除。写成 proxy_set_header Connection close; 会强制所有请求都是短连接。
踩坑 2:max_fails=1 导致后端被误判 down
症状:后端机器偶尔网络抖动一次,nginx 把它屏蔽了 10 秒,用户 10 秒内所有请求都打到剩余机器,触发雪崩。
根因:默认 max_fails=1 太激进。
修复:max_fails=3 fail_timeout=30s。要求连续 3 次失败才判定 down,给网络抖动的缓冲。
延伸:nginx 免费版没有主动健康检查(health_check 是 nginx plus 专属)。真需要主动健康检查的话,要么上 nginx plus,要么用 external health check(比如 Prometheus blackbox 探测 + nginx reload)。
踩坑 3:一致性哈希配了但流量还是不均
症状:用了 hash $request_uri consistent,但看 access_log 里流量分布还是接近平均,缓存命中率没提升。
排查:$request_uri 是整个 URI 路径。如果后端缓存是按 API 前缀分区的(比如 /api/v1/xxx 全部缓存到同一台),URI 差异让 hash 分散了。
修复:改 hash 键:
upstream backend {
hash $request_uri consistent;
server ...;
}
# 或按 API 版本分区
location /api/v1/ {
proxy_pass http://backend_v1;
}
upstream backend_v1 {
hash $host consistent; # 同一域名走同一台
}
教训:一致性哈希的 hash 键选什么比算法本身更重要。想清楚"什么粒度的请求应该落到同一台"。
一份可直接抄的生产模板
upstream backend {
# 6 台后端,4 主 2 备
server 10.0.0.11:8080 weight=3 max_conns=200 max_fails=3 fail_timeout=30s;
server 10.0.0.12:8080 weight=3 max_conns=200 max_fails=3 fail_timeout=30s;
server 10.0.0.13:8080 weight=3 max_conns=200 max_fails=3 fail_timeout=30s;
server 10.0.0.14:8080 weight=3 max_conns=200 max_fails=3 fail_timeout=30s;
server 10.0.0.21:8080 backup max_conns=100;
server 10.0.0.22:8080 backup max_conns=100;
# keepalive 三件套的第 ① 部分
keepalive 64;
keepalive_timeout 65s;
keepalive_requests 1000;
}
server {
listen 80;
server_name api.example.com;
location / {
proxy_http_version 1.1; # keepalive 三件套第 ② 部分
proxy_set_header Connection ""; # keepalive 三件套第 ③ 部分
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_connect_timeout 5s;
proxy_read_timeout 30s;
proxy_send_timeout 30s;
proxy_pass http://backend;
}
# 排障时看一眼 upstream 状态
location /nginx_status {
stub_status;
allow 127.0.0.1;
deny all;
}
}
Cheat Sheet:12 条速查命令
| # | 命令 | 用途 |
|---|---|---|
| 1 | nginx -T 2>&1 \| grep -A 20 'upstream' |
看合并后的 upstream 完整配置 |
| 2 | nginx -T 2>&1 \| grep -E 'keepalive|proxy_http_version' |
验证 keepalive 三件套是否齐全 |
| 3 | curl http://localhost/nginx_status |
看 active connections / handled requests |
| 4 | ss -tanp \| grep :8080 |
看 Nginx 与后端的连接状态 |
| 5 | ss -tan \| grep ESTAB \| wc -l |
看当前 ESTAB 连接总数 |
| 6 | ab -c 20 -n 1000 http://nginx/ |
压测 keepalive 是否生效 |
| 7 | tail -f /var/log/nginx/error.log \| grep upstream |
实时看 upstream 报错 |
| 8 | awk '$(NF-1)==502 {print}' access.log \| wc -l |
统计最近 502 数量 |
| 9 | tail access.log \| awk '{print $7, $9}' \| sort \| uniq -c |
看不同 URI 的响应码分布 |
| 10 | curl -o /dev/null -w "%{time_connect} %{time_starttransfer}" -I http://nginx/ |
看连接建立和首字节耗时 |
| 11 | nginx -s reload |
热加载 upstream 配置 |
| 12 | lsmod \| grep nginx |
看 nginx 内核模块(少见,用来看 kprobe 类扩展) |
收尾:什么情况下不要调 upstream
写 upstream 配置最容易陷入的坑是过度优化。这些情况下别碰 upstream:
- QPS 低于 100:nginx 默认轮询完全够用,调权重调 keepalive 都是白费力气
- 后端就 1 台:upstream 只有一行
server,配什么算法都一样 - 有外部 LB(阿里云 SLB / AWS ALB):LB 前面已经做过一次负载均衡,nginx upstream 里再配一遍是重复劳动
upstream 优化真正的价值在中大规模(QPS 500+ 或后端 4 台以上)。小场景把时间花在后端代码和数据库上更有回报。