1.9 配套代码:Lab 1 的填写模板与算术校验
对应小节:1.9 Lab 1 这里给的是能直接复制粘贴的模板,以及一个帮你做预算加法的校验器。
一、SLO 表模板(复制到 docs/slo.md)
# SLO 表
> 最后更新:YYYY-MM-DD | 对应版本:<commit>
> 填写说明:四要素缺一不可。写不出来的项标「待确认」,并注明打算怎么获取。
## 接口 1:<GET /orders/{id}>
| 字段 | 值 |
| --- | --- |
| 接口 | |
| 关键路径 | 客户端 → ? → ? → 数据库 |
| 目标 QPS(**峰值**) | |
| P50 / P95 / P99 / P999 | |
| 错误率阈值 | |
| 超时率阈值 | |
| 负载前提:数据量 | |
| 负载前提:**数据分布** | (uniform / zipf(alpha) / 真实流量回放——必填) |
| 负载前提:缓存状态 | (cold / warm,预热多久) |
| **测量点** | 客户端 / 网关(不能填「应用内」) |
| 预热时长 | |
| 稳态时长 | |
| 验收轮次 | 至少 3 轮,取中位数 |
| 环境 | 实例规格、副本数、容器 CPU limit |
| 崩溃线 | (压力测试终止条件) |
| 测量日期与版本 | |
**待确认项**:
- [ ] 峰值 QPS 的真实数据(向业务方确认 / 从监控取值)
- [ ] 数据分布(从生产 SQL 日志统计热点比例)
## 接口 2:<POST /orders>
(同上表)
二、延迟预算表模板
## 延迟预算:<GET /orders/{id}>
SLO 目标:P99 ≤ ______ ms
| 环节 | 预算(P99) | 实测 | 状态 | 备注 |
| --- | --- | --- | --- | --- |
| 客户端 → 网关 | | 待测 | | |
| 网关处理 | | 待测 | | TLS / 鉴权 / 限流 |
| **应用排队** | | 待测 | | ⚠️ 没有对应代码,靠饱和度指标验证 |
| 应用逻辑 | | 待测 | | |
| ├ PostgreSQL | | 待测 | | |
| ├ Redis | | 待测 | | |
| └ 下游依赖 | | 待测 | | |
| 响应传输 | | 待测 | | |
| **余量** | | — | | 必须 > 0,建议占 10%~20% |
**合计检查**:所有预算 + 余量 = ______ ms,与 SLO ______ ms 相比:☐ 通过 ☐ 超出
(超出时必须砍某一跳,或放宽 SLO)
**并行标注**:哪些依赖是并行调用的?并行组:__________(并行段取最大值,不相加)
## 超时预算
| 层 | 上游给的时间 | 本层超时 | 留给下游 | 检查 |
| --- | --- | --- | --- | --- |
| 客户端 | — | | | |
| 网关 | | | | 必须 < 上游 |
| 应用 | | | | 必须 < 网关 |
| → PostgreSQL | | | — | 必须 < 应用给的总时间 |
| → Redis | | | — | 必须 < 应用给的总时间 |
| → 下游服务 | | | — | 必须 < 应用给的总时间 |
**重试预留**:如果允许重试,单次超时必须更短。重试次数:____,退避策略:____
三、预算算术校验器
手算容易错,用脚本核对:
# tools/check_budget.py
"""
读取延迟预算 CSV,检查:
1. 合计是否超过 SLO
2. 余量是否为正
3. 超时链是否逐层递减
CSV 格式(表头固定):
name,p99_ms,parallel_group,timeout_upstream_ms,timeout_self_ms
"""
import csv, sys
def check(path):
rows = list(csv.DictReader(open(path)))
problems, notes = [], []
serial = [float(r["p99_ms"]) for r in rows if not r.get("parallel_group")]
groups = {}
for r in rows:
g = r.get("parallel_group")
if g:
groups.setdefault(g, []).append(float(r["p99_ms"]))
parallel_total = sum(max(v) for v in groups.values())
total = sum(serial) + parallel_total
slo = float(input("请输入 SLO 的 P99 (ms): "))
reserve_row = next((r for r in rows if "余量" in r["name"]), None)
reserve = float(reserve_row["p99_ms"]) if reserve_row else 0.0
print(f"\n串行合计 : {sum(serial):8.1f} ms")
for g, v in groups.items():
print(f"并行组 {g:<8}: {max(v):8.1f} ms(组内 {len(v)} 项取最大值)")
print(f"余量 : {reserve:8.1f} ms")
print(f"总计 : {total:8.1f} ms")
print(f"SLO : {slo:8.1f} ms")
if total > slo:
problems.append(f"❌ 预算合计超出 SLO {total - slo:.1f} ms —— 必须砍某一跳或放宽 SLO")
if reserve <= 0:
problems.append("❌ 余量必须为正数")
elif reserve / slo < 0.10:
problems.append(f"⚠️ 余量只占 {reserve/slo*100:.1f}%,建议 10%~20%")
if not any("排队" in r["name"] for r in rows):
problems.append("⚠️ 预算表缺少「应用排队」这一项(它没有代码,但必须计入预算)")
# 超时链检查
chain = [r for r in rows if r.get("timeout_self_ms")]
prev = float("inf")
for r in chain:
self_to = float(r["timeout_self_ms"])
up = float(r["timeout_upstream_ms"]) if r.get("timeout_upstream_ms") else float("inf")
if up != float("inf") and self_to > up:
problems.append(f"❌ {r['name']}:本层超时 {self_to}ms > 上游给的 {up}ms(会级联堆积)")
prev = self_to
print()
if problems:
print("\n".join(problems))
sys.exit(1)
print("✅ 预算自洽,超时链逐层递减")
if __name__ == "__main__":
check(sys.argv[1])
示例 CSV:
name,p99_ms,parallel_group,timeout_upstream_ms,timeout_self_ms
客户端 → 网关,25,,,
网关处理,15,,300,235
应用排队,20,,,
应用逻辑,45,,,
PostgreSQL,40,,200,115
Redis,5,,200,30
下游 HTTP,25,,200,80
响应传输,5,,,
余量,20,,,
四、错误埋点检查清单
做任务 3 时,按这四类逐个排查现有代码:
### 检查 1:分母
搜索关键词:`timer.record`、`histogram`、`requests_total`、`increment`
问题:是否只在成功路径上记录?
修正:把记录放在 `finally` 里,或确保失败/超时也走同一条统计路径。
### 检查 2:测量范围
问题:计时是否漏掉了序列化 / 响应写出?
验证方法:对比 `http_server_requests`(框架自动埋的全量计时)
与你自己埋的 `app_handler_duration`,
如果框架的数字明显更大,说明你的计时范围偏小。
### 检查 3:测量点声明
问题:这个指标是客户端、网关还是应用内测的?
修正:在指标名或 description 里写清楚,例如
`app.handler.duration` → "handler 内部耗时,不含网络与排队"。
### 检查 4:依赖级埋点是否存在
自检问题:你能回答「这个接口的 P99 里,数据库占多少毫秒」吗?
如果不能,说明缺少依赖级 Timer(见 03 节)。
五、完成 Lab 1 后的自检
# 1. 模板文件已填
ls -la docs/slo.md
# 2. 预算校验通过
python3 tools/check_budget.py docs/budget.csv
# 3. 实验档案已建
ls -la docs/experiments/E01-slo-baseline/
# 4. 索引已更新
grep -n "E01" docs/experiments/README.md
四项都通过,Lab 1 就完成了。
六、常见问题
Q:预算表里的「实测」列现在填不了怎么办? A:留空是对的。这一列要等第 4 章搭好观测回路后回来填,那时这张表就变成了你的定位工具。现在硬填数字反而是自欺。
Q:CSV 里的并行组怎么标?
A:给并行调用的条目填同一个组名(例如都填 fanout),串行的留空。校验器会对同组取最大值。
Q:我算出来的合计严重超过 SLO,说明什么? A:两种情况之一:① 你的 SLO 定得太严(用户其实能接受更慢);② 当前架构确实达不到(需要砍功能或加资源)。这两种结论都比「不知道」有价值得多——这正是做预算的意义。