TT Lab
开始
学习 学习路径 课程

压力测试

压测端没造出来的负载不是服务端的极限

在 TT Lab 中继续学习

一句话总结

负载生成器没能产生出来的负载,不是服务器的极限。在读测试结果之前,必须先确认请求的负载和实际施加的负载是否一致。

为什么需要它

在订单 API 的容量评审报告中是这样写的:“以每秒 500 次施加了 1 分钟,p99 是 210 毫秒,错误为 0 次。”然而同一时段,服务器的请求数指标稳定在每秒 48 次,是平的。500 次从来没有施加上去过。

原因很简单。生成器以并发度 10 运行,响应是 200 毫秒。在闭环中,这个组合所能产生的最大吞吐量是 10 ÷ 0.2 = 每秒 50 次。500 次只是写在命令行里的愿望,是物理上不可能出现的数字。然而工具没有给出任何警告,就漂漂亮亮地输出了“p99 210 毫秒”。

这种结果之所以危险,是因为它会对服务器产生错误的信心。只经历了实际负载十分之一的服务器,当然看起来很健康。如果以这个数字为依据来定容量,上线当天就会恰好按十倍的差距垮掉。

工作原理

闭环生成器的吞吐量上限,直接来自利特尔法则。等待中的请求数 L、到达率 λ、停留时间 W 之间,L = λ × W 成立,所以如果把并发度固定为 C,λ 就不可能超过 C ÷ 응답시간(即 C 除以响应时间)。

并发度 响应 200 毫秒时的上限
1 每秒 5 次
5 每秒 25 次
10 每秒 50 次

这里重要的是,这个上限与服务器无关。无论服务器多快,生成器在等待期间,请求就发不出去。所以如果想达到目标速率,就必须把并发度设为不低于目标速率 × 响应时间。

第二个陷阱是速率选项的含义。hey 的 -q,文档写的是“Rate limit, in queries per second (QPS) per worker”——是加在每个 worker 上的上限。-c 5 -q 2 不是每秒 2 次,而是每秒 10 次。反过来,如果以为写进去的是总目标速率,实际负载就会按 worker 数被放大。每个工具对这个定义都不同,选项只要读错一个,整个测试就成了另一个实验。

第三个是生成器自身的资源。一个请求需要一个套接字,所以如果并发度超过了打开文件数的上限,超出的部分就会悄悄失败。进程数、CPU,以及运行生成器的主机的网络带宽,也会以同样的方式成为上限。如果生成器运行在比对象更小的机器上,那个测试测的就是生成器。

因此设计测试时有一个顺序。先确定想要施加的目标速率,再用该速率乘以响应时间,求出所需的并发度。如果是每秒 500 次、响应 200 毫秒,至少需要 100 个 worker。然后确认这个并发度是否在生成器一侧的限制之内——依次是打开文件数、临时端口范围、内存。如果先做好这两项计算,以后需要调查“为什么目标没有施加上去”的事就会减少。

在现场相遇的样子

最常见的信号,是“并发度提高一倍,总吞吐量也恰好提高一倍,响应时间不变”的结果。如果服务器饱和了,吞吐量会变平,响应时间会上升。如果两者都不是,说明服务器还闲着,极限在生成器一侧。

所以判定流程只需要看两个数字。如果服务器一侧的处理时间不变,只是总速率达不到目标,就是生成器的极限。 反过来,如果处理时间增加,同时速率变平,那才是真正的饱和点。如果不做这个区分,就会发生给完好无损的服务器再添加实例的事。

错误分布也是线索。“connection refused”或“too many open files”,很多时候是生成器而不是服务器产生的错误。尤其是文件描述符的上限,在把并发度大幅提高的瞬间会突然出现,而工具只会把它当作失败的请求统计,混进错误率里。这样就会得出“服务器有 5% 失败”的结论。

还有一种更常见的情形,是生成器和对象运行在同一台机器上的测试。本实验的 Pod 正是这种情况。如果两者共用同一个 CPU,负载越高,生成器分到的份额就越少,这样就无法区分是对象变慢了,还是生成器变慢了。在真实测试中,应该把生成器放在另一台机器上,如果做不到,至少要在报告中写明这一事实。

最后,防止这一切的最便宜的办法,是报告模板。只要预留出并排写下请求的负载与实际施加的负载的栏位,两者不一致时,任何人都能看出来。没有这一栏,就没有人会问。

下一项实验要做什么

启动一个响应需要 200 毫秒的 Python 服务器,先用利特尔法则预测各并发度下的吞吐量上限,再用 hey 实际测量来对照。通过实测确认 -q 是每个 worker 的上限,故意做一个远远达不到目标速率的测试,并建立区分它是服务器饱和还是生成器极限的流程。调低打开文件数的上限,亲手做出生成器自身成为极限的样子,最后制作并留存一个检查脚本,确认请求的负载与实际施加的负载是否一并写下。