Administrator
Administrator
Published on 2026-05-13 / 16 Visits
0
1

Nginx负载均衡策略与健康检查


1. 负载均衡概述

Nginx 通过 upstream 模块将客户端请求分发到多台后端服务器,实现高可用和水平扩展。

基本架构:

客户端 → Nginx (反向代理 + 负载均衡) → 后端服务器集群
                                         ├── Server A (192.168.1.10:8080)
                                         ├── Server B (192.168.1.11:8080)
                                         └── Server C (192.168.1.12:8080)

最小配置结构:

http {
    # 定义后端服务器组
    upstream my_backend {
        server 192.168.1.10:8080;
        server 192.168.1.11:8080;
        server 192.168.1.12:8080;
    }

    server {
        listen 80;
        server_name example.com;

        location / {
            proxy_pass http://my_backend;
        }
    }
}

2. 六大负载均衡策略详解

2.1 轮询(Round Robin)— 默认策略

原理: 按顺序逐一将请求分配给每台后端服务器。

适用场景: 后端服务器性能相近,请求处理耗时均匀。

upstream backend_rr {
    # 不需要额外指令,轮询是默认行为
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}

请求分配示意:

请求1 → Server A
请求2 → Server B
请求3 → Server C
请求4 → Server A  (循环)
请求5 → Server B
...

2.2 加权轮询(Weighted Round Robin)

原理: 在轮询基础上,按 weight 值分配更多请求给高性能服务器。权重越大,分到的请求越多。

适用场景: 后端服务器硬件配置不同(如一台 16 核、一台 4 核)。

upstream backend_weighted {
    server 192.168.1.10:8080 weight=5;  # 高配机器,分 5 份
    server 192.168.1.11:8080 weight=3;  # 中配机器,分 3 份
    server 192.168.1.12:8080 weight=1;  # 低配机器,分 1 份
}

请求分配示意(每 9 个请求):

Server A 接收 5 个请求 (权重5)
Server B 接收 3 个请求 (权重3)
Server C 接收 1 个请求 (权重1)

2.3 IP Hash

原理: 根据客户端 IP 地址计算哈希值,将同一 IP 的请求始终转发到同一台后端服务器。

适用场景: 需要会话保持(Session Sticky),如未使用 Redis 集中存储 Session 的应用。

upstream backend_iphash {
    ip_hash;
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}

注意事项:

  • 如果某台服务器需要临时下线,使用 down 标记而不是删除,否则哈希结果会变,导致所有用户 Session 丢失
  • 客户端经过代理(如 CDN)时,IP 可能相同,导致分配不均
upstream backend_iphash_maintain {
    ip_hash;
    server 192.168.1.10:8080;
    server 192.168.1.11:8080 down;  # 临时下线,保持哈希表稳定
    server 192.168.1.12:8080;
}

2.4 最少连接数(Least Connections)

原理: 将新请求分配给当前活动连接数最少的服务器。

适用场景: 请求处理时间差异大(如有的接口耗时 50ms,有的耗时 5s),避免慢请求堆积在某台服务器。

upstream backend_least_conn {
    least_conn;
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}

也可以结合权重使用:

upstream backend_least_conn_weighted {
    least_conn;
    server 192.168.1.10:8080 weight=5;
    server 192.168.1.11:8080 weight=3;
    server 192.168.1.12:8080 weight=1;
}

2.5 通用 Hash(Generic Hash)

原理: 根据自定义的 key(如 URI、请求参数)计算哈希值来分配请求。

适用场景: 缓存场景——确保相同 URL 始终命中同一台后端,提高缓存命中率。

upstream backend_hash {
    hash $request_uri consistent;  # consistent 启用一致性哈希
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}

consistent 关键字的作用:

模式 增减服务器时 说明
普通 hash 大量请求重新映射 缓存大面积失效
consistent hash 仅影响相邻节点 缓存失效最小化

按请求参数哈希:

upstream backend_hash_arg {
    hash $arg_user_id consistent;  # 按 user_id 参数分配
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}

2.6 Random(随机)— Nginx 1.15.1+

原理: 随机选择服务器。可配合 two 参数实现"两次随机选择中取最优"(P2C 算法)。

适用场景: 多级负载均衡架构中,避免多个 Nginx 实例同时选择同一台后端。

upstream backend_random {
    random two least_conn;  # 随机选 2 台,取连接数最少的那台
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}

策略对比总结

策略 会话保持 性能差异适配 缓存友好 配置复杂度 推荐场景
轮询 最低 无状态 + 同配置服务器
加权轮询 无状态 + 异构服务器
IP Hash 需要 Session 保持
最少连接 请求耗时差异大
通用 Hash 可选 缓存代理
Random two 多级 LB 架构

3. 健康检查与故障节点自动剔除

参考:https://blog.csdn.net/jveqi/article/details/151290943

3.1 被动健康检查(Nginx 开源版内置)

Nginx 通过实际请求的响应结果判断后端是否健康。无需额外模块,开箱即用,是 Nginx 开源版默认支持的健康检查方式。

⚠️ 关键理解:被动检查既不是 TCP 探测端口,也不是 ping,它完全不会主动发送任何探测请求。 它依赖真实的客户端流量——当一个真实请求被转发到后端时,Nginx 观察这次请求的成功与否来判断后端是否健康。

3.1.1 核心参数详解

参数 含义 默认值
max_fails fail_timeout 时间窗口内,与后端通信的连续失败次数超过此值,Nginx 将该节点标记为不可用 1
fail_timeout 双重含义: ①统计 max_fails 的时间窗口;②节点被标记不可用后,持续的不可用时间,超过此时间后 Nginx 会再次尝试向该节点发送请求 10s
proxy_next_upstream 定义哪些情况视为"失败",并触发将请求转发到下一台后端服务器 error timeout

3.1.2 什么情况被判定为"失败"?

失败的判定由 proxy_next_upstream 指令控制。可配置的失败类型如下:

失败类型 具体情况
error 与后端建立连接、发送请求或读取响应时发生错误(连接被拒绝、网络不可达等)
timeout 与后端建立连接、发送请求或读取响应时超时(受 proxy_connect_timeout 等参数控制)
invalid_header 后端返回了空的或无效的响应头
http_500 后端返回 500 Internal Server Error
http_502 后端返回 502 Bad Gateway
http_503 后端返回 503 Service Unavailable
http_504 后端返回 504 Gateway Timeout
http_403 后端返回 403 Forbidden
http_404 后端返回 404 Not Found
http_429 后端返回 429 Too Many Requests
non_idempotent 允许对非幂等请求(POST/LOCK/PATCH)也进行重试(默认只重试幂等请求)
off 禁用转发到下一台服务器的功能

默认值是 error timeout,即只有连接错误和超时才算失败。 如果希望后端返回 502/503/504 也触发重试和失败计数,必须显式配置。

3.1.3 配置示例

http {
    upstream backend_passive_check {
        # 在30秒内,连续失败3次,则该节点被标记为不可用30秒
        server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
        server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
        server 192.168.1.12:8080 max_fails=3 fail_timeout=30s;
    }

    server {
        listen 80;
        location / {
            proxy_pass http://backend_passive_check;

            # 定义哪些情况视为失败,触发重试到下一台服务器
            proxy_next_upstream error timeout http_500 http_502 http_503 http_504;

            # 重试的最大次数(0 = 不限制,尝试所有服务器)
            proxy_next_upstream_tries 3;

            # 所有重试的总耗时上限
            proxy_next_upstream_timeout 10s;
        }
    }
}

3.1.4 故障剔除与自动恢复流程

                         ┌──────────────────────┐
                         │  正常转发用户请求       │
                         └──────────┬───────────┘
                                    │
                                    ▼
                    ┌───────────────────────────────┐
                    │  请求转发到 Server A            │
                    │  观察响应结果                    │
                    └───────────────┬───────────────┘
                                    │
                         ┌──────────┴──────────┐
                         │                     │
                    成功响应                 失败响应
                         │              (error/timeout/5xx)
                         │                     │
                         ▼                     ▼
                  fails 计数清零         fails 计数 + 1
                         │                     │
                         │                     ▼
                         │         ┌────────────────────────┐
                         │         │ fail_timeout 内          │
                         │         │ fails >= max_fails ?     │
                         │         └─────┬──────────┬────────┘
                         │               │          │
                         │              否          是
                         │               │          │
                         │               ▼          ▼
                         │          继续正常     ┌──────────────────┐
                         │          转发请求     │ 标记 Server A     │
                         │                     │ 为 "不可用"        │
                         │                     │ 停止转发请求到该节点 │
                         │                     └────────┬─────────┘
                         │                              │
                         │                              ▼
                         │                    ┌───────────────────┐
                         │                    │ 等待 fail_timeout  │
                         │                    │ 时间到期            │
                         │                    └────────┬──────────┘
                         │                             │
                         │                             ▼
                         │                   ┌────────────────────┐
                         │                   │ 发送一个真实请求     │
                         │                   │ 到 Server A 试探     │
                         │                   └───┬────────────┬───┘
                         │                       │            │
                         │                    成功           失败
                         │                       │            │
                         ▼                       ▼            ▼
                  ┌────────────┐         Server A 恢复   继续标记不可用
                  │ 正常工作    │         重新加入负载池   等待下一个周期
                  └────────────┘

用时间线举例说明(max_fails=3 fail_timeout=30s):

时间  事件                                          Server A 状态
───────────────────────────────────────────────────────────────────
0s    请求1 → Server A → 502 错误 (fails=1)         正常(1/3 失败)
5s    请求2 → Server A → 连接超时 (fails=2)          正常(2/3 失败)
8s    请求3 → Server A → 503 错误 (fails=3)          ✗ 标记不可用
8-38s 所有请求跳过 Server A,转发到 B 和 C            ✗ 不可用(冷却期)
38s   Nginx 用一个真实请求试探 Server A               探测中...
38s   → 如果成功                                     ✓ 恢复!重新接收请求
38s   → 如果失败                                     ✗ 继续不可用,再等 30s

3.1.5 被动检查的优点与局限性

维度 说明
优点 开源版默认支持,无需安装任何模块;配置简单,只需 max_fails + fail_timeout
局限1 滞后性:必须等真实请求失败才能发现故障,第一批用户请求会受影响
局限2 低流量盲区:没有真实流量就无法检测,凌晨等低流量时段故障可能长时间不被发现
局限3 恢复探测也是用真实请求,不是专门的健康探针,可能影响该用户的体验
局限4 无法区分"服务完全宕机"和"服务偶尔慢",只能通过调整阈值缓解误判

因此,生产环境强烈建议被动检查 + 主动检查结合使用。 被动检查作为兜底,主动检查实现秒级发现故障。


3.2 主动健康检查(需要第三方模块)

参考:https://juejin.cn/post/7231395869689282617

主动健康检查会定期向后端发送探测请求,无需等待真实流量触发。当后端服务器发生故障或宕机时,模块会自动将该服务器从负载均衡池中移除,直到该服务器恢复正常工作。

方案一:nginx_upstream_check_module(推荐,开源)

模块简介

nginx_upstream_check_module 是 Nginx 的第三方模块,可以定期向后端服务器发送 HTTP/TCP 请求,检测后端服务器的健康状况,并根据检测结果动态地调整负载均衡策略,从而保证后端服务器的可用性和稳定性。

项目地址:https://github.com/yaoweibin/nginx_upstream_check_module

安装步骤(完整流程)

第一步:下载 Nginx 源码和模块

# 下载 Nginx 源码(以 1.20.2 为例)
wget https://nginx.org/download/nginx-1.20.2.tar.gz
tar zxvf nginx-1.20.2.tar.gz

# 下载 nginx_upstream_check_module
wget https://github.com/yaoweibin/nginx_upstream_check_module/archive/refs/tags/v0.4.0.tar.gz \
     -O nginx_upstream_check_module-0.4.0.tar.gz
tar zxvf nginx_upstream_check_module-0.4.0.tar.gz

第二步:打补丁(关键步骤,不同 Nginx 版本对应不同补丁)

# 安装 patch 工具(如果没有)
yum install -y patch

# 切换到 Nginx 源码目录
cd nginx-1.20.2

# 打补丁(根据 Nginx 版本选择对应的 patch 文件)
patch -p1 < /path/to/nginx_upstream_check_module-0.4.0/check_1.20.1+.patch

补丁版本对照: 在模块目录下有多个 .patch 文件,按 Nginx 版本选择:

  • Nginx 1.20.x → check_1.20.1+.patch
  • Nginx 1.18.x → check_1.16.1+.patch
  • Nginx 1.14.x → check_1.14.0+.patch
  • 其他版本查看模块 README

第三步:编译安装

# 配置(--add-module 指向解压后的模块目录)
./configure --prefix=/usr/local/nginx \
            --add-module=/path/to/nginx_upstream_check_module-0.4.0

apt-get install zlib1g zlib1g-dev apt-get install libpcre3 libpcre3-dev

# 编译并安装
make && make install

第四步:验证安装

# 检查配置语法(如果能识别 check 指令说明模块安装成功)
/usr/local/nginx/sbin/nginx -t

# 启动 Nginx
/usr/local/nginx/sbin/nginx

# 验证可用
curl http://localhost:80
check 指令参数详解
check interval=3000 rise=2 fall=3 timeout=1000 type=http;
参数 含义 示例值 说明
interval 检查间隔时间(毫秒) 3000 每 3 秒检查一次
rise 连续成功次数阈值 2 连续 2 次探测成功 → 标记为健康,重新加入负载池
fall 连续失败次数阈值 3 连续 3 次探测失败 → 标记为不健康,从负载池中剔除
timeout 单次探测的超时时间(毫秒) 1000 1 秒内没有响应视为失败
type 检查协议类型 http 支持多种协议
port 指定检查端口(可选) 8080 不指定则使用 upstream 中定义的端口
default_down 初始状态(可选) true true=启动时默认不可用,需探测成功后才接收流量
支持的检查类型(type 参数)
类型 说明 适用场景
tcp 仅检查 TCP 端口是否可连接 最基础,适用于任何 TCP 服务
http 发送 HTTP 请求,检查响应状态码 Web 服务,推荐
ssl_hello 发送 SSL Client Hello 并检查 Server Hello 响应 HTTPS 后端,不需要证书验证
mysql 检查 MySQL 服务是否可连接 MySQL 数据库代理
ajp 检查 AJP 协议(Tomcat) Tomcat 后端
TCP 检查配置

最简单的检查方式,仅验证端口是否能连通:

upstream backend_tcp_check {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;

    # 每 3 秒检查一次 TCP 连接
    # 连续失败 3 次剔除,连续成功 2 次恢复
    check interval=3000 rise=2 fall=3 timeout=2000 type=tcp;
}

TCP 检查的局限: 端口可连通不代表服务正常(如应用内部死锁、数据库连接池耗尽等)。生产环境推荐 HTTP 检查。

HTTP 检查配置(推荐)

发送真实的 HTTP 请求到后端的健康检查接口,验证应用层是否正常:

upstream backend_http_check {
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;

    # HTTP 健康检查
    check interval=3000 rise=2 fall=3 timeout=2000 type=http;

    # 探测请求内容(必须包含完整的 HTTP 请求头)
    check_http_send "GET /health HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n";

    # 期望的响应状态码:2xx 或 3xx 视为健康
    check_http_expect_alive http_2xx http_3xx;
}

check_http_send 格式说明:

"请求方法 请求路径 HTTP版本\r\nHost: 主机名\r\nConnection: close\r\n\r\n"

注意事项:

  • 末尾必须有 \r\n\r\n(空行),表示 HTTP 请求头结束
  • 推荐使用 GET 方法 + HTTP/1.1,兼容性最好
  • Connection: close 避免健康检查占用长连接

check_http_expect_alive 可选值:

说明
http_2xx 200-299 状态码视为健康
http_3xx 300-399 状态码视为健康
http_4xx 400-499 状态码视为健康
http_5xx 500-599 状态码视为健康(一般不用)
SSL 后端检查配置
upstream backend_ssl_check {
    server 192.168.1.10:443;
    server 192.168.1.11:443;

    # SSL Hello 检查,仅验证 SSL 握手是否正常
    check interval=3000 rise=2 fall=3 timeout=2000 type=ssl_hello;
}
健康状态监控页面

配置一个状态页面,可以实时查看所有后端节点的健康状态:

server {
    listen 8888;

    location /upstream_status {
        check_status;              # 输出 HTML 状态页
        access_log off;

        # 安全限制:仅允许内网访问
        allow 10.0.0.0/8;
        allow 172.16.0.0/12;
        allow 192.168.0.0/16;
        deny all;
    }
}

访问 http://nginx-ip:8888/upstream_status 即可查看各节点状态。

状态页面输出格式支持:

URL 格式
/upstream_status HTML 页面(默认)
/upstream_status?format=json JSON 格式(适合程序解析)
/upstream_status?format=csv CSV 格式
主动检查的故障剔除与恢复流程
         ┌──────────────────────────────┐
         │ Nginx 启动,开始定时探测       │
         │ 每隔 interval 毫秒发送一次     │
         └──────────────┬───────────────┘
                        │
                        ▼
            ┌───────────────────────┐
            │ 向 Server A 发送探测    │
            │ (TCP连接 或 HTTP请求)   │
            └─────┬───────────┬─────┘
                  │           │
               成功          失败/超时
                  │           │
                  ▼           ▼
          成功计数 +1     失败计数 +1
          失败计数清零     成功计数清零
                  │           │
                  ▼           ▼
         成功 >= rise?   失败 >= fall?
            │     │        │     │
           是    否       是    否
            │     │        │     │
            ▼     ▼        ▼     ▼
      标记为健康  继续   标记为不健康 继续
      加入负载池  探测   从负载池剔除 探测

与被动检查的关键区别:

维度 被动检查 主动检查
探测方式 依赖真实用户请求 独立的定时探测请求
发现速度 滞后(需等待失败请求) 秒级(interval 可配)
用户影响 第一批请求会失败 故障在用户请求前就被发现
低流量时段 可能长时间无法发现故障 不受流量影响,持续探测
恢复方式 用真实请求试探 专门的探测请求,不影响用户
模块依赖 无(内置) 需要第三方模块或商业版

方案二:Nginx Plus 商业版(内置 health_check 指令)

upstream backend_plus {
    zone backend_zone 64k;  # 共享内存区域(必需)
    server 192.168.1.10:8080;
    server 192.168.1.11:8080;
    server 192.168.1.12:8080;
}

server {
    location / {
        proxy_pass http://backend_plus;
        health_check interval=5s fails=3 passes=2 uri=/health;
    }
}

3.3 被动 + 主动检查结合(推荐生产方案)

upstream backend_combined {
    server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
    server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
    server 192.168.1.12:8080 max_fails=3 fail_timeout=30s;

    # 主动检查(需 nginx_upstream_check_module)
    check interval=5000 rise=2 fall=3 timeout=3000 type=http;
    check_http_send "GET /health HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n";
    check_http_expect_alive http_2xx;
}

双重保障:

  • 主动检查:每 5 秒探测一次,及时发现宕机节点
  • 被动检查:即使主动检查间隔内节点突然故障,真实请求失败也会触发剔除

3.4 备用服务器(Backup)

当所有主服务器都不可用时,请求转发到备用服务器。

upstream backend_with_backup {
    server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
    server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
    server 192.168.1.12:8080 backup;  # 备用服务器,正常情况不接收请求
}

⚠️ 注意: ip_hash 策略下 backup 参数无效。


3.5 后端应用健康检查接口示例

后端应用需提供一个轻量健康检查接口,以下是各语言示例:

Python (Flask):

@app.route('/health')
def health_check():
    # 检查数据库连接、Redis 连接等关键依赖
    try:
        db.session.execute('SELECT 1')
        return 'OK', 200
    except Exception:
        return 'Service Unavailable', 503

Java (Spring Boot):

@RestController
public class HealthController {
    @GetMapping("/health")
    public ResponseEntity<String> health() {
        return ResponseEntity.ok("OK");
    }
}

Node.js (Express):

app.get('/health', (req, res) => {
    res.status(200).send('OK');
});

3.6 Upstream 容错机制详解

参考:https://www.cnblogs.com/kevingrace/p/8185218.html

3.6.1 Nginx 判断节点失效状态的规则

Nginx 默认以 connect refuse(连接拒绝)timeout(超时) 状态作为判断节点失效的依据,不以 HTTP 错误状态码作为失败判定标准——因为只要后端能返回 HTTP 响应(即使是 500/502/503 等错误码),说明该节点网络层面仍然可达,Nginx 默认认为它是存活的。

错误计数规则:

状态 是否计入 fails 是否需要额外配置
timeout(超时) 否,默认记录
connect refuse(连接拒绝) 否,默认记录
502 Bad Gateway ✗ → ✓ 需配置 proxy_next_upstream http_502
500 Internal Server Error ✗ → ✓ 需配置 proxy_next_upstream http_500
503 Service Unavailable ✗ → ✓ 需配置 proxy_next_upstream http_503
504 Gateway Timeout ✗ → ✓ 需配置 proxy_next_upstream http_504
404 Not Found 即使配置了 proxy_next_upstream http_404,也不计入错误数
其他 HTTP 状态码(200/301/403 等) 不记录为失败

⚠️ 关键点: timeoutconnect refuse 永远会被记录为错误状态;502/500/503/504 只有在配置了 proxy_next_upstream 后才会被记录到 fails 计数中;404 比较特殊——配置了 proxy_next_upstream http_404 后会触发请求转发到下一台,但不会累加到 fails 错误数中。

3.6.2 节点失效与恢复的触发条件

fails 达到 max_fails 设定值后,Nginx 将该节点标记为失效,在 fail_timeout 时间内不再向该节点转发请求:

upstream backend {
    # 在 30s 内失败 3 次 → 节点被标记不可用 30s
    # 30s 后 Nginx 重新尝试向该节点发送请求
    server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
    server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
}

完整状态转换:

正常 → fails 累加(在 fail_timeout 窗口内)→ fails >= max_fails → 标记失效
                                                                      │
                                                                      ▼
                                                          等待 fail_timeout 到期
                                                                      │
                                                                      ▼
                                                          用真实请求试探该节点
                                                            │              │
                                                          成功            失败
                                                            │              │
                                                            ▼              ▼
                                                     恢复为正常       重新标记失效
                                                     重新接收流量     再等 fail_timeout

3.6.3 所有节点失效后的处理

当所有后端节点(包括备用节点)均被标记为失效时,Nginx 不会直接返回错误,而是:

  1. 重置所有节点为有效状态,重新开始探测
  2. 将请求尝试转发到各节点
  3. 如果某个节点能正常响应,则返回正确内容
  4. 如果所有节点仍然无法响应,则返回 502 Bad Gateway
  5. 下次请求时继续尝试探测,直到找到可用节点
所有节点失效 → 全部恢复为有效 → 重新逐一探测
                                  │
                        ┌─────────┴─────────┐
                        │                   │
                    发现可用节点         全部仍然失败
                        │                   │
                        ▼                   ▼
                   返回正常响应         返回 502 错误
                                     下次请求继续探测

这个机制确保了即使 Nginx 误判(如网络瞬时抖动导致所有节点被标记失效),系统也能自动恢复,而不是永久拒绝服务。

3.6.4 proxy_next_upstream 的容错与重复处理问题

参考【non_idempotent】 https://zhuanlan.zhihu.com/p/35

场景一:容错——HTTP 错误码也触发自动转发
location / {
    proxy_pass http://backend;
    
    # 当后端返回这些错误时,自动将请求转发到下一台服务器
    proxy_next_upstream http_500 http_502 http_503 http_504 http_404;
    proxy_next_upstream_tries 3;
    proxy_next_upstream_timeout 10s;
}

适用于前台展示类请求,优先保证用户能看到正常页面。

场景二:关闭自动转发——防止重复处理
location /api/order/ {
    proxy_pass http://backend;
    
    # 关闭自动转发!
    proxy_next_upstream off;
}

为什么要关闭?

因为 proxy_next_upstream 默认值为 error timeout,当请求到达 Server A 且 A 返回超时时,Nginx 会将同一个请求重新转发到 Server B。如果这个请求是创建订单、扣款、发短信等非幂等操作,就会导致:

用户下单 → Nginx → Server A(处理中,但响应超时)→ Nginx 判定超时
                                                       │
                                                       ▼
                                                  转发到 Server B
                                                  Server B 再次处理
                                                       │
                                                       ▼
                                               结果:订单被创建了两次!

最佳实践——按路由区分策略:

upstream backend {
    server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
    server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
    server 192.168.1.12:8080 max_fails=3 fail_timeout=30s;
}

server {
    # 读操作(GET 请求为主):开启容错转发
    location /api/query/ {
        proxy_pass http://backend;
        proxy_next_upstream error timeout http_502 http_503 http_504;
        proxy_next_upstream_tries 3;
        proxy_next_upstream_timeout 10s;
    }

    # 写操作(POST 请求为主):关闭自动转发,防止重复提交
    location /api/order/ {
        proxy_pass http://backend;
        proxy_next_upstream off;
    }
    
    # 或者只允许幂等请求重试(默认行为,POST/PATCH/DELETE 不重试)
    location /api/ {
        proxy_pass http://backend;
        # 注意:不加 non_idempotent,POST 等非幂等方法默认不会被转发到下一台
        proxy_next_upstream error timeout http_502 http_503 http_504;
    }
}

补充说明: Nginx 默认只对幂等请求(GET/HEAD/PUT/DELETE/OPTIONS/TRACE)进行 proxy_next_upstream 转发。POST/LOCK/PATCH 等非幂等方法默认不转发,除非显式加上 non_idempotent 参数。这是 Nginx 对重复处理问题的一层内置保护。

3.6.5 超时参数详解

Nginx 可以针对不同 location 独立配置超时时间,根据业务特点灵活调整:

location / {
    proxy_pass http://backend;
    
    # 与后端建立 TCP 连接的超时时间(发起握手等候响应)
    proxy_connect_timeout 5s;    # 默认 60s
    
    # 等待后端响应的超时时间(请求已发出,等待后端处理和返回)
    proxy_read_timeout 30s;      # 默认 60s
    
    # 向后端发送请求体的超时时间(后端必须在规定时间内接收完数据)
    proxy_send_timeout 10s;      # 默认 60s
}
参数 含义 默认值 触发场景
proxy_connect_timeout 与后端建立 TCP 连接的超时 60s 后端宕机、端口未监听、网络不通
proxy_read_timeout 等待后端返回响应的超时 60s 后端处理慢、死锁、资源耗尽
proxy_send_timeout 向后端发送请求的超时 60s 后端接收缓慢、带宽不足

⚠️ proxy_connect_timeout 最好不要超过 75 秒(Linux 内核 TCP 连接超时默认值),设置更大没有意义。

生产建议: 这三个参数的默认值都是 60s,在后端宕机时会导致用户请求卡住很久。务必根据业务实际情况调小(参见 5.1 节)。

3.6.6 Upstream 相关变量(日志排查必备)

log_format 中加入以下变量,可以方便地排查负载均衡和容错问题:

变量 说明 示例值
$upstream_addr 实际处理请求的后端服务器地址 192.168.1.10:8080
$upstream_status 后端服务器返回的 HTTP 状态码 200502
$upstream_response_time 后端服务器的响应时间(秒),精确到毫秒 0.025
$upstream_connect_time 与后端建立连接的耗时 0.002
$upstream_header_time 接收到后端响应头的耗时 0.018
$upstream_cache_status 缓存命中状态(使用 proxy_cache 时) HITMISSEXPIRED

推荐日志格式:

log_format upstream_log '$remote_addr - [$time_local] "$request" $status '
                        'upstream_addr=$upstream_addr '
                        'upstream_status=$upstream_status '
                        'upstream_response_time=$upstream_response_time '
                        'request_time=$request_time';

access_log /var/log/nginx/access.log upstream_log;

容错转发时的日志表现:

当请求经历了多次转发时,$upstream_addr$upstream_status 会以逗号分隔记录每次尝试:

# 请求先到 10:8080(返回 502),然后被转发到 11:8080(返回 200)
upstream_addr=192.168.1.10:8080, 192.168.1.11:8080
upstream_status=502, 200
upstream_response_time=0.003, 0.025

 ---

4. 生产环境综合配置示例

4.1 完整的 nginx.conf

user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log warn;
pid /var/run/nginx.pid;

events {
    worker_connections 10240;
    use epoll;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    # ========== 日志格式(包含 upstream 信息用于排查)==========
    log_format main '$remote_addr - $remote_user [$time_local] '
                    '"$request" $status $body_bytes_sent '
                    '"$http_referer" "$http_user_agent" '
                    'upstream=$upstream_addr '
                    'upstream_status=$upstream_status '
                    'upstream_response_time=$upstream_response_time '
                    'request_time=$request_time';

    access_log /var/log/nginx/access.log main;

    sendfile        on;
    tcp_nopush      on;
    tcp_nodelay     on;
    keepalive_timeout 65;
    gzip on;

    # ========== 后端服务器组定义 ==========

    # 场景1:API 服务 — 加权轮询 + 被动健康检查
    upstream api_servers {
        server 10.0.1.10:8080 weight=5 max_fails=3 fail_timeout=30s;
        server 10.0.1.11:8080 weight=3 max_fails=3 fail_timeout=30s;
        server 10.0.1.12:8080 weight=1 max_fails=3 fail_timeout=30s;
        server 10.0.1.20:8080 backup;  # 备用节点

        # 主动健康检查(需 nginx_upstream_check_module)
        check interval=3000 rise=2 fall=3 timeout=2000 type=http;
        check_http_send "GET /health HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n";
        check_http_expect_alive http_2xx;

        # 与后端保持长连接,减少 TCP 握手开销
        keepalive 32;
    }

    # 场景2:WebSocket 服务 — IP Hash 保持连接
    upstream ws_servers {
        ip_hash;
        server 10.0.2.10:9090 max_fails=2 fail_timeout=15s;
        server 10.0.2.11:9090 max_fails=2 fail_timeout=15s;
        server 10.0.2.12:9090 max_fails=2 fail_timeout=15s;
    }

    # 场景3:静态资源/缓存 — 一致性哈希
    upstream static_servers {
        hash $request_uri consistent;
        server 10.0.3.10:80 max_fails=2 fail_timeout=20s;
        server 10.0.3.11:80 max_fails=2 fail_timeout=20s;
        server 10.0.3.12:80 max_fails=2 fail_timeout=20s;
    }

    # 场景4:长耗时任务 — 最少连接
    upstream task_servers {
        least_conn;
        server 10.0.4.10:8080 max_fails=3 fail_timeout=30s;
        server 10.0.4.11:8080 max_fails=3 fail_timeout=30s;
    }

    # ========== 虚拟主机配置 ==========

    server {
        listen 80;
        server_name api.example.com;

        # API 接口
        location /api/ {
            proxy_pass http://api_servers;

            # 代理头信息传递
            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_send_timeout 10s;      # 发送请求超时
            proxy_read_timeout 30s;      # 读取响应超时

            # 失败重试策略
            proxy_next_upstream error timeout http_502 http_503 http_504;
            proxy_next_upstream_tries 3;
            proxy_next_upstream_timeout 10s;

            # 使用 HTTP/1.1 保持长连接
            proxy_http_version 1.1;
            proxy_set_header Connection "";
        }

        # WebSocket
        location /ws/ {
            proxy_pass http://ws_servers;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection "upgrade";
            proxy_set_header Host $host;
            proxy_read_timeout 3600s;  # WebSocket 长连接超时
        }

        # 静态资源
        location /static/ {
            proxy_pass http://static_servers;
            proxy_cache_valid 200 1d;
            expires 7d;
        }

        # 长耗时任务
        location /task/ {
            proxy_pass http://task_servers;
            proxy_read_timeout 300s;   # 长任务超时 5 分钟
            proxy_next_upstream error timeout;
            proxy_next_upstream_tries 2;
        }
    }

    # ========== 健康检查状态页(仅内网可访问)==========
    server {
        listen 8888;
        allow 10.0.0.0/8;
        allow 172.16.0.0/12;
        deny all;

        location /upstream_status {
            check_status;
            access_log off;
        }

        # Nginx 基础状态
        location /nginx_status {
            stub_status;
            access_log off;
        }
    }
}

4.2 upstream server 参数速查表

参数 说明 示例
weight=N 权重,默认 1 weight=5
max_fails=N 允许最大失败次数,默认 1 max_fails=3
fail_timeout=Ns 失败统计窗口 & 恢复等待时间,默认 10s fail_timeout=30s
backup 备用服务器 backup
down 永久标记为下线 down
max_conns=N 限制最大并发连接数 max_conns=100
slow_start=Ns 恢复后缓慢增加权重(商业版) slow_start=30s

5. 常见问题排查

5.1 后端宕机后 Nginx 非常卡顿(最常见问题)

现象: 后端某台服务器宕机后,部分请求会卡住很久才返回。

根本原因: 不是因为没配 max_fails(默认值就是 1),而是 proxy_connect_timeout 默认 60 秒

当后端宕机时,Nginx 尝试连接该节点,要等整整 60 秒超时后才判定失败并切换到下一台:

之前(默认超时):
  请求 → 宕机节点 → 等待连接 60 秒超时 → 判定失败 → 切到下一台 → 用户等了 60 秒

之后(优化超时):
  请求 → 宕机节点 → 等待连接 3 秒超时 → 判定失败 → 自动切到下一台 → 用户等 ~3 秒

解决方案 — 必须配置超时 + 重试策略:

upstream backend {
    server 192.168.1.10:8080 max_fails=3 fail_timeout=30s;
    server 192.168.1.11:8080 max_fails=3 fail_timeout=30s;
    server 192.168.1.12:8080 max_fails=3 fail_timeout=30s;
}

server {
    location / {
        proxy_pass http://backend;

        # ===== 关键:缩短超时时间 =====
        proxy_connect_timeout 3s;     # 连接后端超时(默认60s,必须改小)
        proxy_send_timeout 5s;        # 发送请求到后端超时
        proxy_read_timeout 10s;       # 等待后端响应超时

        # ===== 关键:失败后自动重试下一台 =====
        proxy_next_upstream error timeout http_502 http_503 http_504;
        proxy_next_upstream_tries 3;      # 最多重试 3 台服务器
        proxy_next_upstream_timeout 10s;  # 所有重试的总耗时上限
    }
}

各超时参数默认值对照(都偏大,生产环境建议调小):

参数 默认值 建议值 说明
proxy_connect_timeout 60s 3-5s 与后端建立 TCP 连接的超时
proxy_send_timeout 60s 5-10s 向后端发送请求体的超时
proxy_read_timeout 60s 10-30s 等待后端返回响应的超时

快速排查命令 — 检查当前配置是否缺少超时设置:

grep -n "proxy_connect_timeout\|proxy_read_timeout\|proxy_next_upstream" /etc/nginx/nginx.conf /etc/nginx/conf.d/*.conf

如果没有输出,说明全部使用默认的 60 秒超时,这就是卡顿的原因。


5.2 如何确认请求分配到了哪台后端?

检查 access_log 中的 upstream 字段:

tail -f /var/log/nginx/access.log | grep 'upstream='

输出示例:

10.0.0.1 - - [09/May/2026:10:00:00 +0800] "GET /api/users" 200 ... upstream=10.0.1.10:8080 upstream_status=200 upstream_response_time=0.025

5.3 如何确认节点是否被剔除?

检查 error_log:

grep 'upstream' /var/log/nginx/error.log

被剔除时会出现类似日志:

upstream server temporarily disabled while connecting to upstream

5.4 配置变更后如何验证?

# 1. 检查配置语法
nginx -t

# 2. 平滑重载(不中断服务)
nginx -s reload

# 3. 验证 upstream 状态(需 check_module)
curl http://localhost:8888/upstream_status

5.5 节点恢复后请求暴增怎么办?

Nginx Plus 支持 slow_start,开源版可以通过以下方式缓解:

upstream backend {
    # 刚恢复的节点权重低,手动调整后 reload
    server 192.168.1.10:8080 weight=1 max_fails=3 fail_timeout=30s;
    server 192.168.1.11:8080 weight=5 max_fails=3 fail_timeout=30s;
}

5.6 所有后端都挂了怎么办?

配置一个本地兜底页面:

upstream backend {
    server 192.168.1.10:8080 max_fails=2 fail_timeout=10s;
    server 192.168.1.11:8080 max_fails=2 fail_timeout=10s;
}

server {
    location / {
        proxy_pass http://backend;
        proxy_intercept_errors on;
        error_page 502 503 504 /fallback.html;
    }

    location = /fallback.html {
        root /usr/share/nginx/html;
        internal;
    }
}


Comment