news 2026/6/12 17:26:54

Nginx upstream 响应时间漂移:我用加权 least_conn + 健康检查重配把 P99 压回 120ms

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Nginx upstream 响应时间漂移:我用加权 least_conn + 健康检查重配把 P99 压回 120ms

Nginx upstream 响应时间漂移:我用加权 least_conn + 健康检查重配把 P99 压回 120ms

P99 从 380ms 飙到 1.2s,流量却只涨了 15%。

折腾了三个小时,最后发现不是后端服务的问题,而是 Nginx 的 upstream 负载均衡策略一直在把请求往"看起来没事"的节点上怼。

事情是怎么开始的

上周二下午,监控群开始刷屏。核心下单接口的 P99 延迟从平时的 120ms 左右爬到了 380ms,峰值甚至到了 1.2s。Error Rate 倒是没涨,但用户体验已经崩了。

我第一反应是后端服务出了问题。拉了三个同事一起排查,数据库、Redis、下游微服务全看了一遍,CPU、内存、连接数、GC 一切正常。

“后端没问题,那问题在哪?” 我盯着 Grafana 的 upstream 面板,发现一个奇怪的现象:三台后端节点的 P99 差距特别大。一台 150ms,一台 400ms,另一台 900ms。

这就不对了。如果负载均衡是均匀的,三台节点的延迟应该接近才对。

打开 Nginx 配置一看,血压上来了

我们的 upstream 配置是这样的:

upstream backend { server 10.0.1.10:8080; server 10.0.1.11:8080; server 10.0.1.12:8080; }

没有 weight,没有 least_conn,没有健康检查。默认的 round robin。

round robin 的问题在于,它只看轮询顺序,不看节点的实际负载。如果某个节点因为一次 Full GC 或者网络抖动变慢了,round robin 照样往它上面发请求。那个 900ms 的节点,就是在处理上一轮积压请求,而新的请求还在源源不断地打过去。

说白了,慢的节点越来越慢,快的节点也撑不住全部流量,整个集群的 P99 被拖垮。

先做个快速排查:确认问题在 Nginx 层

在改配置之前,我先用curl直接请求三台后端节点,确认延迟差距是不是后端本身的问题:

foripin10.0.1.{10..12};doecho"===$ip==="curl-o/dev/null-s-w"time_total: %{time_total}\n"\http://$ip:8080/api/order/createcurl-o/dev/null-s-w"time_total: %{time_total}\n"\http://$ip:8080/api/order/createcurl-o/dev/null-s-w"time_total: %{time_total}\n"\http://$ip:8080/api/order/createdone

结果一目了然:

  • 10.0.1.10: 平均 110ms
  • 10.0.1.11: 平均 280ms
  • 10.0.1.12: 平均 850ms

通过 Nginx 访问时,三台节点各承担 33% 流量。但直接访问后端时,延迟本身就有巨大差异。这说明问题不在后端业务逻辑,而是 Nginx 的调度策略没有感知到这个差异,还在"公平"地分配流量。

方案一:least_conn 把请求导向连接数少的节点

least_conn 的基本逻辑是:每次把请求发给当前 active connections 最少的节点。这个策略在节点性能不均时,比 round robin 要聪明一些。

upstream backend { least_conn; server 10.0.1.10:8080; server 10.0.1.11:8080; server 10.0.1.12:8080; }

改完上线,P99 从 1.2s 降到了 600ms,但还是离 120ms 的目标差很远。

原因也很简单:least_conn 只管连接数,不管响应时间。一个节点连接数少,不代表它处理快。可能是那个节点本身性能差,连接数自然上不去。least_conn 反而可能把更多请求塞给它。

方案二:加权 least_conn + 健康检查,双管齐下

我想要的其实是两个能力:

  1. 感知节点的响应速度,给快的节点更多权重
  2. 自动把慢节点或者不可用的节点摘掉

Nginx 的weight参数可以做到第一点,max_fails+fail_timeout可以做到第二点。组合起来:

upstream backend { least_conn; server 10.0.1.10:8080 weight=5 max_fails=3 fail_timeout=10s; server 10.0.1.11:8080 weight=3 max_fails=3 fail_timeout=10s; server 10.0.1.12:8080 weight=1 max_fails=3 fail_timeout=10s backup; }

weight=5 的是主力节点,性能好,响应快。weight=3 的是普通节点。weight=1 并且标记 backup 的是兜底节点,只有前两台都不可用时才启用。

least_conn 结合 weight 后,Nginx 会在连接数最少且权重高的节点之间做加权选择。简单说,快节点承担更多,慢节点自然被"边缘化"。

健康检查这里有个细节:Nginx 自带的健康检查是被动式的,靠max_fails统计请求失败次数。如果节点只是变慢但还能返回 200,Nginx 不会自动摘掉它。

所以我们加了一层主动探测,用ngx_http_upstream_check_module模块(或者升级到 Nginx Plus 用 active health check)。没条件升级的话,可以用一个外部脚本配合consuletcd动态调整 upstream。

加上主动探测后的完整配置

upstream backend { zone upstream_backend 64k; least_conn; server 10.0.1.10:8080 weight=5; server 10.0.1.11:8080 weight=3; server 10.0.1.12:8080 weight=1 backup; check interval=3000 rise=2 fall=3 timeout=2000 type=http; check_http_send "HEAD /health HTTP/1.0\r\n\r\n"; check_http_expect_alive http_2xx http_3xx; } server { listen 80; location / { proxy_pass http://backend; proxy_connect_timeout 2s; proxy_send_timeout 5s; proxy_read_timeout 10s; } }

interval=3000 表示每 3 秒探测一次。rise=2 表示连续两次探测成功才认为节点恢复。fall=3 表示连续三次失败才摘掉节点。timeout=2000 是探测超时时间,超过 2s 就算失败。

这个参数组合很重要。fall=3 是为了避免偶发抖动导致节点被频繁摘除。timeout=2000 则直接卡住了"慢节点"——如果一个节点 2 秒内都响应不了探测请求,那它大概率也处理不好真实请求。

效果验证:P99 从 1.2s 压回 120ms

配置上线后,我盯了 10 分钟 Grafana。

  • 10.0.1.10(weight=5)的 QPS 从 33% 涨到了 55%,P99 稳定在 110-120ms
  • 10.0.1.11(weight=3)的 QPS 降到了 35%,P99 220ms 左右
  • 10.0.1.12(weight=1 backup)在运行,P99 800ms,但只承担了 10% 的流量

整体 P99 从 1.2s 压回 120ms,跟平时持平。

最直观的变化是:当 10.0.1.12 因为一次网络抖动变慢时,active check 在 6 秒内就把它摘掉了,流量全部切到了前两台。等它恢复后,又自动加回了 upstream。整个过程不需要人工干预。

负载均衡策略对比:一张表说清楚

策略原理适用场景缺点
round robin轮询,按顺序分发节点性能完全一致不感知节点差异
least_conn最少连接数优先长连接、节点性能不均不感知响应时间
ip_hash按客户端 IP 固定需要会话保持单节点故障时影响固定用户
least_conn + weight加权最少连接节点性能差异大需要人工维护权重
least_conn + weight + active check加权 + 主动探测生产环境,节点动态变化需要编译额外模块

生产环境我推荐最后一档。如果没法编译模块,至少做到least_conn + weight + max_fails

一个实时监控 upstream 状态的小脚本

改完配置后,我顺手写了一个监控脚本,用来实时查看每个节点的连接数和响应状态:

#!/bin/bash# nginx_upstream_watch.shNGINX_HOST="localhost"NGINX_PORT="80"INTERVAL=5whiletrue;doecho"===$(date'+%Y-%m-%d %H:%M:%S')==="# 通过 Nginx stub_status 或自定义 location 获取 upstream 状态curl-s"http://$NGINX_HOST:$NGINX_PORT/nginx_status"|grep-E"Active|Reading|Writing|Waiting"# 打印当前 upstream 节点状态(需要 nginx_upstream_check_module 支持)curl-s"http://$NGINX_HOST:$NGINX_PORT/upstream_status"2>/dev/null||echo"upstream_status not available"echo""sleep$INTERVALdone

如果有nginx_upstream_check_module,可以暴露一个/upstream_status接口,直接看到每个节点的 up/down 状态、连续失败次数、响应时间。这个信息在排查时非常关键。

踩坑记录:几个容易忽略的细节

1. weight 和 least_conn 的交互

least_conn 不是严格按连接数最少来选,而是加权后的连接数。weight=5 的节点即使连接数比 weight=3 的节点多,只要加权后仍然"更闲",就会继续接收请求。不要误以为 weight=1 的节点会完全空闲。

2. backup 节点的真正含义

backup 节点只有在所有非 backup 节点都不可用时才被启用。如果主力节点只是变慢但还能返回 200,backup 不会生效。所以 backup 适合"兜底",不适合"分担慢流量"。

3. check 模块需要编译进 Nginx

./configure --add-module=/path/to/nginx_upstream_check_module

如果你用的是官方预编译包,可能没有这个模块。可以用nginx -V查看编译参数确认。

4. 健康检查 URL 要轻量

不要把 /health 做成一个会查数据库、调下游的接口。Nginx 每 3 秒探测一次,如果 /health 本身就很重,等于给后端额外制造了一波压力。我用的 /health 只返回一个静态字符串ok,开销几乎为零。

// Go 示例funchealthHandler(w http.ResponseWriter,r*http.Request){w.WriteHeader(http.StatusOK)w.Write([]byte("ok\n"))}

5. proxy_connect_timeout 要小于 check timeout

如果 proxy_connect_timeout 是 5s,而 check timeout 是 2s,就会出现 Nginx 已经认为节点挂了,但 proxy 还在尝试连接的尴尬局面。建议 proxy_connect_timeout <= check timeout。

6. zone 指令用于共享内存

zone upstream_backend 64k;这行不是摆设。它把 upstream 状态放到共享内存里,worker 进程之间可以同步节点状态。如果不加这个,每个 worker 进程各自维护一套连接数和失败次数,多 worker 场景下 health check 的行为会不一致。

写在最后

这次排查给我的教训是:监控面板上的 P99 飙升,不一定是你写的代码出了问题。有时候,问题出在"流量是怎么被分发的"这个更底层的地方。

round robin 看起来公平,但在真实生产环境里,节点性能永远不可能完全一致。一个节点变慢,如果没有负载均衡层面的自动隔离,整个集群都会被拖下水。

least_conn + weight + active health check 这套组合,本质上不是让负载"绝对均匀",而是让负载"按能力分配",并且能自动把"掉队"的节点摘掉。

如果你的 Nginx upstream 还在用默认配置,我建议今晚就翻一翻。可能你的 P99 还有一半压缩空间。

有问题欢迎评论区交流。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/12 17:22:50

Bayesian错误处理最佳实践:从边缘案例到生产环境部署的完整指南

Bayesian错误处理最佳实践&#xff1a;从边缘案例到生产环境部署的完整指南 【免费下载链接】bayesian Naive Bayesian Classification for Golang. 项目地址: https://gitcode.com/gh_mirrors/ba/bayesian 在Go语言中实现朴素贝叶斯分类器时&#xff0c;错误处理是确保…

作者头像 李华
网站建设 2026/6/12 17:21:53

LangChain对话记忆设计:全量/会话/摘要三种模式实战指南

1. 项目概述&#xff1a;让AI助手真正“记住”你&#xff0c;而不是每次对话都从零开始你有没有试过和某个AI助手聊了十几轮&#xff0c;聊到一半它突然问&#xff1a;“我们之前聊过什么&#xff1f;”或者你刚说过“我叫张伟”&#xff0c;下一句它又问&#xff1a;“请问您怎…

作者头像 李华
网站建设 2026/6/12 17:11:53

TYPELOAD_NEW_VERSION LOAD_TYPE_VERSION_MISMATCH

前提&#xff1a; 今天上传QALS表的append增强结构的请求&#xff0c;传输后一直报这两个错 自己执行过的方案 SE11 -> QALS 激活 增强的结构也激活 SE14-> QALS 激活并调整 使用AL12重置语句缓冲区即可临时解决此次报错问题 SAP LOAD_PROGRAM_TABLE_MISMATCH 处理…

作者头像 李华
网站建设 2026/6/12 17:09:02

工业企业AI平台如何选?四大核心能力决定落地成败

当下工业数字化转型步入深水区&#xff0c;各类 AI 工具、单点应用层出不穷&#xff0c;不少企业陷入选型难题&#xff1a;产品品类繁杂&#xff0c;零散工具拼凑不仅数据割裂、运维成本高&#xff0c;还难以形成协同能力。AI 平台是工业企业数智化升级的核心基础设施&#xff…

作者头像 李华