9.10 Lab 9:总编排
上一节:9.9 步骤八:固化门禁与交付报告 | 下一节:无(全书结束) 配套代码:09-capstone/README 预计时长:3–5 天(分为 8 次,每次 2–6 小时)
一、这个 Lab 的定位
它不是"一个新的练习",而是把前八章的 Lab 串成一条线:
| 本 Lab 的步骤 | 复用了哪个 Lab |
|---|---|
| 步骤一(定义目标) | Lab 1 |
| 步骤二(搭服务) | Lab 3(组件基准的环境) |
| 步骤三(基线) | Lab 4(观测回路)+ Lab 5(噪声底线) |
| 步骤四(容量曲线) | Lab 3(阶梯加压) |
| 步骤五(剖析定位) | Lab 6(埋雷定位) |
| 步骤六(优化验证) | Lab 7(优化与三问) |
| 步骤七(长稳回归) | Lab 3(浸泡)+ Lab 5(回归) |
| 步骤八(门禁报告) | Lab 8(门禁)+ Lab 2(基准) |
如果你做过前八个 Lab,这里你会很熟悉——区别是:这次要在一个完整的、有真实依赖的服务上做。
二、八次实施安排
第 1 次:立项与搭环境(4 小时)
## 目标
跑通「服务 + 依赖 + 观测」三件套
## 任务
- [ ] 实现两个接口(POST /links、GET /{code})
- [ ] `docker compose up -d` 起 PostgreSQL + Redis
- [ ] 灌入 100 万行数据(幂律分布)+ `ANALYZE`
- [ ] 埋四层指标(确认 `/metrics` 有四组)
- [ ] **确认六颗埋雷都存在**(`verify-traps.sh`)
## 产出
- 可运行的服务
- `docs/experiments/E09-baseline/results/env.txt`
## 验收
- [ ] `curl localhost:8080/metrics | grep db_pool_pending` 有输出
- [ ] `verify-traps.sh` 显示六颗埋雷全部生效
- [ ] 手工验证两个接口功能正常
提示:这一步最容易卡在埋点——如果指标埋错了,后面所有分析都缺一块。宁可多花 1 小时确认四层指标齐全。
第 2 次:定义目标(2 小时)
## 目标
产出 SLO 表与延迟预算表
## 任务
- [ ] 写 `docs/slo.md`(含四要素)
- [ ] 写延迟预算表(含加法检查与并行/互斥标注)
- [ ] 写超时预算表(逐层递减)
- [ ] 列出「待确认项」(拿不到的业务数据)
## 验收
- [ ] 别人拿着 SLO 表能独立复现测试条件
- [ ] 延迟预算做过加法检查,余量为正
提示:这一步不要"抄"本文档的示例数字——用你自己的想法,或者问业务方。如果数字是抄来的,后面的容量规划会失去意义。
第 3 次:基线与噪声底线(4 小时)
## 目标
得到「优化前的数据」与「我的尺子有多准」
## 任务
- [ ] 验证观测回路(`verify-observability.sh`)
- [ ] 跑 3 轮基线(固定负载)
- [ ] 跑 10 轮噪声底线(代码不变)
- [ ] 算出声噪底线与 MDD
## 产出
- `experiments/E09-baseline/`
- `experiments/E09-noise/`
- **明确的「本环境能检测多大的差异」**
## 验收
- [ ] 基线数据的 CV < 10%
- [ ] 噪声底线与 MDD 已算出
- [ ] 无趋势漂移
提示:基线数据一定会显示 P99 超标(420ms)——这是正常的,正是埋雷 ① 的效果。不要这时候就去修它。
第 4 次:容量曲线(4 小时)
## 目标
找到「优化前的拐点」
## 任务
- [ ] 跑阶梯加压(每级稳态 2 分钟)
- [ ] **验证实际到达率 == 设定值**
- [ ] 找拐点
- [ ] 记录饱和度时间线(哪类资源先饱和)
## 验收
- [ ] 拐点数据与基线一致(约 400–700 RPS)
- [ ] 明确了「哪一类资源先饱和」
- [ ] 记录了「可以被排除的假设」
提示:拐点会比你想的低很多(600 RPS vs SLO 要求的 3000)。这个落差就是本 Lab 的教学价值——一个缺失的索引让容量差了 5 倍。
第 5 次:剖析定位(6 小时)
这是最核心的一次。
## 目标
产出瓶颈清单(含证据链、贡献占比、被排除的假设)
## 任务
- [ ] **在拐点负载下**剖析(不是低负载)
- [ ] 采四类火焰图
- [ ] 采连续多份线程快照
- [ ] 采数据库侧诊断
- [ ] 对六颗埋雷逐条验证(假设台账)
- [ ] **至少排除一条假设**
- [ ] 写 `bottlenecks.md`
## 验收
- [ ] 每条瓶颈有:现象、根因、证据链、贡献占比、被排除的假设、复现命令
- [ ] **`cpu` 火焰图为空 vs `wall` 火焰图有阻塞栈** 的对比已记录(埋雷 ④ 的证据)
- [ ] 「被排除的假设」有明确依据(不能只说"应该不是")
提示:这一步最容易犯的错是"跳到结论"——比如看到 P99 超标就直接说"是索引问题"(虽然猜对了,但没有证据)。证据链是要写进报告的,别人要能照着复现。
第 6 次:优化与验证(6 小时)
## 目标
修复瓶颈并证明有效
## 任务
- [ ] 按金字塔层级排序(不是按发现顺序)
- [ ] **一次只改一处**,每处测 3 轮
- [ ] 每处回答三问(提升多少、代价、回归)
- [ ] **至少否决一项优化**
- [ ] 写 `OPTIMIZATION.md`
## 验收
- [ ] 每项优化都有 before/after(≥3 轮)+ 显著性
- [ ] 每项都写了代价与回滚方式
- [ ] **优化 2(调度器隔离)测的是「受影响的接口」**
- [ ] 有否决记录(含重新评估条件)
提示:优化 2 会考验你的"测什么"——如果只测 GET /{code} 自己,你会看到 7% 的改善(接近噪声),可能误判为无效。要测 /health 这类不查数据库的接口。
第 7 次:长稳与回归(4 小时)
## 目标
确认「没有引入缓慢泄漏」与「没有让别的接口变慢」
## 任务
- [ ] 跑 1 小时浸泡(负载用 60%–80%)
- [ ] 观察堆基线、连接数、FD、P99 的趋势
- [ ] 检查数据库 QPS 是否有周期性脉冲(埋雷 ③)
- [ ] 用同一套脚本复跑基线(回归验证)
## 验收
- [ ] 堆基线稳定(斜率 < 5 MB/小时)
- [ ] 所有资源指标无单调上升
- [ ] 记录了「在噪声内的退化」(如写路径 +3.5%)
提示:埋雷 ③(缓存雪崩)只有这一步能看到——它的周期是 10 分钟,短跑压测覆盖不到。
第 8 次:固化与交付(6 小时)
## 目标
把成果变成「机制」和「报告」
## 任务
- [ ] CI 门禁(经噪声验证,零误报)
- [ ] 生产告警(含 runbook、含本项目特有告警)
- [ ] 容量表(每个数字可追溯)
- [ ] 写 `PERF-REPORT.md`(九段结构)
- [ ] 更新基线(单独提交,写明理由)
## 验收
- [ ] 门禁零误报
- [ ] 每条告警有 runbook
- [ ] 报告能被非性能工程师读懂
- [ ] 报告含「风险与未验证的场景」
提示:报告里最重要的不是数字,而是「摘要」——因为决策者只看摘要。摘要要写:一句话结论 + 投入 + 风险 + 建议。
三、总验收清单
## Lab 9 最终验收
### 交付物完整性(八份)
- [ ] 1. `docs/slo.md`(SLO 表 + 延迟预算表)
- [ ] 2. `docker-compose.yml` + `seed.sql`(一键起环境)
- [ ] 3. `experiments/E09-baseline/` + `E09-noise/`
- [ ] 4. `experiments/E10-capacity/`(容量曲线)
- [ ] 5. `docs/bottlenecks.md`(瓶颈清单)
- [ ] 6. `docs/OPTIMIZATION.md`(优化收益报告)
- [ ] 7. `experiments/E13-soak/` + 回归结果
- [ ] 8. `perf-gate.yml` + `docs/capacity.md` + `docs/PERF-REPORT.md`
### 质量检查(六个维度)
- [ ] **目标定义**:SLO 含四要素 + 延迟预算 + 崩溃线
- [ ] **数据可信度**:有噪声底线与置信区间,环境元数据完整
- [ ] **瓶颈定位**:有证据链、贡献占比、**被排除的假设**
- [ ] **优化验证**:有 before/after、代价分析、长稳回归、**否决记录**
- [ ] **报告表达**:含摘要与风险,**能被非性能工程师用于决策**
- [ ] **持续化**:门禁**经噪声验证**、有告警与容量表
### 诚实性检查(最容易失分)
- [ ] 明确声明了**环境局限**(如"压测客户端与服务同机")
- [ ] 明确列出了**未验证的场景**
- [ ] 「在噪声内的变化」标注为**不显著**(没有当成成果)
- [ ] 「被否决的优化」有记录(没有隐瞒)
四、常见问题
Q:我只有一台机器,能做完这个 Lab 吗?
A:能,但必须做两件事:① 用 Docker 的 --cpus 把压测客户端与服务隔开;② 在报告里明确声明这个局限(“数据可用于相对比较,不能用于绝对容量规划”)。不要假装同机数据是可信的——这是第 5 章强调的诚实原则。
Q:我的服务跑不出 420ms 这样的明显问题。
A:检查埋雷是否生效(verify-traps.sh)。最常见的失败原因是:① 索引无意中被加上了;② 查询走了缓存(缓存命中率高到掩盖了数据库问题);③ 数据量太小(1 万行时全表扫描也很快)。如果埋雷确实生效但问题不明显,就加大数据量(100 万 → 500 万)。
Q:时间不够,能否跳过某一步? A:不能跳的是:步骤三(没有基线就没有优化验证)、步骤五(没有剖析就是猜)、步骤六(没有验证就没有结论)。可以压缩的是:步骤四(容量曲线可以从完整版改成只测两三级)、步骤七(浸泡从 1 小时压到 30 分钟,但要声明局限)。
Q:最后的报告要写多长? A:摘要 1 段(决策者看)+ 正文 5–10 页。关键不是长度,而是「证据链完整 + 边界清晰」。一份 3 页但有完整证据链的报告,比 20 页只有数字的报告有价值得多。
Q:这个 Lab 做完了,接下来学什么? A:三个方向:① 分布式追踪与延迟归因的自动化(把第 6 章的手工分解变成自动的 span 分析);② 基于 USL 的可扩展性建模(预测多副本下的非线性衰减);③ 混沌工程与故障注入(把第 3 章的破坏性测试变成常态化的实验)。
五、全书回顾
九章的一条主线:
第 0 章 观念 → 为什么要怀疑数据
第 1 章 目标 → 定义「好」是什么
第 2 章 测量 → 保证数据可信
第 3 章 分层 → 决定在哪测
第 4 章 工具 → 怎么采数据
第 5 章 方法 → 让结论站得住
第 6 章 分析 → 从现象到根因
第 7 章 优化 → 改什么、怎么验证
第 8 章 持续 → 不再退化
第 9 章 实战 → 全部串起来
↓
第 9 章不是"最后一章",而是"回到第 0 章的循环"
最终的判断标准只有一个:
你的结论,别人能复现吗?
能复现 → 它是工程结论。 不能复现 → 它只是一次观察。
这就是全书想教的东西。