这篇前言解决什么问题
序讲的是“为什么写”,前言讲的是“怎么用”。具体包括:
- 卷二在 arch-lab 上增加了哪些组件,边界在哪里;
- 代码按什么标准分级,读者需要跑到哪一级;
- 代码块里的标记是什么意思,怎么抽取、怎么验证;
- 压测器、故障注入和数据场景怎么复现;
- 版本、环境和数字的口径是什么。
如果只想尽快读正文,可以直接跳到 1.1,遇到代码标记再回来查。但如果你打算把书里的实验真正跑一遍,建议先花十分钟读完这篇。
卷二在卷一的哪里继续
arch-lab 仍然是那个 Go 订单系统:订单、库存、支付三个核心聚合,同一套状态机,同一组错误码。卷一确定了模块边界、端口与适配器、数据所有权和部署单元;卷二不重新设计这些边界,而是在边界内增加性能与故障治理能力。
卷二新增的组件分为四组:
| 组 | 组件 | 解决的问题 |
|---|---|---|
| 度量 | 延迟直方图、分位数、三本账 | 把“快慢”变成可比较的读数 |
| 容量 | 开环/闭环压测器、容量计算器 | 把业务指标变成 QPS、并发和实例数 |
| 治理 | 缓存、限流、熔断、舱壁、重试、超时 | 把过载和故障限制在局部 |
| 运行时 | 健康检查、摘流、发布分流、开关 | 让故障和变更可控地发生 |
这些组件放在 internal/platform/ 下,遵循卷一定下的依赖方向:平台包不依赖具体业务,业务包通过端口调用平台能力,适配器负责实现。卷二不会把 internal/order 改成“什么都依赖”的大杂烩;如果某一节的代码需要跨层引用,会在正文里说明原因,并在测试里检查依赖方向。
卷二不包含:
- 跨节点复制、共识、分区和多活写冲突(卷三);
- 服务网格、平台工程、完整混沌平台(卷四);
- Kubernetes、CI/CD、监控系统的部署操作;
- 具体中间件的参数调优手册。
这些不是“不重要”,而是与卷二的核心问题(单体内的代价与故障边界)关系较弱。卷二的很多机制在分布式环境下仍然适用,但它们的失效模式会更复杂,留到后续卷展开。
目录结构
抽取后的 arch-lab 结构如下:
archlab/
go.mod module archlab,go 1.26.0
cmd/
orderd/main.go 装配适配器、启动 HTTP 服务
loadgen/main.go 开环与闭环压测器
internal/
order/ 聚合、状态机、领域事件
catalog/ SKU 与价格
inventory/ 库存预留与释放
payment/ 支付状态与退款
ports/ 仓储、库存、支付、事件接口
adapters/
memory/ 内存实现,用于 L1/L2
postgres/ PostgreSQL 实现,用于 L3
platform/
metrics/ 直方图、分位数、三本账
cache/ TTL/LRU、singleflight、逻辑过期
ratelimit/ 令牌桶、滑动窗口、租约
circuitbreaker/ 三态状态机
retry/ 退避、抖动、重试预算
bulkhead/ 信号量舱壁
health/ 存活/就绪与摘流
obs/ trace、日志、结构化字段
config/ 配置加载与校验
httpserver/ 路由、中间件、错误映射
idempotency/ 幂等键与去重窗口
migrations/ PostgreSQL schema
testdata/ 固定场景数据
tools/
deplint/ 包依赖方向检查
riskreg/ 风险台账校验
deploy/
docker-compose.yml PostgreSQL 18.4
这个结构不是要求读者一次建全。每篇文章只抽取自己需要的文件,独立成模块,能单独编译和测试。完整结构用于说明组件之间的关系,不要求把所有代码放进一个仓库。
版本与环境
书中所有“实测”结论都基于下面这套环境。版本不同,结果可能不同,尤其是性能数字。
| 组件 | 版本 | 用途 |
|---|---|---|
| Go | 1.26.0 | 全书主线代码 |
| Java | Temurin 21.0.13 | 线程池与虚拟线程对照 |
| 操作系统 | macOS darwin/arm64 | 本机测量环境 |
| Hugo | 0.163.3+extended | 书稿构建 |
几条口径:
- 卷二的大部分实验不依赖外部进程,使用内存适配器和
httptest; - 需要 PostgreSQL 或 Redis 的场景会明确标注 L3,并写明是否实际启动;
- 没有实际启动的依赖,正文写“未实测”或“预期行为”,不写“实测”;
- 性能数字必须附命令、并发模型、数据规模和运行次数;
- 单次运行只代表当次环境,不包装成稳定结论;
- Java 结论必须用 JDK 21 真跑,不能用本机默认 JDK 代替。
本机默认 Java 可能不是 21,运行前先确认 java -version。如果版本不符,使用 Temurin 21 的本地解压包或容器,并在正文中写明实际使用的方式。
四级可运行标准
不是每段代码都值得启动数据库。书里把可运行性分成四级,读者可以按需选择验证深度。
L1:纯逻辑或单包
不依赖外部进程,只依赖标准库和项目内包。
验收:
gofmt -l .
go vet ./...
go test ./...
并发代码再加 go test -race ./...。卷二的大部分组件属于这一级:直方图、限流器、熔断状态机、重试策略、舱壁、健康状态机。
L2:进程内端到端
使用 httptest 或 127.0.0.1:0 启动真实 HTTP 服务,适配器用内存实现,不连数据库。
验收:
go test -run TestOrderFlow -v ./...
一个脚本要完成创建、查询、支付三条断言,并检查延迟、超时、降级和错误码。第 8 章、第 10 章和第 12 章至少各有一个 L2 场景。
L3:真实依赖
使用 Docker Compose 启动 PostgreSQL 或 Redis,验证索引、连接池、复制延迟、缓存失效等真实行为。
验收:
docker compose -f deploy/docker-compose.yml up -d
go test -tags=integration ./...
只承诺实际跑过的场景。没有跑过的数据库行为,写成“预期”或“需要验证”,不写成结论。卷二不要求每个读者都跑 L3;但书中出现 L3 结论时,必须附环境与版本。
L4:多节点与故障注入
多实例、网络分区、主备切换、消息重复与乱序。卷二只在第 8 章和第 13 章讨论故障域和切换条件,不承诺完整的 L4 实验;平台化混沌、真实网络分区留给卷四。
书中出现 L4 场景时,会明确标注“需真实环境”,不伪造结果。
代码标记与抽取
书里的 Go 代码块用三行注释标记来源:
// file: internal/platform/ratelimit/token_bucket.go
// tier: L1
// verify: go test ./internal/platform/ratelimit/
package ratelimit
三个字段的含义:
| 字段 | 含义 | 约束 |
|---|---|---|
file | 抽取后的相对路径 | 必须在 arch-lab 目录白名单内,不含 .. |
tier | 运行级别 | 只能是 L1/L2/L3/L4 |
verify | 验证命令 | 必须能在抽取后的模块里执行 |
抽取脚本会按文章分目录,把代码块写入独立模块,避免不同文章的包名和类型互相冲突。这样做的原因是:书稿不是一个大仓库,每篇文章的代码需要能单独编译,读者不必先理解全部上下文。
一个代码块只应包含一个完整文件。不要把同一个文件拆成“上半段/下半段”两个代码块,也不要用“(续)”之类的标记;抽取器会把它当成非法路径。如果确实需要多个文件,就给每个文件一个合法的 file 路径。
压测器与故障注入
卷二的自定义压测器放在 cmd/loadgen,支持两种模式:
| 模式 | 行为 | 用途 |
|---|---|---|
| 开环 | 按固定到达率发请求,不等待上一个响应 | 观察排队、超时和协调遗漏 |
| 闭环 | 固定 worker 数,收到响应再发下一个 | 观察吞吐上限和稳定状态 |
两种模式测出的“延迟”含义不同。开环在系统变慢时不会自动减少压力,能暴露排队;闭环会随着系统变慢自动降低到达率,容易掩盖过载。卷二正文会明确每次测量用的是哪种模式。
故障注入只使用可重复的手段:
- 可控延迟:在内存适配器里按固定分布注入延迟;
- 可控错误:按比例返回指定错误码;
- 可控不可用:关闭缓存或副本,观察回源与降级;
- 可控实例丢失:在进程内模拟实例摘除和在途请求。
不使用随机网络故障或不可复现的外部依赖。需要真实网络分区时,会明确标注为 L4,并留给卷四。
固定场景
为了避免每篇文章都造一套数据,arch-lab 固定三组场景:
| 场景 | 客户 | 商品 | 金额 | 用途 |
|---|---|---|---|---|
| S1 正常下单 | cus-001 | sku-book × 1 | 4999 分 | 创建、预留、支付、履约 |
| S2 库存不足 | cus-002 | sku-limited × 2 | 19998 分 | 预留失败、订单取消 |
| S3 重复回调 | cus-001 | sku-book × 1 | 4999 分 | 幂等、重复支付、对账 |
卷二新增两组性能场景:
| 场景 | 负载 | 用途 |
|---|---|---|
| P1 读多写少 | 90% 查询、10% 创建 | 缓存、分页、读写分离 |
| P2 突发流量 | 基线的 3 倍,持续 30 秒 | 限流、舱壁、熔断、降级 |
这些场景的数值是示例值,不是生产数据。正文出现具体数字时,优先复用这五组,避免读者在多个示例之间来回对照。
怎么验证一篇的代码
以 3.3 的 singleflight 为例,最小验证流程是:
- 从代码块中复制
// file:标记和代码,放到临时模块的对应路径; - 在模块根目录执行
go mod init archlab,写入go 1.26.0; - 执行
gofmt -l .、go vet ./...、go test ./...; - 如果失败,先检查 Go 版本和包路径,再检查代码块是否被截断。
书里的代码块都经过这一步验证,但抽取过程可能有复制误差。发现跑不通时,先对照文章中的完整代码块,而不是直接怀疑结论。
每篇文章的结构
卷二 39 篇正文的写法基本一致,读的时候可以先看骨架,再决定细读哪一段。
| 部分 | 作用 | 读法 |
|---|---|---|
| 开头的场景 | 用一个具体问题把本节的目标说清楚 | 判断是否与你的问题相关 |
| 机制与取舍 | 解释为什么这样设计、代价是什么 | 重点读,容易被跳过 |
| 代码与测试 | 可抽取、可运行的最小实现 | 至少跑 L1 |
| arch-lab 的落点 | 这个机制在订单系统里放在哪一层 | 对照卷一的结构 |
| 真实运行 | 命令、输出与解读 | 注意口径,不看孤立的数字 |
| 常见错误与边界 | 五条左右的失败模式 | 上线前逐条自查 |
| 小结与导航 | 一段收束,加上下节链接 | 决定继续读还是回查 |
不是每篇都能跑 L3,但每篇至少有一个 L1 或 L2 片段。如果某篇的代码块在你的环境里跑不通,先按上一节的四步排查,再对照正文里的完整实现;不要只改测试让它通过,那会把书里的结论和你的代码一起改坏。
每篇结尾的“常见错误与边界”不是凑数。它列的通常是同一类机制在真实系统里失效的方式,比如限流器的时钟回拨、熔断器的半开风暴、重试的预算穿透。读代码只能看到“正确路径”,这一节补的是“错误路径”。
两条阅读路线
同一本书,不同角色的读法不同。下面给两条路线,避免从第一页按顺序读到最后一页却抓不住重点。
路线一:按角色
| 角色 | 建议章节 | 目标 |
|---|---|---|
| 后端开发 | 1、3、5、7、11 | 能写出带预算、带幂等、带池化的代码 |
| 架构师 | 1、2、6、8、9、13 | 能定容量、定故障域、定发布策略 |
| SRE / 运维 | 2、8、9、10、13 | 能设计告警、演练和回滚 |
| 技术负责人 | 10、12、13 | 能判断改造是否值得、如何验收 |
路线二:按问题
- 系统“慢”:1.1 → 1.2 → 1.3 → 2.3 → 11.1;
- 系统“扛不住”:2.1 → 2.2 → 5.2 → 6.1 → 6.2;
- 系统“一挂全挂”:5.3 → 6.3 → 8.1 → 8.3 → 13.2;
- 数据“越改越乱”:3.2 → 4.2 → 7.3 → 9.2;
- 发布“不敢发”:9.1 → 9.3 → 10.2 → 10.3。
两条路线都不要求先读完卷一,但涉及模块边界、数据所有权和部署单元的地方,会直接引用卷一产出的 arch-lab 结构。没读卷一也能跟,只是少一层“为什么边界这样划”的背景。
关于数字
书里会出现三类数字,读的时候要分清:
| 类型 | 特征 | 例子 |
|---|---|---|
| 实测值 | 附命令、环境、样本量 | “本机 darwin/arm64,1000 次请求,P99=20ms” |
| 目标值 | 设计目标或 SLO | “下单 P99 目标 400ms” |
| 示例值 | 用于说明公式或配置形状 | “单实例按 50 请求/秒估算” |
目标值和示例值不是实测值,不能拿来证明系统性能。反过来,实测值也只代表当次环境,不代表生产容量。卷二会用专门的章节讲怎么设计压测、怎么读结果。
本书不覆盖的内容
- Go 语言语法、标准库和运行时细节;
- PostgreSQL 安装、调优、备份恢复操作;
- Redis、Kafka、Kubernetes 的部署与运维;
- 分布式共识、复制协议、分区算法的推导;
- 组织变革、绩效管理、招聘。
这些内容分别在语言书、数据库手册、卷三/卷四中处理。卷二只在必要时提到它们,并说明边界。
勘误与反馈
书中的代码、命令和数字都经过本机验证,但环境差异仍然可能导致问题。发现错误时,最有用的反馈是:
- 你执行的完整命令;
- 你的环境(Go/Java 版本、操作系统、架构);
- 完整输出,而不只是“报错了”;
- 你期望的结果和实际结果的差异。
有了这些信息,才能判断是代码问题、环境问题,还是书里的结论需要修正。
小结
卷二在卷一的 arch-lab 上增加四组能力:度量、容量、治理和运行时。代码按 L1–L4 分级,L1 是默认,L2 用于端到端,L3 用于真实依赖,L4 留给后续卷。压测器分开环和闭环,故障注入只使用可重复手段,固定场景覆盖正常、缺货、重复回调和两类性能负载。
记住三条使用约定:代码块带 file/tier/verify 标记;每篇文章的代码应能独立编译;数字要分清实测值、目标值和示例值。剩下的,从 1.1 开始。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。