平均值一样,却只有一边把队列压垮了
目标
以同样的平均请求率,亲手生成等间隔、指数分布、突发三种到达,发送到同一个服务器,测量队列和延迟如何分歧。接着用实测对照闭环的思考时间决定有效请求率的定律,用数字展示假定均匀到达的容量估算偏差多少倍,再连同依据一起确定这个服务的到达模型和 worker 数。
为什么重要
容量估算几乎总是从平均值开始——每秒多少次,每次多少毫秒,所以需要多少个 worker。这个计算中隐藏着“请求均匀到来”的假设,而真实流量因为 cron、通知和重试,是以突发的形式到来的。队列响应的不是平均值,而是瞬间,所以即使平均利用率是 25%,在突发到来的瞬间 worker 也会不够,而这些排队又会引发新的超时和重试。另一边有闭环的陷阱。如果用虚拟用户数来测试,服务器变慢时负载也会随之减少,报告中的“扛到了每秒多少次”就不是服务器的极限,而是测试设置的极限。把这两件事亲手测量一遍,以后写负载时,就会在平均值旁边同时写下到达的形态。
步骤
- 用
python3 /opt/lab/lt/lt-arrival-process/server.py 8080 40 4启动/opt/lab/lt/lt-arrival-process/server.py(端口 8080、服务 40 毫秒、worker 4 个)。用curl -s -o /dev/null -w '%{time_total}\n'测量空闲时的响应时间六次,把得到的以秒为单位的数字每行一个、原样留在/root/lt-arrival-process/01-probe.txt中。然后在/root/lt-arrival-process/01-target.txt中写五行——service_ms=(六次的中位数,四舍五入成毫秒)、workers=(worker 数)、max_rate=(worker 数除以服务时间,每秒次数)、chosen_rate=25、utilization=(chosen_rate 除以 max_rate 所得的值,保留到小数点后第二位)。 - 创建
/root/lt-arrival-process/gen.py。以python3 gen.py <even|poisson|burst> <요청수> <초당요청> <시드> <출력CSV> [URL](占位符依次为 even、poisson 或 burst 之一,请求数、每秒请求数、种子、输出 CSV)调用。如果不给出 URL,就不发送,只把计划写入 CSV——在表头i,intended之后,为每个请求写下编号和预定时刻(以开始为基准的秒数)。even的间隔始终是 1/请求率,poisson的间隔是平均值为 1/请求率的指数分布,burst把 12 个请求在同一时刻集中发送,然后停歇 12/请求率。请接收种子,把随机数固定下来。为了确认,请运行一次python3 gen.py even 300 25 7 /root/lt-arrival-process/sched-even.csv。 - 用试运行把生成器运行三次,生成
/root/lt-arrival-process/sched-even.csv、/root/lt-arrival-process/sched-poisson.csv、/root/lt-arrival-process/sched-burst.csv(全部是300 25 7)。然后在/root/lt-arrival-process/schedules.tsv中写三行。每行是以制表符分隔的四个字段<모양> <요청 수> <평균 간격> <변동계수>(占位符依次为形态、请求数、平均间隔、变异系数),形态依次为even、poisson、burst,平均间隔以秒为单位保留到小数点后第四位,变异系数是间隔的标准差除以平均值,保留到小数点后第三位。 - 用三种到达方式,把同样数量的请求真正发送到同一个服务器。每种方式都先用
curl -s http://127.0.0.1:8080/reset清除记录,用python3 gen.py <모양> 150 25 7 /root/lt-arrival-process/run-<모양>.csv http://127.0.0.1:8080/work(占位符为形态)发送,再用curl -s http://127.0.0.1:8080/dump > /root/lt-arrival-process/srv-<모양>.csv(占位符为形态)获取服务器记录(形态为even、poisson、burst)。然后在/root/lt-arrival-process/latency.tsv中写三行——以制表符分隔的四个字段<모양> <성공 건수> <p50> <p95>(占位符依次为形态、成功次数、p50、p95),延迟是把run-*.csv的received减去sent,换算成毫秒,用最近秩求出,保留到小数点后第一位,只统计code为 200 的行。 - 服务器留下的
/root/lt-arrival-process/srv-*.csv中,每个请求都有qlen(该请求到达的那一刻系统内的请求数)。在/root/lt-arrival-process/queue.tsv中写三行——以制表符分隔的四个字段<모양> <기록 수> <최대 대기열> <평균 대기열>(占位符依次为形态、记录数、最大队列长度、平均队列长度),形态依次为even、poisson、burst,平均值保留到小数点后第二位。然后在/root/lt-arrival-process/05-note.txt中写两行worst=<최대 대기열이 가장 큰 모양>(占位符为最大队列长度最大的形态)和ratio=<그 최대값을 even 의 최대값으로 나눈 값, 소수 첫째 자리까지>(占位符为该最大值除以 even 最大值所得的值,保留到小数点后第一位)。 - 创建
/root/lt-arrival-process/closed.py。以python3 closed.py <URL> <사용자수> <생각시간초> <지속초> <출력CSV>(占位符依次为 URL、用户数、思考时间秒数、持续秒数、输出 CSV)调用时,让每个用户发送一个请求,停歇思考时间,在持续时间内不断重复。CSV 在表头user,sent,received,code之后,写以开始为基准的秒数。用python3 closed.py http://127.0.0.1:8080/work 8 0.2 10 /root/lt-arrival-process/closed.csv运行一次,并在/root/lt-arrival-process/think.txt中写六行——users=8、think_s=0.200、mean_r_s=(平均响应时间)、predicted_rate=(用户数除以 (mean_r_s 加 think_s))、measured_rate=(成功次数除以从最初的 sent 到最后的 received 的时间)、naive_rate=(把响应时间视为 0,用户数除以 think_s)。请求率保留到小数点后第三位,时间保留到小数点后第四位。 - 在
/root/lt-arrival-process/capacity.txt中写五行。formula_workers=(假定均匀到达的计算:把chosen_rate x service_s向上取整后的整数)、observed_even=、observed_burst=(分别是从srv-even.csv和srv-burst.csv中得到的同时处于系统内的请求数的最大值)、underestimate_factor=(observed_burst 除以 formula_workers 所得的值,保留到小数点后第一位)、verdict=<under|ok>(如果 formula_workers 小于 observed_burst,则为 under)。并发请求数的求法是:在arrive处加 1,在end处减 1,按时刻顺序扫描,取最大值。 - 在
/root/lt-arrival-process/arrival-model.txt中写四行。model=<even|poisson|burst>是今后确定这个服务的容量时使用的到达模型,workers=是按该模型确定的 worker 数(整数),evidence=要引用前面步骤制作的文件名和数字,用至少 60 个字符写明为什么选这个模型,risk=用至少 40 个字符写明如果这个模型错了,什么会最先垮掉。评分器会查看workers是否不小于第 7 步观测到的最大并发请求数,以及是否大于假定均匀到达的formula_workers。
参考
- 工作目录是
/root/lt-arrival-process。如果不存在,请先创建。 - 材料只有一个
/opt/lab/lt/lt-arrival-process/server.py。/work是干活的路径,/reset清除记录,/dump以 CSV 返回每个请求的seq,arrive,start,end,qlen。时间不是用 CPU,而是用time.sleep来消耗。 - 压力对象不是容器,而是 Python 标准库服务器。在本实验环境中无法启动容器。请用
(nohup python3 /opt/lab/lt/lt-arrival-process/server.py 8080 40 4 >/dev/null 2>&1 &)启动,用curl确认一次,再施加负载。 - 常见错误:在更换方式之前没有调用
/reset。上一次运行的记录会残留,使队列比较变得混乱。 - 常见错误:把突发到达的延迟读成“服务器变慢了”。服务时间没有变,增加的是等待时间——
srv-*.csv中start减去arrive,就是那段等待。 - k6 open vs closed models、k6 executors、k6 constant-arrival-rate、k6 ramping-arrival-rate、Service Level Objectives (SRE Book)
worker 有几个,每次要花多长时间
用 python3 /opt/lab/lt/lt-arrival-process/server.py 8080 40 4 启动 /opt/lab/lt/lt-arrival-process/server.py(端口 8080、服务 40 毫秒、worker 4 个)。用 curl -s -o /dev/null -w '%{time_total}\n' 测量空闲时的响应时间六次,把得到的以秒为单位的数字每行一个、原样留在 /root/lt-arrival-process/01-probe.txt 中。然后在 /root/lt-arrival-process/01-target.txt 中写五行——service_ms=(六次的中位数,四舍五入成毫秒)、workers=(worker 数)、max_rate=(worker 数除以服务时间,每秒次数)、chosen_rate=25、utilization=(chosen_rate 除以 max_rate 所得的值,保留到小数点后第二位)。
/work 是干活的路径,/reset 和 /dump 是处理记录的路径。空闲时指的是没有施加负载的状态——请一次只发送一个。算一算 4 个 worker 每个花 40 毫秒,每秒能处理多少次,就能看出接下来要施加的每秒 25 次是多么宽裕的负载。
能选择到达形态的生成器
创建 /root/lt-arrival-process/gen.py。以 python3 gen.py <even|poisson|burst> <요청수> <초당요청> <시드> <출력CSV> [URL](占位符依次为 even、poisson 或 burst 之一,请求数、每秒请求数、种子、输出 CSV)调用。如果不给出 URL,就不发送,只把计划写入 CSV——在表头 i,intended 之后,为每个请求写下编号和预定时刻(以开始为基准的秒数)。even 的间隔始终是 1/请求率,poisson 的间隔是平均值为 1/请求率的指数分布,burst 把 12 个请求在同一时刻集中发送,然后停歇 12/请求率。请接收种子,把随机数固定下来。为了确认,请运行一次 python3 gen.py even 300 25 7 /root/lt-arrival-process/sched-even.csv。
指数分布的间隔用 random.Random(seed).expovariate(rate) 生成。burst 的预定时刻只要一行 (i // 12) * (12 / rate) 就够了。三种方式中,最后一个请求的预定时刻都应该几乎相同——这意味着平均请求率相同。评分器会用它自己确定的参数直接运行这个工具。
同样的平均值,不同的形态
用试运行把生成器运行三次,生成 /root/lt-arrival-process/sched-even.csv、/root/lt-arrival-process/sched-poisson.csv、/root/lt-arrival-process/sched-burst.csv(全部是 300 25 7)。然后在 /root/lt-arrival-process/schedules.tsv 中写三行。每行是以制表符分隔的四个字段 <모양> <요청 수> <평균 간격> <변동계수>(占位符依次为形态、请求数、平均间隔、变异系数),形态依次为 even、poisson、burst,平均间隔以秒为单位保留到小数点后第四位,变异系数是间隔的标准差除以平均值,保留到小数点后第三位。
间隔是相邻预定时刻之差(299 个)。标准差请按总体来计算。三个平均间隔几乎相同,只有变异系数分化成 0、1 左右、远大于 1——这正是这一步想要展示的。burst 的间隔里有大量的 0。
用三种方式冲击同一个服务器
用三种到达方式,把同样数量的请求真正发送到同一个服务器。每种方式都先用 curl -s http://127.0.0.1:8080/reset 清除记录,用 python3 gen.py <모양> 150 25 7 /root/lt-arrival-process/run-<모양>.csv http://127.0.0.1:8080/work(占位符为形态)发送,再用 curl -s http://127.0.0.1:8080/dump > /root/lt-arrival-process/srv-<모양>.csv(占位符为形态)获取服务器记录(形态为 even、poisson、burst)。然后在 /root/lt-arrival-process/latency.tsv 中写三行——以制表符分隔的四个字段 <모양> <성공 건수> <p50> <p95>(占位符依次为形态、成功次数、p50、p95),延迟是把 run-*.csv 的 received 减去 sent,换算成毫秒,用最近秩求出,保留到小数点后第一位,只统计 code 为 200 的行。
三种方式的平均请求率相同,所以总时间也差不多结束。然而延迟分布并不相同——请特别看 p50 和 p95 拉开的程度。等间隔时两个值几乎贴在一起,而在突发时相距很远。发送三次大约要花 20 秒。
服务器统计的队列
服务器留下的 /root/lt-arrival-process/srv-*.csv 中,每个请求都有 qlen(该请求到达的那一刻系统内的请求数)。在 /root/lt-arrival-process/queue.tsv 中写三行——以制表符分隔的四个字段 <모양> <기록 수> <최대 대기열> <평균 대기열>(占位符依次为形态、记录数、最大队列长度、平均队列长度),形态依次为 even、poisson、burst,平均值保留到小数点后第二位。然后在 /root/lt-arrival-process/05-note.txt 中写两行 worst=<최대 대기열이 가장 큰 모양>(占位符为最大队列长度最大的形态)和 ratio=<그 최대값을 even 의 최대값으로 나눈 값, 소수 첫째 자리까지>(占位符为该最大值除以 even 最大值所得的值,保留到小数点后第一位)。
三份记录的行数必须相同——因为发送的请求数相同。然而最大队列长度并不相同。等间隔时不会超过 worker 数,而突发时,一次涌来多少,就原样堆积多少。这意味着,即使平均请求率相同,瞬间的并发请求数也是由到达形态决定的。
思考时间决定请求率
创建 /root/lt-arrival-process/closed.py。以 python3 closed.py <URL> <사용자수> <생각시간초> <지속초> <출력CSV>(占位符依次为 URL、用户数、思考时间秒数、持续秒数、输出 CSV)调用时,让每个用户发送一个请求,停歇思考时间,在持续时间内不断重复。CSV 在表头 user,sent,received,code 之后,写以开始为基准的秒数。用 python3 closed.py http://127.0.0.1:8080/work 8 0.2 10 /root/lt-arrival-process/closed.csv 运行一次,并在 /root/lt-arrival-process/think.txt 中写六行——users=8、think_s=0.200、mean_r_s=(平均响应时间)、predicted_rate=(用户数除以 (mean_r_s 加 think_s))、measured_rate=(成功次数除以从最初的 sent 到最后的 received 的时间)、naive_rate=(把响应时间视为 0,用户数除以 think_s)。请求率保留到小数点后第三位,时间保留到小数点后第四位。
把利特尔法则应用到单个用户的一个往返上,有效请求率是 N / (R + Z)。naive_rate 是漏掉 R 时的预测,与实测比较一下,就能看出为什么不能漏掉。这一步要花十几秒。
假定均匀到达的容量,会偏差多少倍
在 /root/lt-arrival-process/capacity.txt 中写五行。formula_workers=(假定均匀到达的计算:把 chosen_rate x service_s 向上取整后的整数)、observed_even=、observed_burst=(分别是从 srv-even.csv 和 srv-burst.csv 中得到的同时处于系统内的请求数的最大值)、underestimate_factor=(observed_burst 除以 formula_workers 所得的值,保留到小数点后第一位)、verdict=<under|ok>(如果 formula_workers 小于 observed_burst,则为 under)。并发请求数的求法是:在 arrive 处加 1,在 end 处减 1,按时刻顺序扫描,取最大值。
请确认把 qlen 字段直接当作最大值,得到的数是否相同——因为那是服务器在到达瞬间统计的值,所以通常相同。不过亲自扫描一遍,就能亲眼看出“即使平均利用率很低,瞬间并发也可能大得多”是怎么回事。除法的分母可能是 1,所以请按小数来计算。
把这个服务的到达过程确定为什么
在 /root/lt-arrival-process/arrival-model.txt 中写四行。model=<even|poisson|burst> 是今后确定这个服务的容量时使用的到达模型,workers= 是按该模型确定的 worker 数(整数),evidence= 要引用前面步骤制作的文件名和数字,用至少 60 个字符写明为什么选这个模型,risk= 用至少 40 个字符写明如果这个模型错了,什么会最先垮掉。评分器会查看 workers 是否不小于第 7 步观测到的最大并发请求数,以及是否大于假定均匀到达的 formula_workers。
第 3 步的变异系数和第 5 步的最大队列长度,就是这个服务的到达并不均匀的证据。一旦接受这个证据,worker 数就由突发的规模而不是平均值来决定。留出多少余量由你来定,但如果低于观测到的最大并发请求数,每次那样的突发到来,都会产生队列。