本卷要解决的问题
卷二不教 Go 语法,也不教怎么装 Prometheus。它只回答一个问题:当系统已经有了边界,下一步怎样让它在流量、故障和变更下仍然“快得住、扛得住、退得回”。
具体拆成四件事:
- 把“快”从感觉变成读数:吞吐、延迟、尾部延迟和性能预算;
- 把“容量”从拍脑袋变成算式:业务指标 → QPS → 并发 → 实例数;
- 把过载和故障限制在局部:缓存、异步、池化、限流、熔断、重试、超时;
- 让变更可控地发生:冗余、切换、健康检查、发布、回滚、观测和复盘。
卷一解决“边界画在哪”,卷二解决“边界内的代价怎么付”,卷三处理分布式与一致性,卷四处理演进与治理。卷二会反复提到卷一的结构,但不重复定义限界上下文、端口适配器和数据所有权。
全书结构:13 章分四段
| 段落 | 章节 | 回答的问题 | 本段产出 |
|---|---|---|---|
| 一、度量与容量 | 第 1–2 章 | 慢在哪里?能扛多少? | 延迟直方图、性能预算表、容量算式、压测报告 |
| 二、单机与链路治理 | 第 3–7 章 | 怎样把过载挡在局部? | 多级缓存、异步链路、连接池、舱壁、限流、熔断、重试与超时 |
| 三、拓扑与变更 | 第 8–10 章 | 坏了怎么切?变更怎么退? | 故障域图、主备切换流程、健康检查、发布与回滚方案 |
| 四、数据与收口 | 第 11–13 章 | 数据库这一层怎么提速?整套改造怎么验收? | 索引与分页优化、读写分离预算、容量规划、故障场景清单 |
13 章不是 13 个性能技巧的堆叠。每一章都往 arch-lab 里加一层可运行或可检查的产物,第 12 章把这些产物合成一次完整的改造,第 13 章给出验收口径和进入卷三的交接。
章节依赖图
第 1 章 度量(吞吐 / 延迟 / 预算)
│
└──► 第 2 章 容量(QPS / 压测 / 余量)
│
├──► 第 3 章 缓存(本地 / 分布式 / 一致性)
│ │
│ └──► 第 11 章 数据层(索引 / 分页 / 读写分离)
│
├──► 第 4 章 异步(收益 / 语义 / 积压)
│
├──► 第 5 章 资源池(连接 / 线程 / 舱壁)
│
└──► 第 6–7 章 过载治理(限流 / 熔断 / 重试 / 超时 / 幂等)
│
├──► 第 8 章 拓扑(冗余 / 切换 / 摘流)
│
├──► 第 9 章 发布(滚动 / 回滚 / 开关)
│
└──► 第 10 章 观测(契约 / 告警 / 复盘)
│
└──► 第 12 章 贯穿项目(改造前后)
│
└──► 第 13 章 综合(容量 / 故障 / 交接)
这张图表达“先有什么再有什么”的阅读顺序,不是代码依赖。第 5、6、7 章可以互换;第 12 章必须放在第 3、4、6 章之后,因为它要把缓存、异步和限流装进同一条链路;第 13 章需要第 2、8、9 章的结论。
十三章速览
第 1 章 性能问题的定义与度量
先把“快”拆成吞吐、平均延迟、尾部延迟三个不同的问题,再讲怎么从 CPU、内存、I/O、锁四类瓶颈里定位真正的时间去处,最后把端到端目标拆成每个调用的绝对截止时间。
产出:延迟直方图与分位数、瓶颈判定表、下单链路 400ms 预算表。
第 2 章 容量估算与压测
从业务指标(日订单、峰值系数、读写比)推 QPS 和并发数,再讨论压测模型、数据构造和流量回放,最后讲怎么读结果、留多少容量余量。
产出:容量算式、开环/闭环压测器、一份带置信边界的压测结论。
第 3 章 缓存体系
比较本地缓存、分布式缓存和多级缓存的命中率与一致性代价,处理失效、更新和并发回源,最后给出穿透、击穿、雪崩和热 key 的治理组合。
产出:多级缓存实现、singleflight、逻辑过期、热 key 探测。
第 4 章 异步化与消息
算清同步改异步的收益与代价,区分 at-most-once、at-least-once 和 effectively-once 的责任边界,再处理积压、顺序、死信和定时对账。
产出:异步改造判据、消费幂等表、积压与死信处置流程。
第 5 章 资源池与隔离
从连接池、线程池和协程池的容量与排队讲起,再用信号量舱壁和资源配额隔离不同调用方,最后分析慢调用怎样沿调用链扩散。
产出:池参数计算、舱壁实现、慢调用传播的失败注入结果。
第 6 章 限流与熔断
比较令牌桶、漏桶、滑动窗口和分布式租约,再实现三态熔断状态机与半开恢复,最后讨论降级、兜底和用户可感知的体验。
产出:限流器、租约式分布式限流、熔断状态机、降级决策表。
第 7 章 超时、重试与幂等
把超时预算沿调用链传下去,算清重试放大与退避抖动,最后用幂等键和去重窗口保证重复请求不产生重复副作用。
产出:截止时间传播、带抖动的重试策略、幂等键存储与故障注入测试。
第 8 章 高可用拓扑
从冗余、故障域和单点消除讲到单区域主备切换,再处理健康检查、流量摘除和在途请求。
产出:故障域图、主备切换流程、健康检查状态机、摘流脚本。
第 9 章 发布与可用性
比较滚动、蓝绿和金丝雀的代价,处理回滚、兼容窗口和数据迁移,最后讨论开关与灰度对数据兼容的约束。
产出:发布策略选择表、兼容窗口检查、开关与数据迁移的先后顺序。
第 10 章 观测与告警
定义订单链路的最小观测契约,把告警从“指标超阈值”改成“症状到行动”的闭环,最后用复盘把一次故障变成可验证的改进项。
产出:trace/日志/指标契约、告警规则、复盘模板与改进台账。
第 11 章 数据层性能
从索引、查询和慢 SQL 讲到分页、批处理与连接管理,再讨论读写分离的延迟预算和一致性兜底。
产出:索引设计、深分页改写、连接池参数、读写分离延迟预算。
第 12 章 贯穿项目:交易系统的高并发改造
把前十一章的机制装进同一个订单系统:先测基线、找瓶颈,再做缓存、异步和限流改造,最后用同一口径比较改造前后,并注入失败验证降级路径。
产出:基线报告、改造清单、改造前后对照数据、失败注入结果。
第 13 章 卷二综合
完成一次从业务目标到实例数的容量规划,列出订单系统必须测的三个故障场景,最后把未解决的问题交接给卷三。
产出:容量规划表、故障场景清单、卷三问题清单。
三条阅读路径
路径 A:顺序通读
适合第一次系统补性能与高可用知识、准备架构评审或面试的读者。
| 阶段 | 章节 | 建议投入 | 读完应该能做什么 |
|---|---|---|---|
| 建立度量 | 第 1–2 章 | 5–6 小时 | 设计延迟指标、做一次可信压测、算出容量 |
| 治理单机 | 第 3–5 章 | 8–10 小时 | 设计缓存、异步链路和资源池 |
| 治理过载 | 第 6–7 章 | 6–8 小时 | 配置限流、熔断、重试、超时和幂等 |
| 拓扑与变更 | 第 8–10 章 | 8–10 小时 | 设计切换、发布、回滚和告警 |
| 数据与验收 | 第 11–13 章 | 8–10 小时 | 优化数据层、完成改造对照和故障清单 |
总计约 35–44 小时。代码量比卷一多,建议至少完整跑通第 3、6、7 章的 L1 测试,否则很容易把“看起来对”当成“跑得通”。
路径 B:改造驱动
适合手上正有一个慢系统或一次高并发改造任务的读者。
按这个顺序读:
- 1.2 瓶颈定位
- 2.1 从业务指标到 QPS
- 12.1 压测基线与瓶颈分析
- 3.1 多级缓存 与 4.1 同步改异步
- 6.1 限流 与 6.2 熔断
- 12.3 改造前后的数字
- 用附录 C 故障演练检查表 做一次演练
这条路径不要求读完前两章的所有细节,但第 1 步和第 3 步不能跳:没有基线,后面的“优化了多少”都无法回答。
路径 C:问题驱动
适合线上已经有具体症状的读者。先查表,再跳章。
| 症状 | 先读 | 再读 |
|---|---|---|
| 平均延迟正常,用户仍抱怨慢 | 1.1 尾部延迟 | 1.3 性能预算 |
| 不知道能扛多少流量 | 2.1 QPS 与并发 | 2.3 容量余量 |
| 缓存越加越不一致 | 3.2 缓存一致性 | 3.3 穿透与热 key |
| 消息积压、重复消费 | 4.2 投递语义 | 4.3 积压与死信 |
| 一个慢接口拖垮整个服务 | 5.3 慢调用扩散 | 5.2 舱壁隔离 |
| 峰值被打穿、限流不准 | 6.1 限流算法 | 6.3 降级与兜底 |
| 重试把下游打挂 | 7.2 重试放大 | 7.1 超时预算 |
| 重复下单、重复扣款 | 7.3 幂等键 | 4.2 消费幂等 |
| 单点故障、切换不敢做 | 8.1 故障域 | 8.2 主备切换 |
| 发布就出事、回滚不干净 | 9.2 回滚与兼容窗口 | 9.3 开关与数据兼容 |
| 告警太多、真故障没人管 | 10.2 告警闭环 | 10.3 复盘闭环 |
| 慢查询、深分页、连接打满 | 11.1 索引与慢 SQL | 11.2 分页与连接 |
arch-lab 在卷二扩展了什么
卷二沿用卷一的订单系统:Order、InventoryReservation、Payment 三个聚合,CREATED → INVENTORY_RESERVED → PAID → FULFILLED 状态机,以及同一组错误码。卷二不重画边界,只在边界内加四组能力。
| 组 | 新增组件 | 放在哪里 |
|---|---|---|
| 度量 | 直方图、分位数、三本账 | internal/platform/metrics |
| 容量 | 开环/闭环压测器、容量计算器 | cmd/loadgen、internal/platform/capacity |
| 治理 | 缓存、限流、熔断、重试、舱壁、幂等 | internal/platform/* |
| 运行时 | 健康检查、摘流、发布分流、开关 | internal/platform/health、internal/platform/runtime |
这些组件不改变领域层。订单聚合仍然不知道缓存和熔断的存在;平台能力通过端口和中间件接入,业务代码只感知“调用可能失败、可能超时、可能被拒绝”。
代码分四级,与卷一相同:L1 纯逻辑、L2 进程内端到端、L3 真实依赖、L4 多节点与故障注入。卷二的大部分章节是 L1;第 8、10、12、13 章至少各有一个 L2 场景;L3 只承诺实际启动过 PostgreSQL 或 Redis 的部分;L4 只在第 13 章讨论场景,不承诺完整实验。
数字口径:实测、目标与示例
性能书最容易骗人的地方是数字。卷二统一用三种口径:
| 类型 | 写法 | 用途 |
|---|---|---|
| 实测值 | 附命令、环境、样本量,例如“本机 darwin/arm64,1000 次请求,P99=20ms” | 证明某次运行的结果 |
| 目标值 | 写成 SLO 或预算,例如“下单 P99 目标 400ms” | 设计约束,不是测量结果 |
| 示例值 | 用于公式或配置形状,例如“单实例按 50 请求/秒估算” | 说明计算过程 |
看到“实测”时,先看有没有命令和原始输出;没有的,按目标值或示例值理解。反过来,实测值也只代表当次机器和当次流量,不能直接外推成生产容量。第 2 章会专门讲怎么设计压测、怎么给结论加边界。
Java 对照只用于解释结构差异,例如虚拟线程与 goroutine 的调度模型、线程池队列与 Go channel 的背压方式。所有 Java 结论都用 JDK 21 实际运行,不用本机默认 JDK,也不引入 Spring 或 Maven。
每篇文章的结构
卷二 39 篇正文的结构基本一致。知道骨架,就能决定哪些段落细读、哪些段落跳过。
| 部分 | 作用 | 建议 |
|---|---|---|
| 开篇场景 | 用一个具体问题或失败案例切入 | 先判断是否与你的系统相似 |
| 机制与取舍 | 解释为什么这样设计、代价是什么 | 细读,别只看结论 |
| 代码与测试 | 可抽取、可运行的最小实现 | 至少跑一次 L1 |
| arch-lab 落点 | 该机制放在订单系统的哪一层 | 对照卷一的结构 |
| 真实运行 | 命令、输出与解读 | 注意是开环还是闭环 |
| 常见错误与边界 | 五条左右的失效模式 | 上线前逐条自查 |
| 小结与导航 | 收束本节并给下一节链接 | 决定继续读还是回查 |
每篇都至少有一个可运行片段。代码块前的三行注释不是装饰:file 决定抽取路径,tier 决定验证级别,verify 给出应执行的命令。把这三行删掉再复制,抽取器会拒绝。
结尾的“常见错误与边界”通常比正文更接近生产。正文展示的是正确路径,这一节补的是限流器遇到时钟回拨、熔断器进入半开风暴、重试穿透预算之类的错误路径。跳过它,代码能跑,事故照样会来。
常用验证命令
书里的每段 Go 代码都可以抽取到临时模块单独编译。最小验证集合如下:
| 目的 | 命令 | 通过标准 |
|---|---|---|
| 格式 | gofmt -l . | 无输出 |
| 静态检查 | go vet ./... | 退出码 0 |
| 单元测试 | go test ./... | 全部 PASS |
| 并发测试 | go test -race ./... | 无 DATA RACE |
| 指定用例 | go test -run TestTokenBucket -v ./... | 输出与正文一致 |
| 压测器 | go run ./cmd/loadgen -rate 200 -duration 30s | 能正常结束并输出分位数 |
| L3 数据库 | docker compose up -d && go test -tags=integration ./... | 只记录实际跑出的数字 |
| Java 对照 | javac --release 21 ... && java -cp out app.Main | 先确认 java -version 是 21 |
抽取后的模块统一用 module archlab 和 go 1.26.0。如果 go vet 报路径错误,先检查代码块的 file 标记是否与正文一致,再检查是否把两个文件粘进了同一个代码块。
-race 不是可选项。第 3、5、6、7 章都有共享状态的并发组件,普通测试通过不代表没有数据竞争;-race 通过也不代表逻辑正确,但至少排除了最常见的错误来源。
版本与验证环境
| 组件 | 版本 | 用途 |
|---|---|---|
| Go | 1.26.0 | 全书主线代码 |
| Java | Temurin 21.0.13 | 虚拟线程与线程池对照 |
| PostgreSQL | 18.4 | L3 索引、分页、连接池验证 |
| Redis | 7.x(可选) | 分布式缓存与限流租约 |
| 操作系统 | macOS darwin/arm64 | 本机测量环境 |
几条纪律:
- Java 结论必须用 JDK 21 真跑,本机默认 JDK 8 不参与任何对照;
- 没有启动过的数据库,正文写“预期行为”,不写“实测”;
- 压测数字必须附并发模型、数据规模和运行次数;
- 单次运行只代表当次环境,不包装成稳定结论;
- 开环与闭环压测的结果不可直接比较,正文会注明用的是哪一种;
- 容量结论必须给出余量,不把“压测跑通”等同于“生产能扛”。
六个常见误区
第一,把平均延迟当性能。平均值会被大量快请求拉低,真正影响用户的是 P95、P99 和超时率。第 1 章先用直方图把这件事说清楚。
第二,先加缓存再找瓶颈。缓存会掩盖问题,也会引入一致性和雪崩风险。顺序应该是先测基线、再定位、最后才决定加不加缓存。
第三,压测只看 QPS 不看延迟分布。系统在 5000 QPS 下“能返回”不等于“能可用”,要看尾部延迟是否已经越过 SLO。
第四,限流值拍脑袋。限流阈值应该来自容量和依赖承受能力,不是“线上大概这么多”。第 6 章会给计算方式,也会讲分布式限流的租约误差。
第五,重试当万能药。没有预算、没有退避、没有幂等的重试,会把一次抖动放大成雪崩。第 7 章把这三件事放在一起讲。
第六,故障演练只演“服务挂了”。真实故障更多是变慢、部分失败、依赖超时和流量倾斜。第 13 章列的三个场景都偏向这类“不彻底”的故障。
读完本卷的标准
不是“看完 45 篇”,而是能独立产出下面五样东西:
- 一张下单链路的延迟预算表,每个阶段有截止时间和降级项;
- 一份容量算式,从业务指标推到实例数,并说明余量;
- 一组可运行的治理组件:缓存、限流、熔断、重试至少各一个;
- 一次改造前后的同口径对照,附命令和失败注入结果;
- 一份故障场景清单,每个场景有观测信号、处置步骤和退出条件。
如果这五样能写出来,卷三的复制、共识和分布式事务才有讨论的基础。否则读得再多,也只是记住了一批中间件名词。
从哪开始
第一次读,从1.1 吞吐、延迟与尾部延迟 开始。手上有具体问题,直接用上面的症状表跳转。要做改造,先读12.1 压测基线与瓶颈分析 ,再回到对应章节。
全书完整篇目见书架 ,或按顺序从 1.1 走到 13.3。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。