Nginx 负载均衡:upstream 配置与健康检查
一个后端扛不住就加第二个,Nginx 的 upstream 就是把请求分给多个后端。配置不复杂,但几个参数不对就会出现「有节点挂了还在往它发请求」。
一、最小配置
upstream backend {
server 127.0.0.1:3001;
server 127.0.0.1:3002;
}
server {
listen 80;
server_name api.bzii.cn;
location / {
proxy_pass http://backend;
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;
}
}二、调度策略
| 策略 | 写法 | 特点 |
|---|---|---|
| 轮询 | 默认 | 依次分配 |
| 加权轮询 | server ... weight=3; | 性能好的多分 |
| 最少连接 | least_conn; | 给当前连接最少的 |
| IP 哈希 | ip_hash; | 同一 IP 固定到同一后端 |
| 一致性哈希 | hash $request_uri consistent; | 按某字段哈希,节点变化影响小 |
| 随机 | random two least_conn; | 随机挑两个里选更优的 |
upstream backend {
least_conn;
server 127.0.0.1:3001 weight=3 max_fails=3 fail_timeout=30s;
server 127.0.0.1:3002 weight=1;
server 127.0.0.1:3003 backup; # 备用,主节点全挂才启用
server 127.0.0.1:3004 down; # 标记为下线,手动摘除
}参数含义:
| 参数 | 作用 |
|---|---|
weight=N | 权重,默认 1 |
max_fails=N | 连续失败 N 次认为不可用 |
fail_timeout=Ns | 判不可用后,多久再试;同时也是失败计数的窗口 |
backup | 备份节点 |
down | 永久下线 |
开源版 Nginx 没有主动健康检查(不会定时探测后端)。它靠
max_fails被动发现故障,也就意味着第一次请求会失败然后重试。商业版 Nginx Plus 才有主动检查。
三、失败重试
location / {
proxy_pass http://backend;
# 这些情况才交给下一个节点重试
proxy_next_upstream error timeout http_500 http_502 http_503 http_504;
proxy_next_upstream_tries 3;
proxy_next_upstream_timeout 10s;
proxy_connect_timeout 5s;
proxy_read_timeout 30s;
proxy_send_timeout 30s;
}| 参数 | 说明 |
|---|---|
proxy_next_upstream | 什么情况下换节点 |
proxy_next_upstream_tries | 最多试几次(含第一次) |
proxy_connect_timeout | 与后端建连超时,建议 ≤ 5s |
proxy_read_timeout | 等待后端响应超时 |
注意:POST 请求默认不会重试到别的节点(避免重复提交)。要允许需加
non_idempotent,但慎用。
四、长连接:否则性能上不去
默认 Nginx 到后端是短连接,每次请求都新建 TCP,高并发下会有大量 TIME_WAIT。
upstream backend {
server 127.0.0.1:3001;
server 127.0.0.1:3002;
keepalive 32; # 每个 worker 保持的空闲连接数
}
location / {
proxy_pass http://backend;
proxy_http_version 1.1; # 必须,1.0 不支持长连接
proxy_set_header Connection ""; # 清空,保持长连接
}# 看看是不是一堆 TIME_WAIT
ss -ant | awk '{print $1}' | sort | uniq -c | sort -rn五、会话保持
如果应用把 session 存在本地内存,用户必须固定到同一台:
upstream backend {
ip_hash; # 按客户端 IP 前三位哈希
server 127.0.0.1:3001;
server 127.0.0.1:3002;
}ip_hash 的问题:
- NAT 出口会导致大量用户聚到同一台;
- 节点上下线会重新哈希,会话丢失;
- 移动端 IP 会变。
更好的方案是让应用无状态:session 存 Redis,或用 JWT。这样想加节点就加节点。
六、把静态资源留在 Nginx 层
upstream backend {
server 127.0.0.1:3001;
}
server {
location / {
proxy_pass http://backend;
}
# 静态资源直接由 Nginx 返回,不打扰后端
location /static/ {
alias /var/www/app/static/;
expires 30d;
}
# 上传目录也直接给
location /uploads/ {
alias /var/www/app/uploads/;
}
}七、看负载是否均衡
给后端加个标记,在日志里看落到哪台:
log_format main '$remote_addr - $upstream_addr - $status - $upstream_response_time';
access_log /var/log/nginx/access.log main;# 统计每台后端接了多少请求
awk '{print $3}' /var/log/nginx/access.log | sort | uniq -c | sort -rn
# 看后端响应耗时
awk '{print $NF}' /var/log/nginx/access.log | sort -rn | head -10八、什么时候真的需要负载均衡
说实话,个人项目基本不需要。单台机器跑 Nginx + 应用能撑很大流量。
真正需要的信号:
- 单台 CPU 长期打满,且已经优化过;
- 需要滚动更新(多台轮流重启,服务不中断);
- 要求高可用(一台挂了还有别的)。
为了「看起来专业」而上负载均衡,只会多一层要维护的东西。
