在Nginx作为反向代理或网关的生产环境中,后端节点偶尔会出现超时、连接失败或返回5xx错误的情况。此时Nginx并不会立刻把失败结果抛给客户端,而是根据配置决定是否将请求转发给下一个后端节点,这个过程就是我们常说的回源重试。合理的重试策略可以在单点故障时保障请求成功率,但不加节制的重试会导致请求被放大、故障扩散。本文将从配置指令、日志定位和优化实践三个层面,完整梳理Nginx的回源重试策略。

一、回源重试的核心配置:proxy_next_upstream
控制Nginx是否执行回源重试的核心指令是proxy_next_upstream,它定义了在哪些失败场景下,Nginx会把当前请求传递给upstream块中的下一个server。默认值是error timeout,即仅在建立连接失败或通信超时时重试。常用的可配置参数包括:
proxy_next_upstream error timeout http_500 http_502 http_503 http_504;
其中error表示与后端建立连接、传递请求或读取响应时发生错误;timeout表示上述任一阶段发生超时;http_500到http_504则表示后端返回了这些状态码时也触发重试。还有一种non_idempotent参数,用于允许非幂等请求(例如POST)也参与重试,默认情况下POST请求是不会被重试的,因为重复提交可能造成数据重复写入,这一点必须格外谨慎。
除了失败场景,重试的规模也需要控制。Nginx提供了两个配套指令:proxy_next_upstream_tries限制尝试的后端节点总数,proxy_next_upstream_timeout限制整个重试过程的总耗时。这两个参数非常关键,如果没有它们约束,假设upstream中配置了十个节点,一个请求最坏情况可能被转发十次,客户端等待时间会成倍增加。推荐的基础配置如下:
upstream backend {
server 10.0.0.1:8080 max_fails=3 fail_timeout=30s;
server 10.0.0.2:8080 max_fails=3 fail_timeout=30s;
server 10.0.0.3:8080 backup;
}
server {
listen 80;
location /api/ {
proxy_pass http://backend;
# 出现连接错误、超时或5xx时尝试下一个节点
proxy_next_upstream error timeout http_502 http_503 http_504;
# 最多尝试3个节点
proxy_next_upstream_tries 3;
# 重试总时长不超过10秒
proxy_next_upstream_timeout 10s;
proxy_connect_timeout 3s;
proxy_read_timeout 8s;
proxy_send_timeout 8s;
}
}
这套配置的含义是:一个请求最多在三个节点之间流转,且整个重试过程累计不超过10秒。无论哪个条件先达到,Nginx都会停止重试并把最后一次的响应(或错误)返回给客户端,避免客户端无限等待。
二、如何通过日志定位和观测重试行为
重试配置生效后,更重要的是能够观测到它。Nginx的日志体系提供了两条线索:error.log和access.log。在error.log中,当某个后端连接失败或超时,会看到类似upstream timed out (110: Connection timed out) while connecting to upstream或no live upstreams while connecting to upstream的记录,这些日志条目就是重试的起点,说明Nginx正在放弃当前节点。
而在access.log中,关键在于记录$upstream_addr、$upstream_status和$upstream_response_time这三个变量。当一个请求发生了重试,$upstream_addr会输出类似10.0.0.1:8080, 10.0.0.2:8080的多个地址,用逗号分隔;$upstream_status则可能是502, 200这样的组合,前面的502代表第一次尝试失败,后面的200代表重试成功。自定义日志格式如下:
log_format upstream_log '$remote_addr - [$time_local] "$request" '
'status=$status upstream=$upstream_addr '
'upstream_status=$upstream_status '
'upstream_time=$upstream_response_time '
'request_time=$request_time retry_flag=$upstream_cache_status';
access_log /var/log/nginx/upstream.access.log upstream_log;
基于这样的日志,我们可以统计出重试请求的占比、重试后最终成功的比例,以及哪些后端节点最常出现在$upstream_addr的失败位置。如果在ELK或Loki等日志平台中按$upstream_addr做聚合,能快速识别出高频故障节点。还有一个容易忽略的细节:$upstream_response_time同样支持多值输出,形如0.005 : 8.003,冒号分隔的时间值往往意味着前一次尝试超时,这对判断超时阈值是否合理非常有价值。
三、重试策略的优化实践与风险规避
回源重试最大的风险在于请求放大。假设上游每秒一万请求,重试率为百分之十,那么后端实际要承受一万一千次请求;如果后端本身就是因为过载而超时,重试等于火上浇油,极易演变为雪崩。因此优化重试策略时,第一个原则是分层设置超时:连接超时proxy_connect_timeout应该设置得较短(通常2到3秒),因为TCP握手失败能快速暴露;读取超时则要根据后端接口的真实耗时分布来设定,一般取P99耗时的1.5倍左右,避免误杀正常慢请求。
第二个原则是结合被动健康检查。上文的max_fails=3 fail_timeout=30s表示节点在30秒内失败三次后会被暂时摘除30秒。这个机制与重试天然互补:重试负责挽救单次请求,健康检查负责把故障节点从候选列表中剔除,减少后续请求再打到它上面的概率。如果使用的是Nginx Plus或者带第三方模块的开源版本,还可以启用主动健康检查,通过定期探测接口提前发现异常节点。
第三个原则是针对业务语义精细化控制。对于查询类的幂等接口,可以放心地把http_500也加入重试条件;对于支付下单等写操作,则应保持默认的error timeout,绝不能开启non_idempotent,否则一次网络抖动可能导致订单重复创建。可以通过不同的location区分策略:
# 只读查询接口:允许较激进的重试
location /api/query/ {
proxy_pass http://backend;
proxy_next_upstream error timeout http_502 http_503 http_504 http_500;
proxy_next_upstream_tries 3;
}
# 交易写接口:只重试连接层面的失败
location /api/order/ {
proxy_pass http://backend;
proxy_next_upstream error timeout;
proxy_next_upstream_tries 2;
proxy_next_upstream_timeout 5s;
}
最后,建议为重试行为建立监控告警。当access.log中多值$upstream_addr的请求占比持续上升,往往意味着后端容量或网络质量出现了劣化,此时应优先扩容或排查故障节点,而不是简单调大重试次数来掩盖问题。重试只是兜底手段,一个健康的系统应当让重试率维持在很低的水平。通过合理的指令配置、完善的日志观测和分场景的策略设计,才能让Nginx的回源重试真正成为高可用架构中可靠的一环。
Nginx回源重试proxy_next_upstream日志分析修改时间:2026-08-31 20:04:43