Nginx 负载均衡:upstream 配置与健康检查

一个后端扛不住就加第二个,Nginx 的 upstream 就是把请求分给多个后端。配置不复杂,但几个参数不对就会出现「有节点挂了还在往它发请求」。

一、最小配置

nginx
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;随机挑两个里选更优的
nginx
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 才有主动检查。

三、失败重试

nginx
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。

nginx
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 "";   # 清空,保持长连接
}
bash
# 看看是不是一堆 TIME_WAIT
ss -ant | awk '{print $1}' | sort | uniq -c | sort -rn

五、会话保持

如果应用把 session 存在本地内存,用户必须固定到同一台:

nginx
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 层

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/;
    }
}

七、看负载是否均衡

给后端加个标记,在日志里看落到哪台:

nginx
log_format main '$remote_addr - $upstream_addr - $status - $upstream_response_time';
access_log /var/log/nginx/access.log main;
bash
# 统计每台后端接了多少请求
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 长期打满,且已经优化过;
  • 需要滚动更新(多台轮流重启,服务不中断);
  • 要求高可用(一台挂了还有别的)。

为了「看起来专业」而上负载均衡,只会多一层要维护的东西。