一个数据库团队花了三周搭建的压测环境,在模拟10万QPS流量时,P99延迟稳定在50毫秒以内。上线后第一天的流量峰值刚到6万QPS,监控系统就开始疯狂报警——大量请求超时,用户反馈页面卡顿。
这不是个例。2013年,Azul Systems的CTO Gil Tene在Strange Loop会议上发表了名为《How NOT to Measure Latency》的演讲,揭示了一个困扰行业多年的真相:大多数负载测试工具都在系统性地说谎。
协调遗漏:测量系统与被测系统的"共谋"
Gil Tene提出了一个术语来描述这个问题的本质:协调遗漏(Coordinated Omission)。
想象一个客服中心的场景。每个客服代表(测试线程)在处理完一个客户请求后,会等待一段时间再拨打下一个电话。当系统响应变慢时,客服代表会被迫等待,但他们不会在系统变慢时额外拨打更多电话来"弥补"。
这正是传统负载测试工具的工作方式。当被测系统响应变慢时,测试工具会自动降低请求速率——因为测试线程被阻塞了。这种"协调"导致了一个致命后果:测试工具避开了测量那些本该发生的延迟峰值。
Red Hat性能团队在他们的技术博客中举了一个数学例子:假设一个系统在测试期间发生了10分钟的完全中断。在传统测试模式下,只有10个测试线程会被阻塞10分钟。但在真实世界中,如果请求以每分钟20个的速率持续到达,这10分钟内会产生200个请求,它们都要排队等待。
如果只统计那些被阻塞的测试线程,平均响应时间会被严重低估。更糟糕的是,测试工具根本无法察觉自己遗漏了什么。
开放模型与封闭模型:性能差异可达一个数量级
2006年,卡内基梅隆大学的研究团队发表了一篇题为《Closed versus open system models and their impact on performance and scheduling》的论文,系统性地比较了两种系统模型。
封闭系统模型假设存在固定数量的用户,每个用户在前一个请求完成后才会发起新请求。这是大多数传统负载测试工具的设计基础。
开放系统模型则假设请求独立于系统状态到达,不受之前请求完成情况的影响。这更接近真实世界的Web服务场景——用户不会因为服务器慢就减少访问。
论文中的实验数据显示了一个令人震惊的事实:在相同负载下,开放系统的平均响应时间可以比封闭系统高出一个数量级以上。
论文中定义了"服务需求变异性"($C^2$,变异系数的平方)这个关键参数。当$C^2$较高时(比如$C^2 = 43$,代表某些超级计算场景),即使是MPL=1000的封闭系统,其响应时间仍然远低于开放系统。
更重要的是,调度策略的效果在两种模型下截然不同。在开放系统中,短作业优先(SJF)等策略可以带来数量级的性能提升;但在封闭系统中,调度策略的收益微乎其微。研究者发现,当思考时间为零时,所有工作保持型调度策略在封闭系统中的性能完全相同——这是Little定律的直接推论。
百分位数的幻象:平均值的陷阱依然存在
很多人已经意识到平均值的问题,转而使用百分位数。但前Netflix架构师Adrian Cockcroft在2023年的文章中指出了一个更深层的陷阱:百分位数无法合并。
如果你测量了60个一分钟区间的P99值,你无法通过平均这些值得到一小时的P99。因为每一分钟的P99可能对应完全不同的请求。Cockcroft提出了一个更激进的方案:使用混合模型分解响应时间分布。
通过期望最大化(EM)算法,可以将响应时间分布分解为多个正态分布的叠加。每个分布对应系统中不同的延迟来源——数据库查询、缓存命中、网络传输等。这种方法不仅能提供比百分位数更丰富的信息,还允许数学上正确的合并和预测。
他在文章中展示了一个真实Web服务的响应时间分布,分解后显示了至少8个不同的峰值。这些峰值的相对高度和位置,能够反映系统内部状态的变化——缓存命中率波动、数据库连接池状态、GC停顿等。
工具设计的结构性缺陷
ScyllaDB团队在他们的技术分析中详细剖析了YCSB基准测试工具的设计问题。YCSB默认工作在封闭模式下:一个线程发送请求,等待响应,然后发送下一个。
当研究者尝试修复这个问题时,他们发现需要同时处理两个层面:
负载生成层面:测试工具必须能够独立于系统响应维持请求发送速率。这需要区分"静态调度"(基于预设时间点)和"动态调度"(基于上一次请求完成时间)。前者在遇到延迟峰值时会排队补发,后者则会自然降低速率。
延迟测量层面:当请求被延迟发送时,记录的延迟数据必须进行修正。正确的延迟计算应该是:
$$\text{Latency} = (\text{now} - \text{intended\_time}) + \text{service\_time}$$其中intended_time是请求本该发送的时间点,而不是实际发送时间。
ScyllaDB的实验显示,在未修正的情况下,YCSB报告的P99延迟可能只有几百微秒;但修正后,同一测试的真实P99延迟可能高达600毫秒以上——差了三个数量级。
检测与规避:工程实践指南
Red Hat团队提供了一个简单但有效的检测方法:CTRL+Z测试。
在负载测试运行时,暂停被测服务进程(kill -STOP),等待一段时间后恢复(kill -CONT)。如果测试工具报告的延迟数据没有显示明显的峰值,那么它就存在协调遗漏问题。
对于需要正确测量的场景,有几个关键原则:
-
明确区分系统模型:根据真实工作负载选择开放、封闭或部分开放模型。Cockcroft提出的经验法则是:每会话请求数少于5个时,开放模型更准确;超过10个时,封闭模型更接近现实。
-
使用正确的工具配置:JMeter等传统工具需要显式设置吞吐量目标,而非仅设置并发用户数。k6等新一代工具提供了
arrival-rate执行器,能够以开放模型方式工作。 -
验证测量有效性:对比"预期发送请求数"和"实际发送请求数"。如果两者存在显著差异,说明负载生成器已被阻塞。
-
采用分布分析而非百分位数:收集原始延迟数据或高精度直方图(如HdrHistogram),进行分布分解分析。
尾声
负载测试的困境反映了一个更深层的工程问题:当测量系统与被测系统耦合时,测量结果会系统性偏离真相。
这不是一个可以通过简单调参解决的问题。它要求测试工程师理解排队论的基本原理,区分不同的系统模型,选择合适的工具配置,并对结果进行批判性审视。
那些看起来"过于完美"的压测报告,往往是最危险的。它们可能正在掩盖那些会在生产环境中让系统崩溃的延迟峰值。
参考文献
- Gil Tene, “How NOT to Measure Latency,” Strange Loop Conference, 2015
- Bianca Schroeder et al., “Closed versus open system models and their impact on performance and scheduling,” NSDI ‘06, 2006
- ScyllaDB Team, “On Coordinated Omission,” ScyllaDB Blog, 2021
- Red Hat Performance Team, “Coordinated Omission,” Red Hat Blog, 2022
- Adrian Cockcroft, “Percentiles don’t work: Analyzing the distribution of response times for web services,” Medium, 2023
- Gil Tene, “Correcting YCSB’s Coordinated Omission problem,” psy-lob-saw blog, 2015
- Grafana Labs, “Open source load testing tool review,” Grafana Blog, 2020