报告写着每秒500次,实际只有48次
目标
用利特尔法则预测闭环生成器的吞吐量上限并实际测量对照,实测确认 hey 的速率选项是每个 worker 的上限,并建立区分“未达到目标速率的结果是服务器饱和还是生成器极限”的流程,以及报告检查脚本。
为什么重要
压力测试报告中最常出错的数字,不是延迟,而是负载本身。并发度为 10、响应 200 毫秒的闭环,无论写多高的目标,每秒都不会超过 50 次。然而工具会不加警告地输出漂亮的百分位数,只经历了实际负载十分之一的服务器,当然看起来很健康。速率选项的定义因工具而异,一个词读错,实际负载就会按 worker 数被放大或缩小;当打开文件数这样的生成器自身资源成为极限时,那种失败会混进服务器的错误率里。所以在读取结果之前,必须先确认请求的负载和实际施加的负载是否一致,并且要把这个确认交给报告模板和检查脚本,而不是靠人的记忆。
步骤
- 把
/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写这五行的中位数,以毫秒整数表示。 - 创建
/root/lt-generator-limits/predict.tsv。没有表头,共四行,每行是以制表符分隔的两个字段<동시성> <초당 요청 수 상한>(占位符依次为并发度、每秒请求数上限)。并发度依次为 1、2、5、10,上限是동시성 ÷ 서비스 시간(초)(即并发度 ÷ 服务时间(秒)),保留到小数点后第二位。服务时间是第 1 步测得的值。 - 用
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 -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。 - 用
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,保留到小数点后第三位。 - 在
/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。 - 创建
/root/lt-generator-limits/check-report.sh。以报告文件路径为第一个参数,如果requested_rps=、achieved_rps=、achieved_ratio=三行都带有数字,就以 0 结束,只要缺一行,就必须以非 0 值结束。然后写出一份能通过该检查的报告/root/lt-generator-limits/report.md,其中三个值必须与第 5 步测得的值相同。
参考
- 工作目录是
/root/lt-generator-limits。如果不存在,请先创建。 - 压力对象只有一个
/opt/lab/lt/lt-generator-limits/slow.py。Pod 中无法启动容器,所以直接在 127.0.0.1 上启动 Python 标准库的 HTTP 服务器来使用。 - hey 的 CSV 第一行是表头,第一个字段是
response-time,最后一个字段是offset。总 RPS 按요청 수 ÷ (max(offset + response-time) − min(offset))(即请求数 ÷ (max(offset + response-time) − min(offset)))求得。 - 常见错误:把速率选项读成总目标速率。第 4 步就是这个确认。
- 常见错误:把没有达到目标的结果直接写成“服务器极限”。如果服务器一侧的处理时间不变,极限就在生成器一侧。
- hey — 选项 -n、-c、-q 的定义、k6 — open vs closed model、k6 — scenarios 与到达率、vegeta — 固定速率攻击、wrk2 — 目标速率与校正
启动一个服务时间固定的压力对象
把 /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 次”自己证明自己。报告中也请一并写下测试条件。