从一次线上超时说起——Nginx
故障定位思路
接手一个老旧的 Nginx 反向代理集群时,发现上游应用节点偶尔返回 502,业务却迟迟不会自动切换到正常节点。检查 upstream 配置发现只写了最基本的轮询,没有配置任何故障转移机制。这就是 proxy_next_upstream 的典型缺失场景——当请求已经发往一个上游节点,该节点返回非正常响应(超时、错误状态码等),Nginx 默认不会自动尝试下一个节点。
本文围绕 proxy_next_upstream 及其配套指令,梳理出一条从故障转移到健康检查的完整配置链条,并提供可直接用于生产环境的实战建议。
补充参考:此处可内链到“Nginx故障排查实例”。
proxy_next_upstream 核心指令详解与Nginx
指令作用与触发条件
proxy_next_upstream 定义在 location、server 或 http 上下文中,用于指定当上游服务器返回哪些错误时,Nginx 应将请求转发给下一个上游服务器。默认值为 proxy_next_upstream error timeout,即只对连接超时和 I/O 错误进行重试。
proxy_next_upstream error | timeout | invalid_header | http_500 | http_502 | http_503 | http_504 | http_403 | http_404 | http_429 | non_idempotent | off ...;
每个参数的含义:
- error:与上游服务器建立连接、发送请求或读取响应时发生 TCP 错误。
- timeout:与上游通信超时(由 proxy_connect_timeout / proxy_read_timeout 等控制)。
- invalid_header:上游返回的响应头格式无效。
- http_5xx:匹配对应的 HTTP 状态码。
- non_idempotent:默认情况下,Nginx 不会对非幂等请求(POST、PATCH 等)执行重试,加上此参数后允许重试非幂等请求(需评估业务幂等性)。
- off:禁止任何重试,只发一个上游。
与 proxy_next_upstream_tries 配合
单靠 proxy_next_upstream 只能决定哪些情况可以重试,但重试次数由 proxy_next_upstream_tries 控制,默认为 0(不限制,仅受 upstream 节点数量限制)。建议设置一个合理上限,例如 3 次,避免因所有节点都超时而陷入过多无效重试。
proxy_next_upstream_tries 3;
相关阅读:此处可内链到“Nginx常见问题”专题。
被动健康检查:基于请求结果的故障检测
配置前的检查
被动健康检查不依赖额外的检测请求,而是利用业务请求的响应结果来判断上游是否健康。Nginx 的 max_fails 和 fail_timeout 就是实现被动健康检查的关键参数,定义在 upstream 的 server 指令中。
upstream backend {
server 10.0.1.1:8080 max_fails=3 fail_timeout=10s;
server 10.0.1.2:8080 max_fails=3 fail_timeout=10s;
}
工作原理:当请求被转发到某个上游节点且触发 proxy_next_upstream 规则(比如超时),Nginx 会将此次请求记为一次失败。如果连续失败次数达到 max_fails(默认 1),该节点会在 fail_timeout 时间内被标记为不可用,新请求将跳过该节点。时间过后节点自动恢复,恢复后立即参与负载均衡。
实战调优:
max_fails不要设得太小(例如 1),避免短暂网络抖动立即摘除节点;但也不要太大(如 10),否则故障节点会继续接收请求造成大范围超时。3~5 次是常见选择。fail_timeout反映节点恢复周期。对于快速恢复的应用(如进程重启无需等待),可设为 5~10s;对于需要长时间修复的故障,可设为 30s 以上。- 注意被动健康检查依赖真实的业务流量,如果某节点长期没有收到请求,不会触发失败计数,也不会被主动标记为不健康。这正是许多生产环境引入主动健康检查的原因。
延伸阅读:此处可内链到“Nginx配置案例”相关文章。
主动健康检查:主动探测上游状态
先看关键判断
Nginx 开源版默认不提供主动健康检查模块,但可以通过商业版 Nginx Plus 的 health_check 指令或第三方模块(如 ngx_http_upstream_check_module)实现。以下以 Nginx Plus 的配置为例:
upstream backend {
zone backend 64k;
server 10.0.1.1:8080;
server 10.0.1.2:8080;
}
server {
location / {
proxy_pass http://backend;
health_check interval=5s fails=3 passes=2 uri=/health;
}
}
参数说明:
- interval:探测间隔,默认 5s。
- fails:连续失败次数达到该值后节点标记为不健康。
- passes:连续成功次数达到该值后节点标记为健康(从故障恢复)。
- uri:探测的路径,通常应用需提供一个轻量级的健康检查端点(如返回 200 及空内容)。
使用第三方模块时,需重新编译 Nginx 并添加模块,配置语法略有不同。无论哪种方式,主动健康检查能够在不依赖业务请求的情况下定时探测,有效解决“流量低谷时期无法发现节点故障”的问题。
完整实战配置示例
容易忽略的细节
场景:两个 Node.js 应用实例(端口 3000、3001),要求 502/504 超时自动重试最多 2 次,被动健康检查摘除连续失败 5 次的节点(30s 后恢复),并配合主动健康检查每 10s 检测 /health 端点。
upstream node_app {
zone node_app 64k;
server 127.0.0.1:3000 max_fails=5 fail_timeout=30s;
server 127.0.0.1:3001 max_fails=5 fail_timeout=30s;
}
server {
listen 80;
location / {
proxy_pass http://node_app;
proxy_http_version 1.1;
proxy_set_header Connection "";
# 故障转移触发条件
proxy_next_upstream error timeout http_502 http_504;
proxy_next_upstream_tries 2;
# 连接/读取超时(避免上下游阻塞)
proxy_connect_timeout 5s;
proxy_read_timeout 10s;
}
location /health {
# 主动健康检查端点(Nginx Plus 语法)
proxy_pass http://node_app;
health_check interval=10s fails=3 passes=2 uri=/health;
}
}
注意事项:
- 如果使用 Nginx 开源版且不打算编译第三方模块,可以只依赖被动健康检查,但需确保业务流量足以触发失败计数。
proxy_next_upstream_timeout和proxy_next_upstream_tries可以配合限制整体重试耗时,避免用户等待过久。- 对于敏感写操作(POST/PUT),除非业务做了幂等设计,否则不要开启
non_idempotent,否则可能导致重复写入。
常见问题与排错思路
配置后依然不自动切换
检查:
- 是否误写了
proxy_next_upstream off? - 触发条件是否覆盖了实际错误?例如上游返回 500 但只配置了
http_502 http_504。 - 是否所有上游都失败了?此时 Nginx 会返回最后一个节点的错误,而不会再跳转。
- 是否没有设置
proxy_buffer_size等缓存参数导致读响应时触发invalid_header?
上游短暂挂起但节点未被摘除
被动检查的 max_fails 默认只有 1,如果上游只在某个请求上超时一次,节点不会被摘除。将 max_fails 适当调高(如 3),配合较短的 fail_timeout(如 10s)可实现更灵敏的摘除。
非幂等请求重复执行
某些支付或订单接口,如果因为超时导致重试,可能引发重复扣款。除了不在 proxy_next_upstream 中加入 non_idempotent,还可以在应用层通过幂等 Key 做去重。
想继续深入:此处可内链到“Nginx优化清单”文章。
关联教程:此处可内链到“Nginx部署与验证”内容。
总结
我的处理经验
proxy_next_upstream 是 Nginx 故障转移的基础,搭配 max_fails / fail_timeout 可构建低成本的被动健康检查。对于高可用要求严苛的场景,引入主动健康检查(Nginx Plus 或第三方模块)能进一步缩短探测延迟。配置时注意重试次数、超时时间、非幂等请求处理以及上下游的响应时间匹配。希望本文的实战配置能帮助你在线上更稳定地管理上游服务。
延伸阅读
