阅读系统的方法¶
系统问题难,不是因为术语多,而是因为同一现象同时受到多层契约约束。最有效的读法不是背 API,而是建立一条可验证的因果链。
从现象写出可证伪的问题¶
“服务很慢”无法直接研究。先把它改写为带对象、时间窗口、负载与比较基线的问题:
在固定请求分布与并发度下,为什么 p99 延迟从 40 ms 上升到 180 ms,而吞吐量和 CPU 利用率几乎不变?
这个问题已经排除了很多无关方向,同时保留了排队、尾延迟、I/O、锁竞争、GC、调度抖动和下游重试等可能性。好的系统问题至少包含:
- 工作负载:输入大小、读写比例、并发、到达过程、热点和突发;
- 观察量:延迟分位数、吞吐、带宽、CPU 时间、运行队列、缺页、重传;
- 边界:进程、容器、主机、NUMA 节点、网络路径、版本;
- 对照:基线版本、另一台机器、另一种参数或可重复的空载实验。
七层检查表¶
1. 契约¶
先问规范保证什么,而不是实现“通常怎样”。例如 C++ 的 happens-before、POSIX write 的返回值、TCP 的字节流语义、文件系统的崩溃保证和数据库的隔离级别都属于契约。契约决定哪些观察能够跨版本成立。
2. 状态¶
列出状态及其唯一或共享所有者:
| 状态 | 常见所有者 | 需要追问 |
|---|---|---|
| 协程栈、程序计数器 | 运行时任务 | 谁调度?挂起点保存什么? |
| 页表与 TLB | 进程地址空间、CPU | 修改怎样传播?何时失效? |
| TCP 发送窗口 | 内核协议栈 | 由确认、拥塞还是接收端限制? |
| 缓存行 | 核心与一致性目录 | 谁可写?失效何时可见? |
| 日志序列号 | 存储引擎 | 持久化顺序与提交点在哪里? |
没有状态图,就很容易把控制消息、数据副本和逻辑所有权混在一起。
3. 控制流¶
把“调用函数”改写为状态迁移。下面的简化循环明确了就绪、执行、等待与取消的分叉:
from collections import deque
ready = deque(["request"])
waiting = {}
while ready:
task = ready.popleft()
event = step(task)
if event.kind == "yield":
ready.append(task)
elif event.kind == "wait":
waiting[event.key] = task
elif event.kind == "cancel":
release(task)
真实系统还必须回答:唤醒是否丢失、取消是否传播、资源由谁释放、回调是否可重入、队列是否有界。
4. 数据路径¶
画出数据的每次复制、编码、校验、缓存和队列边界。对一次发送,至少区分用户缓冲区、内核 Socket 缓冲区、协议分段、DMA 描述符、网卡队列和链路帧。send() 返回只说明数据进入了某个边界,并不等于对端应用已经处理。
5. 顺序与并发¶
“先发生”可能指程序顺序、编译器顺序、处理器可见顺序、缓存一致性顺序、内核事件顺序、网络到达顺序或分布式逻辑顺序。每次看到“之前”“之后”“同时”,都要写明是哪一种偏序关系。
6. 成本模型¶
先用数量级定位,再追求精确。一次操作的粗略响应时间可拆为:
平均值不能解释尾部。排队系统在利用率接近上限时会出现非线性放大,因此 80% 与 95% 利用率并不是“只差 15 个百分点”。
7. 失败与恢复¶
失败不是最后附加的一节,而是机制的一部分。至少区分:
- 进程、主机、网络分区、磁盘和时钟故障;
- 超时、取消、重试、重复执行和部分成功;
- 崩溃前已进入易失缓存、已落盘、已复制、已对外确认的不同状态;
- 资源泄漏、优先级反转、活锁、饥饿和级联故障。
规格、实现与测量必须分开¶
同一句话常混入三种证据:
- 规格:允许或禁止哪些行为;
- 实现:某个版本如何实现;
- 测量:某台机器在某个负载下发生了什么。
例如“Python 线程不能并行”既不精确,也不稳定。需要写清解释器、版本、是否执行 Python 字节码、扩展是否释放解释器锁、工作负载是否 I/O 密集,以及“并行”指墙钟时间还是核心同时执行。
从最小实验到因果证据¶
一个有用实验应做到:
- 一次只改变一个机制相关变量;
- 预热与稳态分开;
- 记录版本、CPU 拓扑、频率策略、内核、编译参数和输入;
- 同时保留结果与反例;
- 用观测工具验证实验确实走了预期路径。
不要在没有确认路径的情况下解释性能计数器。一个缓存未命中指标可能受采样、推测执行、复用计数器和事件定义影响;一个网络延迟也可能把客户端排队、DNS、TLS 和服务端处理揉在一起。
写出完整解释¶
最终答案至少应形成这条链:
现象 → 契约 → 状态与控制流 → 数据路径 → 成本模型 → 观测证据 → 反例与边界 → 修复后的新风险
当链条中出现“显然”“一般来说”“系统会自动”时,应继续追问:哪个系统、哪个版本、由哪个状态迁移实现、如何观测。