这一篇说明这本卷怎么读、代码怎么跑、金额和数字怎么理解。赶时间的读者可以只读「每章的结构」和「实验工程」两节。
这本书假设你已经知道什么
本书假设读者具备以下基础:
- 能读懂 Go 代码,知道结构体、接口、错误处理、
context的基本用法。 - 写过 HTTP 服务,理解路由、中间件、状态码。
- 用过关系型数据库,知道事务、索引、唯一约束。
- 对缓存、消息队列有概念级认识,不要求生产运维经验。
- 知道什么是幂等、什么是最终一致,不要求能手写分布式事务。
不要求读者做过支付或电商。本书会从商品与库存开始,一步步搭出交易链路。但也不打算从「什么是数据库事务」讲起。
如果你完全没有交易系统经验,建议先读第 1、2、3 章,再读第 13 章;中间的营销与增长章节可以按需跳读。
13 章的组织方式
本卷 13 章按「交易基础 → 营销与权益 → 增长与实验 → 平台化 → 收口」排列:
- 商品与库存:商品、SKU、价格、可用与预留库存、对账与回补。
- 订单:下单校验、金额计算、状态机、超时关闭、查询可见性。
- 支付与对账:渠道抽象、回调幂等、掉单补偿、退款与冲正。
- 优惠券与满减:模板与实例、叠加最优解、预算与防刷。
- 会员与权益:等级与成长值、权益发放回收、订阅续费。
- 活动与秒杀:活动库存、预热、限流、风控与兜底。
- 邀请与裂变:邀请绑定、归因、奖励幂等、增长度量。
- 实验与配置:稳定分流、显著性与误判、开关治理。
- 用户触达:人群圈选、召回归因、退订与频控。
- 开放平台:应用权限、配额、回调签名、重放、第三方隔离。
- 国际化:货币最小单位、时区、数据驻留、区域路由。
- 运营后台:后台权限、审计、操作状态机、报表口径。
- 收口:下单到对账的端到端链路、券与活动验证、质量指标。
每一章的三节递进关系是:第一节定义模型与状态,第二节给出主实现与取舍,第三节处理失败、并发与边界。不建议只读第三节,因为失败场景依赖前两节的定义。
每章的结构
每章遵循相似结构:
- 问题开场:一个具体的用户投诉、财务差异或线上事故。
- 模型与判定:把现象拆成状态、金额、流水、约束。
- 主实现:可运行的 Go 代码,配套测试与运行输出。
- 取舍与替代方案:至少两个备选方案,说明适用条件。
- 失败场景:一个具体故障,说明如何发生、用户看到什么、怎么定位。
- 小结与边界:这一章不解决什么,留给哪一章。
「失败场景」和「取舍」是每节都有的。如果某一节只讲「怎么做」而不讲「什么时候不该这么做」,那多半是写漏了。
实验工程
代码在 webapp-lab 实验工程里,module 名为 webapp,Go 版本 1.26.0。卷三的包按章划分:
webapp-lab/
go.mod
internal/
catalog/ # 第 1 章:商品与库存
order/ # 第 2 章:订单
payment/ # 第 3 章:支付与对账
coupon/ # 第 4 章:优惠券与满减
member/ # 第 5 章:会员与权益
activity/ # 第 6 章:活动与秒杀
growth/ # 第 7 章:邀请与裂变
experiment/ # 第 8 章:实验与配置
reach/ # 第 9 章:用户触达
openplatform/ # 第 10 章:开放平台
i18n/ # 第 11 章:国际化
admin/ # 第 12 章:运营后台
growthbase/ # 第 13 章:收口
每个包只依赖标准库,不引入第三方框架。原因是交易逻辑不该被 ORM 或支付 SDK 遮住:库存扣减的原子性、回调的幂等、对账的差异检测,都应该在代码里看清楚。
运行某一章:
go test ./internal/catalog/ -count=1
go test ./internal/payment/ ./internal/coupon/ -count=1
运行全卷:
go test ./... -count=1
代码块开头的三行标记:
// file: internal/payment/callback.go
// tier: L1
// verify: go test ./internal/payment/
file 是路径,tier 是级别,verify 是验证命令。正文代码与工程文件逐字节一致。
代码分级
| 级别 | 含义 | 依赖 | 本卷情况 |
|---|---|---|---|
| L1 | 单包纯逻辑 | 仅标准库 | 第 1–12 章全部 |
| L2 | 进程内端到端 | httptest、内存适配器 | 第 13 章 |
| L3 | 真依赖 | 数据库、缓存、支付沙箱 | 本卷不做 |
| L4 | 多节点、故障注入 | 容器、网络工具 | 本卷不做 |
L3 不做,不是因为这些不重要,而是因为支付沙箱、数据库隔离级别、分布式锁的行为高度依赖真实环境。书里给了 L1/L2 的判定与测试,L3 的接入留到读者自己的项目。
读者如果要把代码用于生产,至少需要补三件事:真实的存储与事务、真实的渠道 SDK 与验签、真实的监控与告警。书里的代码是模型的实现,不是生产系统的复制品。
金额与时区约定
交易系统的很多 bug 来自单位。本卷统一约定:
- 金额一律用
int64存最小货币单位(人民币为分,日元为元)。 - 字段名带
Cents或Minor,例如AmountCents、AmountMinor,避免歧义。 - 不用浮点表示金额;百分比折扣先算整数再取整,取整规则在代码与文档中写明。
- 时间统一用 Unix 秒或毫秒的
int64,字段名带At,例如CreatedAt、ExpiresAt。 - 时区只影响展示,不影响存储;跨时区的「今天」必须显式带上时区。
- 币种用三位大写代码,不用符号;符号只在最终展示时拼。
这些约定不是行业标准,但它们能避免大部分「差一分钱」「差一天」的问题。读者在自己的项目里可以换命名,但建议保留「单位写进字段名」这一条。
数字与版本口径
本书对数字的使用有三条规则:
- 实测就是实测。 标注「实测」的输出,都在本机真实执行过,命令和结果一起给出。
- 示例就是示例。 订单量、GMV、库存、券预算都是案例假设,不代表真实业务。
- 预期就是预期。 没有跑过的结果,写「预期」「应当」,不写「实测」。
版本口径:
- Go 1.26.0,module
webapp。 - 金额单位:分;存储单位:GB(1 TB = 1024 GB)。
- 所有阈值都是起点,换产品、换规模、换渠道都要重新标定。
读者如果发现某个数字和自己的环境差很多,先看它是实测还是示例。示例只用来表达比例关系。
三种读法
按交易主循环读。 第 1 → 2 → 3 → 4 → 13 章。回答「一件商品从展示到收款、退款、对账,中间发生了什么」。适合第一次接触交易系统的读者。
按营销活动读。 第 4 → 5 → 6 → 9 章。回答「一次活动怎么设计库存、预算、权益与触达」。适合正在做运营活动的读者。
按平台化读。 第 10 → 11 → 12 章。回答「对外开放、跨区域、后台运营各自的边界」。适合系统已经跑起来、要对外扩展的团队。
每章独立,包也独立,但建议至少先读第 1、2 章,因为后面的章节会反复引用库存与订单状态。
怎么判断自己读懂了
每章末尾有「小结与边界」。读完一章,试着回答:
- 这个状态/金额/库存,谁拥有写权限?变动有没有流水?
- 这个操作重复执行两次,会不会多扣、多发、多退?
- 流量涨十倍,先坏的是哪一段?这一章有没有给出判据?
三个问题都能用自己的话回答,并且能指出代码里对应的函数,才算读懂。只记住「用了 Redis」「加了锁」不算。
与站内其他内容的关系
站内已有交易、支付、库存、优惠券、增长相关的文章。本书不复述它们的定义与清单,只在需要时用一段话交代结论并给出链接。
本书新增的是三样东西:
- 把能力放回「回声」的具体阶段,说明为什么现在做、不现在做会怎样。
- 每一节都有可运行的 Go 代码和真实输出。
- 每一节都有一个失败场景,说明做错了用户或财务会看到什么。
如果你已经读过站内的支付或库存文章,可以跳过概念部分,直接看「判定」「取舍」「失败场景」。
已知的取舍
写这本卷时,我刻意做了一些取舍:
- 不写渠道接入细节。 支付宝、微信、Apple Pay 的签名与证书差异很大,书里只讲统一抽象。
- 不写分布式事务框架。 只讲本地事务、消息、对账、补偿这几种手段的适用边界。
- 不写风控模型。 防刷只讲到规则与维度。
- 不写多机房。 多活、单元化、容灾留到卷四。
- 不写完整财务系统。 只写业务侧的对账接口与差异处理。
如果读者需要这些内容,站内有对应专题,本书会在相关章节给出入口。
环境准备
实验工程只依赖 Go 标准库。准备 Go 1.26.0:
go version
# go version go1.26.0 darwin/arm64
进入工程后先跑全量测试:
go test ./... -count=1
第一次编译稍慢,之后单包测试通常在几百毫秒内完成。正文输出均来自本机 -count=1 的结果。
命名与错误处理约定
为了让跨章阅读一致,本书遵循几条约定:
- 金额字段以
Cents或Minor结尾;时间字段以At结尾。 - 状态用字符串枚举,迁移函数返回
error,不 panic。 - 参数错误、业务拒绝、系统失败分开表达;参数错误不重试,业务拒绝不告警,系统失败才重试。
- 幂等函数通常接收一个「已处理集合」或幂等键,返回是否首次处理。
- 对账函数返回差异列表或差异金额,不直接修改数据。
这些约定是为了让读者能快速看出「这段代码在防什么」。读者可以用别的风格,但建议保留「幂等函数显式接收幂等键」这一条。
代码阅读顺序
正文里的代码块通常只展示一个文件的核心部分,完整文件在实验工程里。建议按下面的顺序读:
- 先看类型定义。字段决定了模型能表达什么,很多问题在读字段时就能发现。
- 再看入口函数。它决定这一节的主判定或主流程。
- 最后看测试。测试用例是行为契约,比注释更准确。
如果时间有限,可以只读类型定义和测试。类型定义告诉你「系统认为有哪些东西」,测试告诉你「这些在什么情况下应该怎样」。
代码块开头的 file 路径可以直接在工程里找到。正文与工程逐字节一致,如果不一致,以工程文件为准,并把它当作勘误。
表格怎么读
本书的表格分三类:
- 对比表:多个方案在若干维度上的差异。不要只看哪一列更好,要看自己的约束落在哪一列。
- 参数表:阈值、默认值、单位。这些是起点,不是结论。
- 状态表:状态、允许的操作、触发条件。读的时候检查「有没有状态没有出口」。
表格里的示例数字会在表前或表后注明。没有注明的阈值,通常是代码里的默认值,可以在实验工程里找到对应常量。
如果一张表读起来吃力,先只看第一列与最后一列:第一列是对象,最后一列是后果。中间的维度可以按需展开。交易系统的表格尤其如此,很多设计问题在「后果」这一列就能看出来。
反馈与勘误
书中的代码都在实验工程里,读者可以直接改、直接跑。如果发现某段代码跑不通,或者某个结论与你的经验不符,欢迎指出。特别是「实测」类内容,如果你的环境和书里差很多,那很可能是我漏写了某个前提。
对于示例数字,不必纠结具体值,关注比例关系即可。对于阈值建议,把它当作起点,用自己的数据重新标定。
致谢
感谢在写作过程中提供真实对账差异、支付事故与评审意见的同事。书里的失败场景大多来自真实事故的抽象,细节做了改写,但故障模式没有编造。
也感谢卷一、卷二的读者,他们对「判定链」「失败语义」的反馈直接影响了本卷的写法。
如果这本书只能留下一个习惯,我希望是:每次改金额或库存之前,先写一句「这笔变动对应哪条流水」,再动代码。
继续阅读
探索更多技术文章
浏览归档,发现更多关于系统设计、工具链和工程实践的内容。