延迟、流量、错误与饱和度,是判断服务能否满足用户需求的四个互补角度。它们提供的是一组起始问题,并不是四个放到任何系统里都能自动证明健康的通用计数器。是否有用,取决于在哪里测量、描述哪些工作、采用什么时间窗口,以及各项统计如何对应。响应很快也可能意味着业务操作失败,因此不能看到延迟下降就直接宣布用户体验改善。

区分用户体验与工作需求

延迟描述一项工作在指定边界内花了多长时间。成功操作与失败操作应该分开观察,避免立即返回的错误让总体分布显得更健康。尾部信息很有价值,但必须同时保留观测数量;很少的样本即使算出了高分位数,也不能证明已经稳定掌握稀有行为。客户端延迟与服务器内部处理时间覆盖的链路不同,比较它们之前应先说明各自包含和遗漏的阶段。

流量描述需求或已经完成的工作,应采用请求每秒、字节每秒等合适单位,并明确统计的是到达、完成还是重试。队列卡住时,完成速率可以下降,但外部请求需求仍然持续增加。并发请求数则是数量,不是请求速率;它可以作为补充信息,却不能直接替换流量面板中的速率。批处理系统也应选择适合自身任务的工作单位,而不是机械套用接口请求数。

说明失败条件与资源压力

错误描述未达到承诺结果的工作。状态码失败是一种信号,但正常响应中包含错误业务数据,也可能违反服务契约。应明确哪些结果、哪些延迟违规进入指标,并使用相同统计范围的分母。除了比例,还应展示实际数量,使安静时段的一次错误与高峰时段影响大量用户的事故不会因为百分比相似而被混淆。

饱和度描述受约束资源距离可用上限有多近。处理器、内存、连接池、工作线程占用和队列等待时间,可以揭示不同的瓶颈。线程池全部忙碌不一定意味着不健康,但如果队列持续增长、最老任务不断等待,就能看出占用率为何值得关注。某一时刻的快照并不是整段时间的平均值,而很低的平均值也可能藏住短暂过载。容量阈值需要结合工作负载与实测,不能只在图上画一条没有依据的红线。

坏掉的样子

混用统计总体会制造难以解释的故事,例如延迟来自应用成功请求,错误来自入口代理,流量却来自另一个区域。它们可以独立变化,所以关联之前必须写明边界与过滤条件。快速拒绝请求可以改善全请求平均延迟,同时使错误率上升;只统计已经完成的请求,还会遗漏尚未结束的等待,让用户正在经历的停顿从延迟和吞吐图中同时消失。

四个信号中的任何一个都不能独立证明根因。处理器空闲时,数据库连接池可能已经耗尽;队列增长既可能源于更多需求,也可能因为下游变慢。应利用信号选择下一步排查方向,再查看链路、日志、依赖和变更证据。告警应针对可采取行动的用户影响或即将耗尽的资源,不能把任何单次仪表波动都等同于需要立刻叫醒值班人员的事故。

快速验证

保存为 golden.py 后使用 Python 3 执行。实验包含一个十秒窗口内的人造已完成请求,以及另行标注的窗口结束时线程与队列快照,用于核对聚合与解读。它没有向真实服务施压,也没有测量当前服务器;分位数直接来自原始样本的最近秩计算,与利用直方图桶进行插值估计的方式不同。

from math import ceil
from statistics import mean

# Synthetic completed requests in one 10-second observation interval.
records = [(True, 0.05)] * 90 + [(True, 0.8)] * 5 + [(False, 0.005)] * 5
success = sorted(duration for ok, duration in records if ok)
failure = [duration for ok, duration in records if not ok]
p99_success = success[ceil(0.99 * len(success)) - 1]
traffic_rps = len(records) / 10
error_ratio = len(failure) / len(records)
# These are end-of-interval gauges, not interval averages.
busy_workers, total_workers = 4, 4
queued_requests, oldest_queue_seconds = 12, 1.2

assert p99_success == 0.8 and traffic_rps == 10 and error_ratio == 0.05
assert mean(failure) < mean(success)
assert busy_workers / total_workers == 1
print(f"traffic={traffic_rps:.1f} completed requests/s; errors={error_ratio:.1%}")
print(f"success p99={p99_success:.3f}s; failed mean={mean(failure):.3f}s")
print(f"success mean={mean(success):.6f}s; all-request mean={mean(d for _, d in records):.6f}s")
print(f"worker snapshot={busy_workers}/{total_workers}; queue={queued_requests}; oldest={oldest_queue_seconds}s")

输出显示,每秒完成十次请求,错误比例为百分之五,成功请求的百分之九十九分位数为零点八秒,失败请求平均只耗时零点零零五秒。成功请求平均约零点零八九四七四秒,而加入快速失败后,全请求平均值降低到零点零八五二五秒。这个可复现的差异说明,混合平均值下降不等于业务变得更有用,必须同时查看结果是否成功。

快照中四个工作线程都处于忙碌状态,还有十二个请求排队,最老请求已经等待一点二秒。这些仪表提供资源压力背景,却没有证明整个十秒窗口的占用率始终为百分之一百。应把它们与延迟、流量定义和后续队列趋势一起阅读,再判断需要扩容、限制进入的工作量,还是调查某个变慢的依赖,避免用一个瞬时数值替代完整的故障分析。

延伸阅读

来源