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

压力测试

报告写着每秒500次,实际只有48次

在 TT Lab 中继续学习

目标

用利特尔法则预测闭环生成器的吞吐量上限并实际测量对照,实测确认 hey 的速率选项是每个 worker 的上限,并建立区分“未达到目标速率的结果是服务器饱和还是生成器极限”的流程,以及报告检查脚本。

为什么重要

压力测试报告中最常出错的数字,不是延迟,而是负载本身。并发度为 10、响应 200 毫秒的闭环,无论写多高的目标,每秒都不会超过 50 次。然而工具会不加警告地输出漂亮的百分位数,只经历了实际负载十分之一的服务器,当然看起来很健康。速率选项的定义因工具而异,一个词读错,实际负载就会按 worker 数被放大或缩小;当打开文件数这样的生成器自身资源成为极限时,那种失败会混进服务器的错误率里。所以在读取结果之前,必须先确认请求的负载和实际施加的负载是否一致,并且要把这个确认交给报告模板和检查脚本,而不是靠人的记忆。

步骤

  1. 把 /opt/lab/lt/lt-generator-limits/slow.py 以 0.2 秒的响应启动在端口 8090 上。然后用 curl -s -o /dev/null -w '%{time_total}\n' 请求 / 五次,把这五行原样留在 /root/lt-generator-limits/01-probe.txt 中,并在 /root/lt-generator-limits/01-service.txt 中写两行 port= 和 service_ms=。service_ms 写这五行的中位数,以毫秒整数表示。
  2. 创建 /root/lt-generator-limits/predict.tsv。没有表头,共四行,每行是以制表符分隔的两个字段 <동시성> <초당 요청 수 상한>(占位符依次为并发度、每秒请求数上限)。并发度依次为 1、2、5、10,上限是 동시성 ÷ 서비스 시간(초)(即并发度 ÷ 服务时间(秒)),保留到小数点后第二位。服务时间是第 1 步测得的值。
  3. 用 hey -t 60 -o csv 测量两次。并发度 1 用 -n 30,并发度 5 用 -n 125,把原始 CSV 分别原样留在 /root/lt-generator-limits/c1.csv 和 /root/lt-generator-limits/c5.csv 中。然后在 /root/lt-generator-limits/03-measured.tsv 中写两行,每行是以制表符分隔的三个字段 <동시성> <예측 RPS> <측정 RPS>(占位符依次为并发度、预测 RPS、实测 RPS)(保留到小数点后第二位)。总 RPS 按 요청 수 ÷ (max(offset + response-time) − min(offset))(即请求数 ÷ (max(offset + response-time) − min(offset)))计算——只要有 hey 的 CSV 中的两个字段就能求出。
  4. 用 hey -n 100 -c 5 -q 2 -t 60 -o csv 测量,把原始文件留在 /root/lt-generator-limits/q.csv 中。然后在 /root/lt-generator-limits/04-q.txt 中写五行 q_flag=、workers=、expected_total_rps=、measured_rps=、per_worker=。expected_total_rps 是根据 -q 值和 worker 数预测的总速率,measured_rps 是根据 q.csv 重新计算的值(保留到小数点后第二位),per_worker 在 -q 是每个 worker 的上限时为 yes,是总上限时为 no。
  5. 用 hey -n 100 -c 2 -q 25 -t 60 -o csv 测量,把原始文件留在 /root/lt-generator-limits/shortfall.csv 中。-q 25 加上 2 个 worker,所以请求的负载是每秒 50 次。在 /root/lt-generator-limits/05-shortfall.txt 中写三行 requested_rps=、achieved_rps=、achieved_ratio=。达成率是 achieved_rps ÷ requested_rps,保留到小数点后第三位。
  6. 在 /root/lt-generator-limits/verdict.txt 中写六行。baseline_service_ms= 是 c1.csv 响应时间的中位数,loaded_service_ms= 是 shortfall.csv 的中位数(毫秒整数),service_ratio= 是两者之比(保留到小数点后第二位),rps_ratio= 是第 5 步的达成率,verdict= 是按下面规则选出的值,reason= 是至少 60 个字符的依据。规则:rps_ratio 大于等于 0.8 则为 ok,否则如果 service_ratio 大于等于 1.3 则为 server,两者都不是则为 generator。
  7. 在把打开文件数上限降到 64 的 shell 中运行 hey -n 400 -c 200 -t 5,把人类可读的输出原样留在 /root/lt-generator-limits/fdlimit.txt 中(不带 -o csv)。然后在 /root/lt-generator-limits/07-fd.txt 中写五行 nofile=、concurrency=、ok_count=、error_count=、limit=。成功数和错误数从 fdlimit.txt 中统计,limit= 写这次失败是哪一方的极限,server 或 generator。
  8. 创建 /root/lt-generator-limits/check-report.sh。以报告文件路径为第一个参数,如果 requested_rps=、achieved_rps=、achieved_ratio= 三行都带有数字,就以 0 结束,只要缺一行,就必须以非 0 值结束。然后写出一份能通过该检查的报告 /root/lt-generator-limits/report.md,其中三个值必须与第 5 步测得的值相同。

参考

启动一个服务时间固定的压力对象

把 /opt/lab/lt/lt-generator-limits/slow.py 以 0.2 秒的响应启动在端口 8090 上。然后用 curl -s -o /dev/null -w '%{time_total}\n' 请求 / 五次,把这五行原样留在 /root/lt-generator-limits/01-probe.txt 中,并在 /root/lt-generator-limits/01-service.txt 中写两行 port= 和 service_ms=。service_ms 写这五行的中位数,以毫秒整数表示。

这个对象没有加锁,同时进来多少,就同时处理多少——在本实验中,极限不在服务器,而在产生负载的一侧。后台运行用 nohup ... &,请等 1 秒左右,直到它起来。中位数是 sort -n 之后中间的那一行。

先用利特尔法则预测上限

创建 /root/lt-generator-limits/predict.tsv。没有表头,共四行,每行是以制表符分隔的两个字段 <동시성> <초당 요청 수 상한>(占位符依次为并发度、每秒请求数上限)。并发度依次为 1、2、5、10,上限是 동시성 ÷ 서비스 시간(초)(即并发度 ÷ 服务时间(秒)),保留到小数点后第二位。服务时间是第 1 步测得的值。

在闭环中,一个 worker 在等待响应期间,不会发送下一个请求。所以 C 个 worker 所能产生的最大速率是 C ÷ 响应时间(利特尔法则)。这个上限与服务器性能无关——无论服务器多快,在等待期间请求都发不出去。

实际测量,看预测对不对

用 hey -t 60 -o csv 测量两次。并发度 1 用 -n 30,并发度 5 用 -n 125,把原始 CSV 分别原样留在 /root/lt-generator-limits/c1.csv 和 /root/lt-generator-limits/c5.csv 中。然后在 /root/lt-generator-limits/03-measured.tsv 中写两行,每行是以制表符分隔的三个字段 <동시성> <예측 RPS> <측정 RPS>(占位符依次为并发度、预测 RPS、实测 RPS)(保留到小数点后第二位)。总 RPS 按 요청 수 ÷ (max(offset + response-time) − min(offset))(即请求数 ÷ (max(offset + response-time) − min(offset)))计算——只要有 hey 的 CSV 中的两个字段就能求出。

hey 的 CSV 第一行是表头,第一个字段是 response-time,最后一个字段是 offset(从测试开始到发送该请求的时刻)。实测值不超过预测值才是正常的——如果超过了,就是服务时间测错了,或者对象提前断开了响应。

-q 不是总目标速率,而是每个 worker 的上限

用 hey -n 100 -c 5 -q 2 -t 60 -o csv 测量,把原始文件留在 /root/lt-generator-limits/q.csv 中。然后在 /root/lt-generator-limits/04-q.txt 中写五行 q_flag=、workers=、expected_total_rps=、measured_rps=、per_worker=。expected_total_rps 是根据 -q 值和 worker 数预测的总速率,measured_rps 是根据 q.csv 重新计算的值(保留到小数点后第二位),per_worker 在 -q 是每个 worker 的上限时为 yes,是总上限时为 no。

如果 -q 是总上限,总速率就应该在 2 左右。请看实际是多少,再作判断。也请确认 hey 的文档是怎么写这个选项的——一个词的差别,就会让整个测试变成另一个实验。必须设得比并发度 5 的上限(25)更低,限制才会真正起作用。

制造一个设定了目标、却连一半都没能施加上去的测试

用 hey -n 100 -c 2 -q 25 -t 60 -o csv 测量,把原始文件留在 /root/lt-generator-limits/shortfall.csv 中。-q 25 加上 2 个 worker,所以请求的负载是每秒 50 次。在 /root/lt-generator-limits/05-shortfall.txt 中写三行 requested_rps=、achieved_rps=、achieved_ratio=。达成率是 achieved_rps ÷ requested_rps,保留到小数点后第三位。

并发度 2、响应 0.2 秒,按利特尔法则,上限是每秒 10 次。无论把速率上限设得多高,都不可能超过它——速率选项的意思是“不要发送得比这更快”,而不是“发送到这么多”。

是服务器饱和,还是生成器极限

在 /root/lt-generator-limits/verdict.txt 中写六行。baseline_service_ms= 是 c1.csv 响应时间的中位数,loaded_service_ms= 是 shortfall.csv 的中位数(毫秒整数),service_ratio= 是两者之比(保留到小数点后第二位),rps_ratio= 是第 5 步的达成率,verdict= 是按下面规则选出的值,reason= 是至少 60 个字符的依据。规则:rps_ratio 大于等于 0.8 则为 ok,否则如果 service_ratio 大于等于 1.3 则为 server,两者都不是则为 generator。

服务器一旦饱和,处理时间就会增加。如果处理时间不变,只是总速率达不到目标,说明服务器还闲着,极限在产生负载的一侧。如果不做这个区分,就会给完好无损的服务器再添加实例。

生成器自身成为极限的时刻

在把打开文件数上限降到 64 的 shell 中运行 hey -n 400 -c 200 -t 5,把人类可读的输出原样留在 /root/lt-generator-limits/fdlimit.txt 中(不带 -o csv)。然后在 /root/lt-generator-limits/07-fd.txt 中写五行 nofile=、concurrency=、ok_count=、error_count=、limit=。成功数和错误数从 fdlimit.txt 中统计,limit= 写这次失败是哪一方的极限,server 或 generator。

ulimit -n 64 只对该 shell 及其子进程生效——请像 bash -c 'ulimit -n 64; hey ...' 这样写在一行里。hey 的输出中有 Status code distribution 和 Error distribution 两节。读一读错误措辞,就能知道这次失败是谁造成的。

让报告模板自己去提问

创建 /root/lt-generator-limits/check-report.sh。以报告文件路径为第一个参数,如果 requested_rps=、achieved_rps=、achieved_ratio= 三行都带有数字,就以 0 结束,只要缺一行,就必须以非 0 值结束。然后写出一份能通过该检查的报告 /root/lt-generator-limits/report.md,其中三个值必须与第 5 步测得的值相同。

评分器会故意给这个脚本一份缺了项目的报告,看它能不能判定失败——无条件以 0 结束的脚本通不过。这样一个小小的检查,就能让“我们施加了每秒 500 次”自己证明自己。报告中也请一并写下测试条件。