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

压力测试

负载阶梯与饱和点

在 TT Lab 中继续学习

目标

像爬梯一样逐级提高并发度,测量吞吐量与延迟的关系,用数字找出饱和点以及满足 SLA 的最大负载点。然后用 Little 定律验证测量本身是否有效。产物放在 /root/lt2/ 下。

为什么重要

负载测试中最隐蔽的失败,是“负载生成器没有产生目标负载”。如果一次响应占用 2 秒,同步生成器在这 2 秒内不会发送原本应到达的请求。系统最慢区间的样本会整体消失,而生成器反而配合了自己制造的背压。结果是即使提高负载,p99 也不变化,只有生产环境的尾延迟恶化。因此,每次运行都需要附加一行验收检查:报告的实际吞吐量是否与设定的目标吞吐量一致。如果不一致,该次运行的延迟分布就不可信。本实验的第 6、7 步正是进行这项检查。

准备——启动负载目标

本实验运行在 Ubuntu 24.04 VM 上,并且已安装 Docker。即便如此,我们仍以Python 进程而不是容器运行目标。不是因为做不到,而是因为这样才正确。

不能随便选择负载目标。只有单个请求成本明确的目标,负载阶梯才会呈现应有形状,第 6 步用 Little 定律进行的复核(rps × 평균 지연 ≈ 동시성)才成立。如果 nginx 只返回静态文件,每次请求甚至不到 1ms,测量到的就不是服务器,而是负载生成器自身——提高阶梯也看不到拐点;即使出现,也只是 hey 的极限,而非目标系统的极限。

下面的目标将单次请求成本设为 50ms。正因为存在这 50ms,提高并发时才能清晰看到吞吐量先上升、再停止增长的位置。

mkdir -p /root/lt2
cat > /root/lt2/target.py <<'PY'
import time
from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
BODY = b"labhub load-test target
"
class H(BaseHTTPRequestHandler):
    protocol_version = "HTTP/1.1"
    disable_nagle_algorithm = True     # 헤더와 본문이 따로 나가면 지연 ACK 로 40ms 가 얹힌다
    def do_GET(self):
        time.sleep(0.05)               # 요청 하나의 처리 비용
        self.send_response(200); self.send_header("Content-Length", str(len(BODY)))
        self.end_headers(); self.wfile.write(BODY)
    def log_message(self, *a): pass
ThreadingHTTPServer(("127.0.0.1", 8085), H).serve_forever()
PY
setsid nohup python3 /root/lt2/target.py >/dev/null 2>&1 &
curl -s -o /dev/null -w '%{http_code}
' http://127.0.0.1:8085/    # 200

如果一定要改用容器,也必须像 docker run -d --name lt-web -p 127.0.0.1:8085:8080 -v /root/lt2:/app python:3.12-alpine python /app/target.py 这样封装同一个 Python 目标。基于上一段的原因,改成 nginx 会使后续所有数字失去意义。

步骤

  1. 在 /root/lt2/plan.md 中编写阶梯计划。必须包括:并发度 1, 2, 5, 10, 20 五个阶段、每个阶段的持续时间、预热计划,以及成功标准(例如 p95 阈值)。
  2. 创建 /root/lt2/ramp.sh 并执行 chmod +x。它必须把输出目录作为第一个参数($1),依次运行并发度 1, 2, 5, 10, 20,并将结果保存为 $1/c<동시성>.txt(例如 c10.txt)。
  3. 针对 /root/lt2/out 运行脚本。必须生成 /root/lt2/out/c1.txt、c2.txt、c5.txt、c10.txt、c20.txt 五个文件;每个文件的总响应数至少为 100,并保留 Requests/sec 和 95% in 行。
  4. 创建 /root/lt2/ramp.csv。格式为 concurrency,rps,p95,errors,写入并发度 1/2/5/10/20 的五行数据。rps 和 p95 读取对应 cN.txt 的值;errors 是状态码分布中非 2xx 的响应数(整数,必须精确一致)。
  5. 在 /root/lt2/knee.txt 中写一行 knee_concurrency=。按照 1 → 2 → 5 → 10 → 20 的顺序查看并发度,找到相对前一阶段 RPS 增长率首次低于 10% 的位置,并把其前一个并发度作为答案。若始终增长 10% 以上,答案为 20。
  6. 在 /root/lt2/little.txt 中写四行:concurrency=10、rps=(out/c10.txt 的 Requests/sec)、latency_avg_s=(同一文件的 Average)、product=(rps × 平均延迟,保留两位小数)。乘积与并发度 10 相差很大时,该次运行没有产生目标负载。
  7. 再运行一次固定到达率的(open)测试并保存到 /root/lt2/open.txt。在 /root/lt2/model.md 中写明:closed(固定并发)模型说明、open(固定到达率)模型说明、coordinated omission 说明,以及 target_rps=(设定的目标吞吐量)和 actual_rps=(从 open.txt 读取的实际吞吐量)两行。
  8. 在 /root/lt2/max-safe.txt 中写两行:max_safe_concurrency=、max_safe_rps=。答案是 ramp.csv 中p95 不超过 0.2 秒且 errors 为 0 的行里最大并发度及其对应 RPS。

参考

编写负载阶梯计划

在 /root/lt2/plan.md 中编写阶梯计划。必须包括:并发度 1, 2, 5, 10, 20 五个阶段、每个阶段的持续时间、预热计划,以及成功标准(例如 p95 阈值)。

在 /root/lt2/plan.md 中写明五个并发阶段、各阶段持续时间、预热和成功标准。如果不预先定义通过条件,就会在看到结果后才制定标准。

编写执行脚本

创建 /root/lt2/ramp.sh 并执行 chmod +x。它必须把输出目录作为第一个参数($1),依次运行并发度 1, 2, 5, 10, 20,并将结果保存为 $1/c<동시성>.txt(例如 c10.txt)。

/root/lt2/ramp.sh 接收输出目录作为第一个参数,依次运行五个阶段,并将结果保存为 c<并发度>.txt。硬编码路径后无法在其他目录重跑。不要忘记 chmod +x。

运行五个阶段

针对 /root/lt2/out 运行脚本。必须生成 /root/lt2/out/c1.txt、c2.txt、c5.txt、c10.txt、c20.txt 五个文件;每个文件的总响应数至少为 100,并保留 Requests/sec 和 95% in 行。

/root/lt2/out 下必须包含 c1、c2、c5、c10、c20 的全部结果,且每阶段至少 100 个响应。每个文件需保留摘要和延迟分布段,以供下一步读取。

创建结果表

创建 /root/lt2/ramp.csv。格式为 concurrency,rps,p95,errors,写入并发度 1/2/5/10/20 的五行数据。rps 和 p95 读取对应 cN.txt 的值;errors 是状态码分布中非 2xx 的响应数(整数,必须精确一致)。

在 /root/lt2/ramp.csv 中整理各并发度的 rps、p95、errors。数值必须来自原始输出,errors 是非 2xx 响应数。

寻找饱和点

在 /root/lt2/knee.txt 中写一行 knee_concurrency=。判定规则如下:按 1 → 2 → 5 → 10 → 20 查看并发度,找到相对前一阶段 RPS 增长率首次低于 10% 的位置,并把其前一个并发度作为答案。若始终增长 10% 以上,答案为 20。

在 /root/lt2/knee.txt 中写一行 knee_concurrency=。答案是吞吐量开始不再显著增长位置之前的并发度,精确规则已写在说明中。

用 Little 定律验证运行

在 /root/lt2/little.txt 中写四行:concurrency=10、rps=(out/c10.txt 的 Requests/sec)、latency_avg_s=(同一文件的 Average)、product=(rps × 平均延迟,保留两位小数)。乘积与并发度 10 相差很大时,该次运行没有产生目标负载。

在 /root/lt2/little.txt 中写入并发度 10 区间的 rps、平均延迟及其乘积。乘积与并发度相差很大,说明负载生成器没有真正产生目标负载。

比较开放与封闭模型

再运行一次固定到达率的(open)测试并保存到 /root/lt2/open.txt。在 /root/lt2/model.md 中写明:closed(固定并发)模型说明、open(固定到达率)模型说明、coordinated omission 说明,以及 target_rps=(设定的目标吞吐量)和 actual_rps=(从 open.txt 读取的实际吞吐量)两行。

单独运行固定到达率测试并保存到 /root/lt2/open.txt,在 model.md 中说明两种模型与 coordinated omission。并列写出目标吞吐量和实际吞吐量。

计算满足 SLA 的最大负载点

在 /root/lt2/max-safe.txt 中写两行:max_safe_concurrency=、max_safe_rps=。答案是 ramp.csv 中p95 不超过 0.2 秒且 errors 为 0 的行里最大并发度及其对应 RPS。

在 /root/lt2/max-safe.txt 中写入满足条件的最大并发度及其吞吐量。判定条件是说明中的两项,必须直接从表中选择。