线上服务一旦出现"接口偶尔变慢"的投诉,第一反应往往是查后端应用的监控。但如果后端监控显示一切正常,而用户仍在反馈慢,问题很可能出在Nginx这一层的转发环节,或者是慢请求占比太小被平均值掩盖了。Nginx的访问日志里其实藏着一个关键字段——upstream_response_time,它精确记录了每一次请求在后端上游上花费的时间。通过对这个字段做分布统计,你能直观看到有多少请求落在50毫秒以内、有多少请求超过了1秒,从而判断性能问题的严重程度和影响范围。

一、先搞清楚两个时间字段的区别
Nginx日志中有两个容易混淆的时间变量:$request_time和$upstream_response_time。前者是请求从进入Nginx到完整返回的总耗时,包含了接收客户端数据、等待后端、传输响应给客户端的全部时间;后者只统计Nginx与上游(upstream)之间的交互耗时,即向后端转发请求到收到后端完整响应的时间。
两者的差值就是Nginx自身以及客户端侧网络的开销。如果$request_time远大于$upstream_response_time,说明时间消耗在客户端传输或Nginx缓冲区上,比如大文件下载、客户端网络差等;如果两者接近且都偏大,那瓶颈就在后端服务本身。此外还有一个$upstream_connect_time,它记录与后端建立连接的耗时,如果这个值偏高,通常意味着后端机器负载过高、连接池打满或者网络抖动。
需要注意的是,当配置了多级upstream或者一次请求经历多次重试时,$upstream_response_time会输出多个以逗号分隔的值,例如"0.012, 0.300",表示第一次转发失败后重试到了第二台后端。统计时必须取总和或者只取最后一个值,否则结果会失真。
二、日志格式配置与原始日志准备
默认的combined日志格式并不包含upstream时间字段,需要自定义log_format。建议在http块中定义一个分析友好的格式,用竖线分隔字段,方便后续用awk按列提取:
# nginx.conf 的 http 块中
log_format timing '$remote_addr|$status|$request_time|$upstream_response_time|$upstream_connect_time|$request_uri';
server {
listen 80;
access_log /var/log/nginx/timing.log timing;
}配置完成后执行nginx -t检查语法,再通过nginx -s reload平滑重载。观察一段时间后,日志文件中每一行类似下面这样:
192.168.1.23|200|0.315|0.298|0.002|/api/user/list 10.0.5.11|500|3.102|3.050|0.001|/api/order/create 172.16.8.4|200|0.051|0.040, 0.006|0.001, 0.001|/api/search
第三行就是典型的重试场景,upstream时间出现了两组值。日志的切割建议配合logrotate按小时滚动,这样统计粒度更细,能对比出不同时段的分布变化,比如大促期间的慢请求占比是否明显上升。
三、用awk统计响应时间分布
有了结构化日志,一行awk命令就能得到分布结果。思路是把upstream时间映射到不同区间,统计每个区间的请求数量和占比。考虑到多值的情况,这里取最后一个值(即最终成功响应的那台后端):
cat /var/log/nginx/timing.log | awk -F'|' '
{
# 取 upstream_response_time 的最后一个值,处理重试产生的多值情况
n = split($4, arr, ",")
t = arr[n] + 0
if (t < 0.05) bucket = "[0, 50ms)"
else if (t < 0.1) bucket = "[50ms, 100ms)"
else if (t < 0.5) bucket = "[100ms, 500ms)"
else if (t < 1.0) bucket = "[500ms, 1s)"
else if (t < 3.0) bucket = "[1s, 3s)"
else bucket = ">=3s"
count[bucket]++
total++
}
END {
printf "%-18s %8s %8s\n", "区间", "数量", "占比"
split("[0, 50ms)|[50ms, 100ms)|[100ms, 500ms)|[500ms, 1s)|[1s, 3s)|>=3s", order, "|")
for (i = 1; i <= 6; i++) {
b = order[i]
if (b in count) printf "%-18s %8d %7.2f%%\n", b, count[b], count[b]/total*100
else printf "%-18s %8d %7.2f%%\n", b, 0, 0
}
print "总请求数:", total
}'输出的表格能一眼看出慢请求占比。除了区间分布,分位数往往比平均值更有说服力,尤其是P95和P99,它们反映了最差的那部分用户的体验。下面这条命令直接计算P50、P90、P99:
cat /var/log/nginx/timing.log | awk -F'|' '
{
n = split($4, arr, ",")
vals[NR] = arr[n] + 0
}
END {
asort(vals)
printf "P50=%.3fs P90=%.3fs P99=%.3fs MAX=%.3fs\n",
vals[int(NR*0.5)], vals[int(NR*0.9)], vals[int(NR*0.99)], vals[NR]
}'如果想进一步定位是哪些接口拖慢了整体,可以按URI维度分组,统计每个接口的平均耗时和请求数,找出"高频且慢"的接口,这类接口才是优化的首要目标:
cat /var/log/nginx/timing.log | awk -F'|' '
{
n = split($4, arr, ",")
sum[$6] += arr[n]; cnt[$6]++
}
END {
for (u in sum) printf "%8.3fs %6d %s\n", sum[u]/cnt[u], cnt[u], u
}' | sort -rn | head -20四、用Python绘制分布直方图
命令行适合临时排查,如果要做周期性报告,用Python读取日志并绘制直方图和累积分布曲线更直观。matplotlib绘制的图形可以直接放进周报,让团队对性能趋势有一致认知:
import re
from collections import Counter
import matplotlib.pyplot as plt
buckets = [0.05, 0.1, 0.5, 1.0, 3.0]
labels = ['<50ms', '50-100ms', '100-500ms', '0.5-1s', '1-3s', '>=3s']
def parse(path):
times = []
with open(path, encoding='utf-8') as f:
for line in f:
parts = line.strip().split('|')
if len(parts) < 6 or parts[3] == '-':
continue # 跳过未命中upstream的请求
# 重试场景取最后一个值
t = float(parts[3].split(',')[-1].strip())
times.append(t)
return times
times = parse('/var/log/nginx/timing.log')
counter = Counter()
for t in times:
for i, edge in enumerate(buckets):
if t < edge:
counter[labels[i]] += 1
break
else:
counter[labels[-1]] += 1
plt.rcParams['font.sans-serif'] = ['SimHei']
fig, axes = plt.subplots(1, 2, figsize=(12, 4))
axes[0].bar(counter.keys(), [counter[l] for l in labels], color='#4c72b0')
axes[0].set_title('响应时间区间分布')
times.sort()
axes[1].plot(times, [i / len(times) for i in range(len(times))])
axes[1].axhline(0.95, color='r', linestyle='--')
axes[1].set_title('累积分布曲线(红线为P95)')
plt.tight_layout()
plt.savefig('response_time.png', dpi=120)注意代码里对值为"-"的处理,当请求被Nginx直接返回(如静态文件命中、404由Nginx自身响应)时,upstream字段为"-",不参与统计才能避免拉低整体数据。累积分布曲线与0.95红线的交点就是P95耗时,一眼即可判断是否满足SLA。
五、从临时排查走向常态化监控
靠人工跑脚本只能解决眼前的问题,更可持续的做法是把响应时间分布接入监控体系。轻量方案是用crontab每五分钟执行一次awk统计脚本,将各区间占比和P99写入文本,再通过一个简单的location暴露给Prometheus抓取。有一定规模的团队则可以直接部署json格式的access_log,交给ELK或Loki,在Grafana中做按接口维度的heatmap面板。
告警规则建议关注两个指标:一是P99超过阈值(例如1秒)持续两个周期,二是慢请求占比突增,比如超过1秒的请求比例从0.5%涨到5%。后者往往比平均值更能提前暴露问题,因为均值会被大量快速请求稀释,等到平均响应时间明显上升时,通常已经有一批用户受到影响了。把分布统计做成常态化手段,性能回归就能在第一时间被发现,而不是等用户投诉了才回头翻日志。
Nginx日志分析upstream_response_time响应时间分布修改时间:2026-09-16 06:03:38