模块 10第 3 学期 · 可靠性与扩展12 海报条目

分布式系统的正确性

登录后可在不同设备间同步进度。在此之前,进度只保存在当前浏览器中。

为什么重要

并发 bug 不会在演示时出现。它们出现的方式是:客户被扣了两次款,一张优惠券被兑换了三次,同一个座位卖给了两个人,或者月底余额对不上账。在金融科技领域,这些不只是 bug,而是监管事件。

学习目标

  1. 识别竞态条件,并用原子操作、约束和锁来修复。
  2. 充分理解复制、CAP 和最终一致性,能够围绕它们设计用户体验。
  3. 知道什么时候需要分布式锁和 leader 选举,以及它们的陷阱。
  4. 避免死锁。
  5. 在存在时钟偏差的情况下正确处理时间。
  6. 诊断内存泄漏和垃圾回收停顿。

课程内容

10.1 竞态条件(39, 43)

经典形态是先检查后执行(check-then-act):读一个值,做判断,再写入。在读和写之间,别人已经动了手。 修复方法,按优先顺序:

  1. 原子语句: UPDATE accounts SET balance = balance - 100 WHERE id = $1 AND balance >= 100,并检查受影响的行数。
  2. 数据库约束: 唯一索引让第二次插入直接失败。
  3. 乐观锁(版本号列),适合低竞争场景。
  4. 悲观锁(SELECT ... FOR UPDATE),适合高竞争场景。 在应用代码中:长期运行的 Node 进程里的共享可变状态会被所有请求共享;模块级变量并不是按用户隔离的。线程安全(43) 在 Go、Java、Python async 和 worker 中同样重要。

10.2 死锁(40)

两个事务各自持有对方需要的锁。预防办法:始终按一致的顺序加锁(例如按 ID 升序),保持事务简短,并设置锁超时。Postgres 会检测到死锁并中止其中一个事务;你的代码必须重试。

10.3 复制、CAP 与最终一致性(32, 34, 35, 95)

  • 复制(32): 在多个节点上保存副本,以保证持久性并扩展读能力。异步副本会有延迟。
  • 网络分区(95) 一定会发生。分区期间,CAP 指出系统必须做出选择:拒绝部分请求(一致性),或者用可能过期的数据响应(可用性)。
  • 最终一致性(35): 副本会随时间收敛。对设计的影响:保证读到自己的写入(用户刚写完时把他的读请求路由到主库),展示“处理中”状态,绝不根据有延迟的副本做涉及资金的决定。
  • 账本和余额:强一致、单一事实来源、只追加的复式记账记录。

10.4 分布式锁与 leader 选举(33, 38)

  • 当多个实例不能同时做同一件事时就需要它(只允许一个定时任务执行者、一个分区只有一个消费者)。
  • 优先使用数据库原生方案:Postgres advisory lock、基于 SKIP LOCKED 的任务队列。
  • Redis 锁需要过期时间(TTL)和 fencing token:一个被暂停的进程可能在锁过期后醒来,仍然去写数据。存储层必须拒绝过期的 token。
  • Leader 选举(Raft、etcd、ZooKeeper、Kubernetes leases)通常是直接使用基础设施提供的能力,而不是自己造。

10.5 时间与时钟偏差(96)

  • 机器时钟会漂移;绝不要用不同服务器的挂钟时间来给事件排序。
  • 用数据库生成的时间戳或序列号来排序;用单调时钟来测量时长。
  • Token 过期和 webhook 时间戳校验需要留一点偏差容忍度。

10.6 内存泄漏与垃圾回收(41, 42)

  • 托管语言中的泄漏通常是你一直持有的引用:没有上限、不断增长的缓存,从未移除的事件监听器,全局数组,未关闭的流。
  • 症状:内存稳步上涨,然后 OOM 重启;长时间 GC 停顿导致延迟飙升。
  • 工具:堆快照(Node 用 Chrome DevTools)、--inspect、按实例的内存指标、有上限的 LRU 缓存。

实验 10:双花挑战

  1. 做一个钱包:充值、转账、提现。写一个同时发起 200 笔并发转账的测试。看着 AI 写的朴素版本凭空丢钱或多出钱。
  2. 用原子更新和约束修复它;改成复式记账账本,保证所有分录之和始终为零。
  3. 用两笔顺序相反的转账制造死锁;通过统一加锁顺序修复。
  4. 从有延迟的副本读取数据(模拟延迟),复现“读不到自己的写入”的 bug;修复它。
  5. 用堆快照找出一个事先埋好的内存泄漏。

完成标准(Definition of Done)

AI 提示词包

  • “找出这个代码库中所有先检查后执行的代码序列。针对每一处,展示竞态是怎么发生的,并给出原子化的修复。”
  • “把这段余额逻辑重写成复式记账账本,并附上不变量测试。”