Nginx 请求时间为什么远大于上游响应时间
用户等了 8.2 秒,上游却只用了 120 毫秒,剩下的时间真是 Nginx 吃掉的吗?这篇文章用一条时间轴、增强日志和三组实验讲清楚。
先看一个常见的模拟故障场景。
用户说:“这个接口等了 8 秒多。”
Nginx 日志也确实显示:
request_time=8.200
upstream_response_time=0.120
后端团队一看,接口只处理了 120 毫秒,于是说:“应用没问题,剩下 8 秒肯定耗在 Nginx。”
网关团队觉的 Nginx 不可能凭空吃掉 8 秒,客户端则坚持用户确实等了 8 秒。三方的数据都可能是真的,问题出在大家拿了不同的秒表。
先说结论:这两个时间根本不是一个范围
Nginx 官方对 $request_time 的定义是:从读取到客户端发来的第一个字节开始,到最后一个响应字节发送给客户端、准备写入访问日志为止。
它覆盖的是用户请求在 Nginx 里经历的完整生命周期。
而 $upstream_response_time 记录的是 Nginx 从上游接收响应所花的时间。它描述的是上游交互阶段,不包含完整的客户端侧链路;同时,它也不应该被简单理解成“应用代码执行时间”。连接、传输、重试等因素都可能影响它。
把一次只有单个上游、没有内部跳转的请求简化成时间轴,大致是这样:
客户端开始上传请求
│ T0:Nginx 读到第一个字节
│
│ 读取请求头和请求体、限流、鉴权、路由……
│ T1:请求可以发往上游
│
│ 连接上游
│ T2:连接建立
│
│ 等待并接收响应头
│ T3:收到完整响应头
│
│ 接收上游响应体
│ T4:收完上游响应
│
│ 缓冲、过滤、限速、发送给客户端
│ T5:最后一个响应字节发送完成,写访问日志
request_time ≈ T5 - T0
upstream_response_time 覆盖上游交互阶段,结束于 T4 附近
这只是概念图。遇到重试、子请求、缓存或内部跳转,链路会复杂得多。
所以,下面这个公式是错的:
Nginx 自身耗时 = request_time - upstream_response_time
这个差值最多只能叫“尚未解释的边缘链路时间”,可能来自客户端上传、Nginx 处理、响应缓冲或客户端接收。
第一种情况:请求还没到后端,时间已经过去了
最容易制造“8.2 秒对 120 毫秒”的,是慢上传。
假设客户端上传一个 8MB 的请求体,实际上传速度只有 1MB/s。光把数据送到 Nginx,就需要大约 8 秒。Nginx 收完后把请求交给上游,上游 120 毫秒处理完成。
最终你就可能看到:
request_time≈8.2s
upstream_response_time≈0.12s
这 8 秒既不是后端业务耗时,也不是 Nginx 在“计算”。大部分时间只是 Nginx 在等待客户端把请求发完。
为什么上传阶段特别容易被隐藏?因为 proxy_request_buffering 默认是 on。Nginx 会先读取完整请求体,再把请求发给代理服务器。只看后端日志,自然看不到前面的慢上传。
遇到这种情况,先看三个信号:
- 请求方法是不是 POST、PUT 或 PATCH;
- 慢请求是否集中在移动网络、跨地域客户端或上传接口。
先别急着看 Nginx CPU,要问请求什么时候真正到达上游。
第二种情况:上游早就返回了,客户端还没收完
反方向也一样。
假设上游 200 毫秒生成了一个 5MB 响应,而客户端只能以 100KB/s 接收。上游很快,用户却可能等待几十秒。
proxy_buffering 默认也是 on。开启响应缓冲后,Nginx 会尽快从上游读取响应,先放进内存缓冲区;放不下时,还可能写入临时文件。随后,Nginx 再按照客户端实际接收能力慢慢发送。
于是出现一个很容易误判的现象:
$upstream_response_time 很短:Nginx 已经迅速收完上游响应;$request_time 很长:客户端迟迟没有收完;$bytes_sent 或 $body_bytes_sent 很大:慢请求集中在大响应接口。
如果还配置了 limit_rate,或者上游通过 X-Accel-Limit-Rate 要求限速,差值会更明显。
关掉 proxy_buffering 后,响应会更同步地转发。慢客户端产生的背压可能传到上游,$upstream_response_time 反而变长。这通常是计时边界变化,判断时还要结合缓冲配置、响应大小和客户端网络。
第三种情况:进入上游前后,还有很多事情要做
Nginx 并不总是“收到请求后立刻 proxy_pass”。
请求在进入业务上游之前,可能还经历:
- rewrite、location 匹配与内部跳转;
上游响应结束之后,也可能继续经历压缩、过滤、缓存写入、限速以及向慢客户端发送。
这些时间都会影响完整的 $request_time,却不一定体现在你正在观察的那一个业务上游时间里。
所以差值大时,正确问题不是“Nginx 为什么慢”,而是:
在请求进入上游之前、上游返回之后,分别还经过了哪些阶段?
第四种情况:你看到的 upstream 时间可能不止一个
这是日志分析脚本最容易踩的坑。
如果第一次连接上游失败,Nginx 又重试了另一台服务器,日志可能出现:
upstream_addr=10.0.0.11:8080, 10.0.0.12:8080
upstream_status=502, 200
upstream_response_time=0.301, 0.120
逗号表示一次请求处理过程中联系了多个上游。发生由 X-Accel-Redirect 或 error_page 引起的内部跳转时,不同上游组还可能使用冒号分隔。
这时 $upstream_response_time 已经不是一个可以直接相减的数字,而是一组与 $upstream_addr、$upstream_status 按位置对应的值。
如果监控平台强行把字符串取第一个、取最后一个,或者当成普通小数解析,结论都会失真。
比较时间之前,先确认三件事:
还有几类边界值,别急着报警
如果请求命中代理缓存,或者本来就是 Nginx 提供的静态资源,请求可能根本没有访问业务上游。此时 upstream 相关变量可能是 -,但 $request_time 仍然有值。
如果状态码是 499,通常意味着客户端在响应完成前断开;408 则常与客户端没有及时发送完整请求有关。这两类请求的时间更不能直接归因给后端。
如果没有选出可用的上游,$upstream_status 可能记录 502,其他 upstream 时间字段则可能没有正常值。
因此,监控平台至少要把 -、单值、多值和不同状态码分开处理。不要把 - 转成 0 后,再得出“Nginx 白白消耗了全部时间”的结论。
一份够用的增强日志配置
只记录 $request_time 和 $upstream_response_time 不够。排查慢请求时,至少要把连接、首包、完整响应、大小、状态和地址放在同一行里。
log_format timing_json escape=json
'{'
'"time":"$time_iso8601",'
'"request_id":"$request_id",'
'"remote_addr":"$remote_addr",'
'"method":"$request_method",'
'"uri":"$request_uri",'
'"status":"$status",'
'"request_length":"$request_length",'
'"bytes_sent":"$bytes_sent",'
'"request_time":"$request_time",'
'"upstream_addr":"$upstream_addr",'
'"upstream_status":"$upstream_status",'
'"upstream_connect_time":"$upstream_connect_time",'
'"upstream_header_time":"$upstream_header_time",'
'"upstream_response_time":"$upstream_response_time",'
'"upstream_bytes_sent":"$upstream_bytes_sent",'
'"upstream_bytes_received":"$upstream_bytes_received",'
'"upstream_cache_status":"$upstream_cache_status"'
'}';
access_log /var/log/nginx/access_timing.log timing_json;
这里把时间字段也写成字符串,是为了在变量值为 - 或多值列表时,日志仍然是合法 JSON。
如果后端还没有统一的链路 ID,可以把 Nginx 生成的请求 ID 传给上游:
proxy_set_header X-Request-ID $request_id;
如果系统已经有 Trace ID,就沿用原有规范,不要再制造第二套关联字段。
配置变更后,先检查再平滑加载:
nginx -t
nginx -s reload
由 systemd、容器或 Kubernetes 管理的环境,应使用对应的配置检查与发布流程,不要绕过现有变更机制。
三个实验,十分钟看懂时间花在哪里
以下命令中的 example.com 需要替换为隔离测试环境的实际地址。
实验一:制造慢上传
准备一个测试文件,并限制上传速度:
ddif=/dev/zero of=/tmp/slow-upload.bin bs=1M count=2
curl-sS --limit-rate 200k \
--data-binary @/tmp/slow-upload.bin \
https://example.com/upload -o /dev/null
在 proxy_request_buffering on 下,重点观察 $request_length、$request_time 和上游时间。你会发现,上传等待主要进入完整请求时间,而上游仍可能很快。
实验二:制造慢下载
找一个较大的测试响应,限制客户端下载速度:
curl-sS --limit-rate 100k \
https://example.com/large-file -o /dev/null
在 proxy_buffering on 下,如果上游可以被快速读取,$upstream_response_time 会较短,而 $request_time 会随着客户端接收时间增长。
实验三:切换响应缓冲
在隔离的测试环境里,将同一接口的 proxy_buffering 从 on 改为 off,重复慢下载实验。
关闭缓冲后,响应会同步转发,慢客户端的背压可能让 upstream 时间也随之增长。这个实验最能说明:指标变化不一定代表系统变慢,也可能只是计时边界变了。
不要在生产环境直接切换缓冲做实验。大响应、慢客户端和临时文件策略都会影响连接占用与上游压力。
可截图:6 步判断慢请求发生在哪一段
面对“request_time 很大、upstream 很小”,按下面的顺序查:
- 先看状态码和 upstream 是否存在。 缓存命中、静态响应、499、408、502 要单独分类。
- 确认 upstream 是单值还是多值。 有重试或内部跳转时,先还原每一次尝试。
- 看请求与响应大小。 大请求体优先排查慢上传;大响应优先排查慢下载与缓冲。
- 拆上游阶段。 connect 高,关注连接与网络;header 高,关注首包和应用排队;response 与 header 差距大,关注响应体生成和传输。
- 检查缓冲与限速。 看
proxy_request_buffering、proxy_buffering、limit_rate 以及上游响应头。 - 用请求 ID 串联证据。 对齐 Nginx、应用日志和 APM,而不是拿不同请求的聚合平均值互相证明。
如果只有一个上游值,可以暂时计算:
edge_gap = request_time - upstream_response_time
但请把它命名为“未解释时间”或“边缘链路差值”,不要叫“Nginx 耗时”。它是排查入口,不是责任判决书。
技术管理者真正该改的,是监控口径
很多性能争议不是系统没有数据,而是数据的边界不同。
客户端看的是用户等待时间,Nginx 看的是代理生命周期,上游应用看的是自身代码或框架处理时间。三者都叫“响应时间”,却不是同一个指标。
更合理的监控方式,是把链路至少拆成三段:
- 网关与代理段:鉴权、限流、路由、缓冲、过滤与发送;
再用同一个请求 ID,把访问日志、应用日志和 Trace 串起来。
这样,事故会上不需要再争论“8 秒是不是 Nginx 吃掉的”。团队可以直接回答:8 秒发生在哪一段、由什么证据证明、下一步由谁处理。
最后记住一句话:
指标的名字不重要,计时从哪里开始、在哪里结束,才决定它能证明什么。
你见过哪一种:“慢上传”“慢下载”,还是“第一次 upstream 失败后重试”?欢迎留言说说最后是谁背了这口锅。
如果团队还在用“request_time 减 upstream”划分责任,可以把这篇文章发给一起排障的人。
来源
- Nginx 官方文档:Module ngx_http_log_module —
$request_time:https://nginx.org/en/docs/http/ngx_http_log_module.html#var_request_time - Nginx 官方文档:Module ngx_http_upstream_module — upstream 变量:https://nginx.org/en/docs/http/ngx_http_upstream_module.html#variables
- Nginx 官方文档:Module ngx_http_proxy_module —
proxy_request_buffering:https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_request_buffering - Nginx 官方文档:Module ngx_http_proxy_module —
proxy_buffering:https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_buffering - Nginx 官方文档:Module ngx_http_core_module —
limit_rate:https://nginx.org/en/docs/http/ngx_http_core_module.html#limit_rate
阅读原文:点击这里
该文章在 2026/7/19 14:04:46 编辑过