4. 性能指标提取:响应时间、吞吐量、错误率与百分位延迟

日志里埋了那么多数据,到底看哪些?

我刚开始做性能分析那会儿,也犯过傻——盯着平均响应时间看半天,觉得系统还行。结果线上用户骂翻了,说页面卡得要死。后来我才明白,平均值会骗人。真正能反映系统真实体验的,是那几个关键指标:RT、TPS/QPS、错误率,还有P99、P95。

这一章,咱们就把这些指标掰开揉碎,讲清楚它们怎么算、怎么看、怎么从日志里提出来。

4.1 响应时间(RT)——最直观的“慢”

响应时间,说白了就是一个请求从发出去到收到完整响应,花了多久。单位通常是毫秒。

在日志里,我们一般会在入口处打两个时间戳:

  • request_start:请求到达时间
  • request_end:响应写完时间

RT = request_end - request_start。

关键点:RT 要区分“服务端处理时间”和“客户端感知时间”。如果网络开销大,客户端感知的 RT 会远大于服务端日志里记录的 RT。我一般建议两端都埋点,对比着看。

举个例子,一条典型的访问日志:

2025-03-21 10:00:01.234 | GET /api/order/list | user_id=1024 | rt=342ms

这里的 rt=342ms 就是服务端视角的响应时间。如果这个接口的 RT 持续超过 500ms,就该查查了。

4.2 吞吐量(TPS/QPS)——系统到底能扛多少

吞吐量衡量的是系统在单位时间内能处理多少请求。

  • QPS(Queries Per Second):每秒查询数,常用于读接口
  • TPS(Transactions Per Second):每秒事务数,常用于写操作或完整业务链路

其实很多场景下大家混着用,不用太纠结。关键是统计口径要一致。

怎么从日志里算?很简单:

QPS = 总请求数 / 时间窗口(秒)

比如,1分钟内日志里出现了 6000 条请求记录,那 QPS 就是 100。

我的习惯:统计吞吐量时,时间窗口别用太短(比如1秒),容易抖动。我一般取 1分钟 或 5分钟 的窗口,算出来的值更稳。如果要做毛刺分析,再切到 10秒 窗口。

吞吐量和响应时间之间有个经典关系——当系统接近瓶颈时,RT 会急剧上升。你想想看,如果 QPS 从 100 涨到 200,RT 从 50ms 涨到 80ms,还算正常。但如果 QPS 从 200 涨到 210,RT 直接飙到 500ms,那说明系统已经快扛不住了。

4.3 错误率——别让“成功”蒙住眼

错误率 = 错误请求数 / 总请求数 × 100%。

日志里常见的错误状态码:

  • 4xx:客户端问题(如 404、429)
  • 5xx:服务端问题(如 500、502、503)
  • 超时:请求没在规定时间内返回

我一般会分开统计:

  • 业务错误率:比如下单失败、支付超时,这些需要业务日志配合
  • 系统错误率:5xx 和超时,直接反映系统健康度

我曾经踩过的坑:有一次线上告警说错误率 0.5%,看着不高。结果一查,是某个核心接口的 5xx 全被重试机制吞掉了,日志里只记了最后一次成功。所以,错误率统计一定要在“原始请求”层面做,别被重试逻辑污染了。

4.4 P99/P95 延迟——平均值的“照妖镜”

平均 RT 是 200ms,但 P99 是 2s。这说明什么?99% 的请求都在 2s 以内,但平均被那 1% 的慢请求拉高了?不对,恰恰相反——是大部分请求很快,但有一小撮请求慢得离谱。

百分位延迟的计算逻辑:

  • 把所有请求的 RT 从小到大排序
  • P50(中位数):排在第 50% 位置的 RT
  • P95:排在第 95% 位置的 RT
  • P99:排在第 99% 位置的 RT

举个例子,假设有 100 个请求的 RT 数据:

指标 值 含义
平均 RT 150ms 看着还行
P50 80ms 一半请求在 80ms 以内
P95 300ms 95% 的请求在 300ms 以内
P99 1200ms 1% 的请求超过 1.2s

看到没?平均 RT 150ms 掩盖了 P99 1.2s 的问题。那 1% 的用户体验极差,可能就是你的核心客户。

我的建议:做性能优化时,优先盯着 P99 打。把 P99 降下来,平均 RT 自然就跟着降了。别反过来——优化平均值,可能只是把快请求弄得更快,慢请求纹丝不动。

4.5 从日志里提取这些指标

实际操作中,我一般用两种方式:

4.5.1 实时计算(流式处理)

适合用 Flink、Spark Streaming 或 ClickHouse 的物化视图。日志进来后,直接按时间窗口聚合:

-- 伪SQL:计算每分钟的P99
SELECT 
  toStartOfMinute(timestamp) AS minute,
  quantile(0.99)(rt) AS p99_rt,
  count() AS total_requests,
  sumIf(1, status >= 500) / count() AS error_rate
FROM access_log
GROUP BY minute

4.5.2 离线计算(批处理)

适合用 Hive、Spark SQL 做小时级或天级分析:

-- 计算每小时的吞吐量和P95
SELECT 
  date_format(timestamp, 'yyyy-MM-dd HH:00') AS hour,
  count() / 3600 AS avg_qps,
  percentile_approx(rt, 0.95) AS p95_rt,
  sum(CASE WHEN status >= 500 THEN 1 ELSE 0 END) / count() AS error_rate
FROM access_log
GROUP BY date_format(timestamp, 'yyyy-MM-dd HH:00')

注意:百分位计算在数据量大时很吃资源。我习惯先对 RT 做分桶(比如 0-10ms、10-50ms、50-100ms……),用近似算法算 P99,精度损失不大,性能提升明显。

4.6 核心知识体系

下面这张图,把本章的核心逻辑串起来了:

性能指标提取核心体系 日志原始数据 响应时间 (RT) 吞吐量 (TPS/QPS) 错误率 P50 / P95 / P99 💡 指标间的关系 • RT 上升 → 系统资源吃紧 → 吞吐量可能下降(或 RT 飙升) • 错误率突增 → 往往伴随 RT 异常(超时、熔断) • P99 远高于平均 → 存在长尾请求,需排查慢查询或资源争抢 • 优化优先级:P99 > 错误率 > 平均 RT > 吞吐量(在容量充足时)

4.7 避坑指南

  • 别只看平均:平均值会掩盖长尾问题。P99 才是用户体验的真相。
  • 时间窗口要对齐:算 QPS 时用 1 分钟窗口,算 P99 时也用 1 分钟窗口,否则指标之间没法对比。
  • 错误率要分层:4xx 和 5xx 分开看。4xx 可能是客户端重试导致,5xx 才是真问题。
  • 百分位计算有成本:数据量大时,用近似算法(如 TDigest)代替精确排序。

我曾经犯过的错:有一次线上 P99 从 200ms 涨到 800ms,我盯着数据库慢查询查了半天,结果发现是某个上游服务超时重试导致线程池被打满。所以,P99 异常时,先看调用链,别一头扎进单点排查。