从“为什么读”到“怎么读”
序言回答的是这本书为什么存在。前言要回答更具体的问题:它由哪几卷组成,为什么按这个顺序;代码放在哪里,怎么运行;哪些数字可以信,哪些只是假设;章节之间怎么导航;以及读完之后,如何确认自己不是只记住了几个名词。
如果你已经决定按卷通读,这一篇可以当作使用说明。如果你只想跳到某一章解决问题,也建议先花十分钟看完工程约定和命名规则,否则你可能会在代码块、验证级别和链接方式上浪费不必要的时间。
本书不要求读者先读完整个系列再动手。每一章都尽量自成闭环:有产品场景、有判断、有失败场景、有可运行代码或可复核计算。但“自成闭环”不等于“互不依赖”。很多章节会引用前面章节的假设,尤其是第 1 章的产品事实、第 2 章的边界和第 12 章的容量模型。
四卷分工与顺序
全书按一个互联网产品的成长顺序分成四卷:
| 卷 | 主题 | 主要问题 | 典型读者 |
|---|---|---|---|
| 卷一 | 产品、流量与基础能力 | 产品事实如何变成服务端形状?账号、资料、入口怎么打底? | 第一次负责完整产品域的后端工程师 |
| 卷二 | 内容、社交与实时互动 | 内容怎么发、怎么分发、怎么互动、怎么通知? | 做内容社区、Feed、IM 的工程师 |
| 卷三 | 交易、增长与平台化 | 库存、订单、支付、券、实验怎么不出错? | 做电商、会员、增长业务的工程师 |
| 卷四 | 规模化与架构演进 | 什么时候拆、怎么拆、怎么回退、成本怎么控? | 技术负责人、架构师、平台团队 |
这个顺序有一个简单理由:后一卷的问题必须在前一卷的约束下才有意义。
没有账号和入口,内容社交只能停留在单机演示;没有真实的内容与交易流量,规模化讨论就只能停留在拓扑图上;没有交易和增长带来的强一致与合规要求,平台化很容易变成“为了拆而拆”。反过来,如果一开始就按卷四的规模设计卷一的产品,团队通常会在验证产品之前先被基础设施拖垮。
四卷之间不是严格的先后依赖。读者可以只读卷一,也可以从卷二的内容链路切入。但如果你要理解某个结论为什么成立,最好回到它依赖的前置章节。每章开头会尽量说明这一点。
每一卷交付什么
本书不希望读者读完只得到一堆观点。每一卷都有一组可以检查的产物。
| 卷 | 可检查产物 | 检查方式 |
|---|---|---|
| 卷一 | 形状分类器、容量模型、SLO 计算、账号与资料模块、BFF 聚合、端到端用例 | go test、假设数字代入、端到端脚本 |
| 卷二 | 内容状态机、Feed 参数、关系模型、互动计数、IM 序号与通知边界 | 表驱动测试、参数表复核、场景推演 |
| 卷三 | 库存扣减、订单状态机、支付回调幂等、券叠加、实验分流 | 状态迁移测试、对账清单、口径对照 |
| 卷四 | 拆分触发条件、单元化路由、演练模板、成本模型、架构健康度自评 | 评审清单、演练记录、预算表 |
“可检查”不等于“生产可用”。卷一的代码大多做到 L1,少数做到 L2;真实依赖和故障注入不会伪装成本地测试。这个分级在下面会详细说明。读者应该把产物当成论证工具,而不是可以直接复制到公司的脚手架。
工程约定:一个工程、三行头、四级验证
本书所有 Go 代码来自同一个实验工程 webapp-lab,模块名 webapp,使用 Go 1.26.0。工程不依赖外部框架,标准库优先,目的是让读者把注意力放在判断和边界上,而不是框架配置上。
每个代码块的开头有三行固定头部:
// file: internal/platform/product/shape.go
// tier: L1
// verify: go test ./internal/platform/product/
三行分别表示:
file:这段代码在webapp-lab中的相对路径。读者可以按路径找到完整文件,代码块内容与文件逐字节一致。tier:验证级别,说明这段代码需要什么环境才能证明它可运行。verify:最小验证命令。复制这条命令,应该能在工程根目录复现代码块所声称的结果。
代码块之外的解释可以自由书写,代码块之内不改一个字。这样做的原因很实际:书里能跑、复制下来不能跑,是技术书最常见的信任崩塌方式。逐字节一致至少能保证读者看到的不是“意译版代码”。
L1 到 L4 的定义
| 级别 | 判定 | 验收方式 | 本卷落点 |
|---|---|---|---|
| L1 | 纯逻辑或单包,不依赖外部进程 | gofmt -l 无输出、go vet、go test,并发包加 -race | 绝大多数章节 |
| L2 | 进程内端到端,httptest 或本机端口,内存适配器 | 一个脚本跑通产品用例并断言状态 | 第 13 章收口 |
| L3 | 真实依赖,例如 Docker Compose + PostgreSQL | 固定场景脚本与原始输出,只写实际跑过的数字 | 本卷不要求每章都有 |
| L4 | 多节点、故障注入、网络分区 | 真实环境演练与记录 | 本卷不做,相关结论明确标注 |
卷一的目标是:所有章节至少 L1;账号、资料与 BFF 有一条 L2 用例;如果环境允许,补一次 L3 端到端。L4 不在本卷范围内,任何涉及多节点容灾的结论都必须标注“需真实环境验证”。
这个分级不是为了给代码贴标签,而是为了管理读者的预期。L1 通过只说明局部逻辑正确,不说明它能承受真实流量;L2 通过说明一条产品用例在进程内跑通,不说明数据库、网络和第三方服务也可靠;L3 才涉及真实依赖,但仍然不是生产环境。把 L1 当成生产可用,是读这类书最容易犯的错误。
每个章节至少留下一个产物
卷一每一节都尽量留下一个可以运行或可以复核的产物:
- 一个纯函数,例如产品形状分类器、容量估算、错误预算计算;
- 一张可核对的表,例如读路径分级、灰度比例、回源保护策略;
- 一段可执行的检查,例如接口语义分类、版本兼容判断、缓存窗口计算。
不是每一节都需要代码。涉及组织、流程和产品判断的内容,可能只有清单和对照表。但只要出现代码,就必须能按三行头复现;只要出现数字,就必须说明口径和来源。
版本与事实纪律
本书对事实的处理有三条规则。
第一,只有本机真实运行过的命令和输出,才会写成“运行输出”。运行输出会尽量保留原始格式,包括包名、耗时和失败信息。耗时只代表一次本机运行,随机器负载变化,不作为性能承诺。
第二,没有跑过的数字,一律写成“案例假设值”或“预期结果”。DAU、QPS、延迟、命中率、成本和比例,除非明确标注为运行输出,都是用来演示推导的输入,不是任何真实系统的实测。
第三,不编造框架、版本号、论文和链接。涉及外部能力时,只写当前工程实际使用或当前文档能确认的内容;不确定的地方,宁可写“需要按你的环境验证”,也不写一个看起来确定的结论。
这三条规则会让书里的一些段落显得不够“干脆”。比如容量章节不会告诉你“一台机器能扛多少 QPS”,而会给你一个公式和一组假设,让你代入自己的请求体大小、延迟目标和利用率。这看起来更麻烦,但它避免了一个更糟的结果:读者把别人的机器数字当成自己的容量承诺。
书中也会出现“一次本机运行,耗时随机器变化”这样的说明。它不是免责声明,而是提醒读者:测试输出证明的是行为,不是性能。行为可以复现,性能只能在相同环境、相同数据和相同并发模型下比较。
命名与导航约定
卷一的文件与 slug 使用统一前缀:
inet-base-<章>-<节>-<英文短名>
例如 inet-base-01-2-scale-curves 表示卷一第 1 章第 2 节,主题是规模曲线。其他卷分别使用 inet-social-、inet-growth-、inet-scale- 前缀。这样在文件列表、搜索结果和站内链接里,读者能一眼看出章节属于哪一卷。
章节页的正文最后一个 ## 之前有一行章节导航,指出上一节和下一节。导航只出现在正式章节页;序、前言、目录和附录不写导航,避免把“线性阅读”的假设强加给工具页。
站内链接统一使用 Hugo 的 relref,参数是目标页面的 slug,而不是手写 URL。这样做有两个好处:slug 变化时链接会一起更新;构建时能发现指向不存在页面的链接。读者在 Markdown 源码里看到 /inet-base-01-1-product-shape/,就知道它指向的是哪一节。
标题命名也有约定。章节标题保留编号和文字,例如“1.1 产品形态如何决定服务端形状”;书名只出现在卷首和目录,不出现在每一节的导航行里。这样导航行更短,也避免同一书名在页面上重复十几次。
与站内已有专题的关系
本站已经有《后端架构设计》《游戏服务端架构》等书稿,也有 miniblog、design、algorithm-interview 等专题。本书不重复它们的内容,只处理它们没有回答的那条链路:互联网产品的产品形态,如何决定服务端的读写路径、数据模型与容量结构。
具体来说:
- 通用架构模式、质量属性、演进方法,站内已有书稿讲得更系统,本书遇到时只给判断和
relref。 - 中间件部署、集群运维、故障排查,属于运维与中间件专题,本书只说明“为什么需要这一层”和“放在哪里”。
- 算法与数据结构,属于算法专题,本书不推导排序模型、索引结构或一致性算法。
- 语言语法与标准库,属于语言书稿,本书默认读者能读懂 Go 代码,不逐行解释语法。
同题只链接,不重述。这样做的目的不是省篇幅,而是保持每本书的边界清晰。读者遇到需要展开的机制时,可以顺着链接去读更专门的资料;本书则继续回答产品阶段和取舍问题。
如果你发现某个概念在本书里只有结论没有推导,先看该段的 relref。如果没有链接,通常意味着它属于本书的边界之外,或者需要读者按自己的环境验证。
如何验证本卷代码
卷一的代码可以按包验证。最常用的三条命令是:
gofmt -l internal/account internal/profile internal/bff
go vet ./internal/account/ ./internal/profile/ ./internal/bff/
go test ./internal/account/ ./internal/profile/ ./internal/bff/
第一条检查格式,没有输出表示通过;第二条做静态检查;第三条运行账号、资料和 BFF 三个基础包的测试。本书校样时,这三条命令在本机均以退出码 0 结束。具体输出和完整工程结构见附录 B。
只想跑一个包时,把路径换成目标包即可,例如:
go test ./internal/platform/product/
这条命令只验证产品形状分类器,不依赖数据库、网络或其他包。它适合在读第 1 章时随手运行,也适合在修改分类规则后做快速回归。
如果某条命令失败,先确认三件事:Go 版本是否为 1.26.0;当前目录是否是 webapp-lab 根目录;失败包是否正在被其他改动影响。不要用“全仓测试通过”替代单包结论,也不要用单包通过推断全仓健康。
代码块怎么读
读代码块时,建议按下面的顺序:
- 先看
file,知道它在工程里的位置和所属包。 - 再看
tier,判断它需要什么环境才能验证。 - 最后看
verify,确认作者声称的最小验证方式。 - 读代码时先看导出的类型和函数签名,再看错误处理和边界条件。
- 读完回到正文,看作者为什么这样取舍,以及哪些情况没有覆盖。
不要跳过错误处理。书里的很多判断藏在错误分支里:为什么这个输入要拒绝、为什么这个状态不能迁移、为什么这个字段不能出现在导出结果里。只看成功路径,会把一个有边界的实现误读成万能方案。
也不要因为代码短就跳过测试。测试文件通常比实现文件更长,因为它要覆盖边界、非法输入和状态迁移。测试是论证的一部分,不是附录。
一个最小的阅读环境
读这本书不需要复杂的开发环境。最小配置是一台能运行 Go 的电脑、一个终端和一个文本编辑器。如果你只读正文,甚至不需要安装 Go;但如果你要验证代码,建议把环境准备好。
需要确认的只有几件事:
- Go 版本是 1.26.0。版本差异可能影响标准库行为和测试结果。
- 当前目录是
webapp-lab根目录,里面有go.mod,模块名是webapp。 - 能访问本机文件系统,不需要访问外网;工程不依赖外部模块下载。
- 如果要跑 L3,需要本机或远程的 PostgreSQL;卷一不强制要求。
一个够用的自检流程是:
go version
cat go.mod
go test ./internal/platform/product/
第一条确认版本,第二条确认模块,第三条确认最基本的 L1 包能通过。三条都正常,再开始读第 1 章。如果第三条失败,先不要怀疑书里的结论,优先检查目录和 Go 版本。
不需要 IDE,也不需要调试器。书里的代码块都很短,用 go test 的失败信息通常就能定位问题。遇到测试失败时,先看测试名和断言行,再回到实现里找对应的边界条件。这个过程本身就是阅读的一部分。
关于目录与路径
书里的路径都相对于 webapp-lab 根目录,例如 internal/platform/product/shape.go。如果你把工程放在其他位置,命令里的相对路径不变,只要先进入工程根目录即可。
Windows 读者需要注意路径分隔符。Go 工具链接受正斜杠,书里的命令可以直接使用;但如果你在 PowerShell 里手写路径,反斜杠和转义规则可能不同。遇到问题时,先用 go env GOMOD 确认当前模块,再执行测试。
工程不包含 vendor 目录,也不依赖私有仓库。这样做是为了让读者在任何一台机器上都能复现,而不是先配置一套公司内部的代理和证书。
章节依赖与跳读建议
卷一 13 章不是严格的线性依赖,但有些章节确实需要前置知识。下面这张表给出最低限度的建议。
| 目标章节 | 建议先读 | 原因 |
|---|---|---|
| 第 2 章 需求到架构 | 1.1、1.2 | 用例边界需要产品形状和容量假设作为输入 |
| 第 3 章 账号与身份 | 1.1 | 会话模型取决于产品端形态和安全底线 |
| 第 4 章 资料与隐私 | 第 3 章 | 资料字段的可见性依赖身份与关系模型 |
| 第 5 章 多端与 BFF | 第 3、4 章 | BFF 聚合的是账号、资料和端上差异 |
| 第 6 章 产品化 API | 第 2 章 | 资源与命令的划分来自领域边界 |
| 第 7 章 流量入口 | 1.3、第 6 章 | 网关职责取决于可用性目标和 API 语义 |
| 第 8 章 读写与缓存 | 1.1、1.2 | 读多写少和一致性窗口来自产品事实 |
| 第 9 章 异步与消息 | 第 8 章 | 异步边界通常出现在读路径和写路径拆分之后 |
| 第 10 章 发布与灰度 | 第 6、9 章 | 发布单元依赖 API 兼容和异步补偿 |
| 第 11 章 可观测性 | 1.3 | SLI 来自可用性底线和用户体验目标 |
| 第 12 章 容量与成本 | 1.2 | 容量模型是规模曲线的延续 |
| 第 13 章 收口 | 第 2、3、5 章 | 端到端用例要装配边界、账号和 BFF |
这张表只表达“先读什么更顺”,不是硬依赖。第 6 章可以在第 3 章之前读;第 11 章可以独立读;第 13 章最好放在第 2、3、5 章之后,因为它要把这些产物装配起来。
如果你只有一小时,建议按 1.1 → 1.2 → 1.3 的顺序读,先拿到分类顺序、容量假设和可用性底线。如果你正在做架构评审,建议先读第 2 章和第 13 章,看一份决策记录和一条端到端用例如何收口,再回头补前置章节。
跳读时最容易犯的错误,是把某章的结论直接搬到另一个阶段。比如第 12 章的副本数估算依赖第 1 章的请求假设;如果只读第 12 章,很容易把示例数字当成通用答案。每章开头的“适用阶段”和“假设”不是客套话,跳读时至少要扫一眼。
每章建议做的三件事
读完一章,建议做三件小事,而不是立刻翻下一页。
第一,复述这一章解决的产品问题。不要用技术名词开头,先用一句产品语言说清楚:用户在什么场景下遇到了什么困难,服务端为什么不能按原来的方式继续。
第二,改一个假设,看结论是否变化。把 DAU 乘以十、把缓存命中率降到一半、把可用性目标提高一个九,观察哪条结论先翻转。这个动作能帮你找到本章最敏感的假设。
第三,跑一次最小验证。有代码就跑对应的 go test;没有代码就把表里的数字代入自己的产品,看看是否自洽。验证不一定要成功,失败往往更有信息量。
这三件事加起来通常不超过二十分钟,但比通读一遍更能留下可复用的判断。
如果时间只够做一件,优先做第二件:改一个假设,看结论是否变化。
四卷之间的手接手清单
四卷虽然可以独立阅读,但每一卷结束时都会留下一份“交给下一卷”的清单。它不是目录,而是下一卷开始前应该确认的前提。
| 交接 | 下一卷需要什么 | 如果缺失会怎样 |
|---|---|---|
| 卷一 → 卷二 | 账号、资料、BFF 的可运行基线;内容与关系的初步边界 | 卷二的内容链路没有身份和端上契约可依赖 |
| 卷二 → 卷三 | 内容、关系、通知的流量模型;互动与消息的失败语义 | 卷三的交易场景缺少真实流量和通知链路 |
| 卷三 → 卷四 | 订单、支付、券的峰值与成本数据;对账和补偿机制 | 卷四的拆分与容量讨论缺少真实负载依据 |
这份清单的作用,是提醒读者不要跳过前置条件直接读下一卷。如果你只关心卷四的拆分,至少先知道卷一的形状分类、卷二的流量模型和卷三的交易峰值;否则拆分方案很容易变成“为了拆而拆”。
当然,清单不是门槛。你可以在没有完整基线的情况下读任意一卷,但要清楚自己缺什么。缺账号模型时,卷二的关系链讨论只能停留在抽象层;缺交易峰值时,卷四的容量估算只能使用别人的假设。
读完之后怎么判断自己学会了
读完一卷,能复述目录不算学会。更有用的检验,是能不能对一个新产品做出下面五个判断。
第一,这个产品最可能以什么方式坏?回答要具体到故障模型,而不是“高并发”“数据不一致”这种笼统的词。
第二,为了接住这种坏法,最少需要哪些能力?回答要能区分“现在必须有”和“以后可以加”,并说明理由。
第三,这些能力的成本是什么?包括机器、研发、运维、故障半径和团队学习成本。
第四,如果假设错一倍,哪个结论会翻转?回答要指向具体章节和具体公式,而不是“都不确定”。
第五,如果明天要上线,先做哪三件事?回答要能落到可执行的检查项,而不是“加强监控”这种空话。
五个问题不需要全对。能发现自己卡在哪一问,就说明这一卷读出了边界。下一步不是重读整本,而是回到对应章节补那一个缺口。
版本变化时怎么办
书里的验证结果绑定在写作时的版本上。Go 1.26.0、模块 webapp、当前文件清单,都是那个时刻的事实。读者读到较晚的版本时,可能会遇到两种情况。
第一种是工具链变化。新版本 Go 可能调整标准库行为、测试输出格式或模块规则,导致命令仍然能跑,但输出与书里不同。这时先看失败信息,再判断是行为变化还是代码问题;不要为了复现书里的输出而回退版本。
第二种是工程增补。作者可能在同一模块里继续添加包,文件清单和测试数量会变化。这不影响已有代码块的可复现性,只要 file、tier、verify 三行仍然对应;如果对应不上,说明代码块需要更新,应该以工程当前文件为准。
无论哪种变化,都建议先跑三条最小命令:go version、cat go.mod、go test 一个已知的 L1 包。三条正常,再继续读;三条不正常,先修环境,不要急着怀疑结论。
版本变化不是坏事。它提醒读者,书里的“实测”只对那个环境成立,真正可迁移的是判断方法和验证习惯。
还有一种变化来自读者自己的环境:操作系统、芯片架构、容器限制、文件系统大小写敏感度,都可能让同一条命令表现不同。遇到这种情况,先确认差异是否影响结论。如果只是输出顺序或耗时不同,可以忽略;如果测试结果不同,再逐项排查。
不要为了复现书里的每一个字符而过度调试环境。书里的命令是手段,不是目的;能验证判断成立,就已经达到目的。
如果确实无法复现,记录下版本、命令和失败信息,再继续读。带着一个未解的问题阅读,往往比停下来和环境影响较劲更有收获。
问题可以在读完一整章后回看,也可以在附录 B 的工程快照里找线索。
阅读不是一次性验收,允许带着问题继续。
小结
前言给出了这本书的使用说明:四卷按产品成长顺序组织,卷一负责产品约束与基础能力;代码来自 webapp-lab,使用 Go 1.26.0 和模块 webapp;每个代码块有三行头,按 L1 到 L4 分级;数字只在真实运行时写成实测,其余都是假设;章节页有导航,首尾页没有;站内链接用 relref,同题不重述。
一个最小阅读环境只需要 Go 1.26.0、终端和工程根目录。先跑 go version、cat go.mod 和一个 L1 包的测试,确认环境正常,再开始读正文。遇到失败时,先检查目录、版本和包路径,不要急着修改代码。
如果你只记住一件事,请记住:这本书的结论都带条件。读每一章时,先找它的产品事实和假设,再看它的判断,最后看它的失败场景。跳过前两步,后面的内容就会变成另一本“架构模式大全”。
常见错误与边界
- 把 L1 测试当成生产验证。它只证明单包逻辑在给定输入下正确,不证明真实流量下可靠。
- 把代码块当成完整项目。代码块与工程文件逐字节一致,但工程本身是教学实验,不是生产脚手架。
- 忽略三行头。
file、tier、verify是复现的最小信息,跳过它们很容易在错误目录或错误版本上运行。 - 把案例假设值当成容量承诺。DAU、QPS、延迟和成本都要按自己的产品和环境重新代入。
- 只看章节正文,不看导航和附录。导航帮助定位,附录提供术语、工程结构和容量速查。
- 把
relref当成普通链接。修改 slug 或移动文件时,链接会随构建报错,这是有意的保护。 - 认为四卷必须按顺序读完。可以跳读,但要回到前置章节确认假设,尤其是第 1、2、12 章。
- 用“全仓通过”掩盖单包失败。本书的验证以包为单位,失败时先定位具体包,不要用平均值安慰自己。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。