系统设计 Lab
Payment ledger 的瓶颈是 correctness,不是 QPS:钱绝不能被凭空造出、丢失或被 double-charge。
调节支付速率、idempotency-key TTL、外部 provider 的 latency、ledger entry 总数、reconciliation 窗口和 region 数。设计会先加上保证 correctness 的机制——idempotency key、append-only 的 double-entry ledger、ACID 写入、异步的 PSP state machine——只有在 integrity 锁定之后,才按 account 分区。
分步讲解
一步一步把它想清楚
要点
常规演进场景
从左到右点击,就是预设的演示路径。每张卡片都会改变 workload 输入。
推荐形态
当前架构路径
Clients
Client 提交支付,超时就重试,有时会重发同一个请求
Payment API
Payment API 校验支付请求,并掌管支付的 state machine
Idempotency layer 存下 idempotency key,让重试请求只被处理 exactly once
Ledger
Ledger service 把配平的 debit 和 credit 记录为不可变的 source of truth
Datastore
ACID store 原子且持久地提交配平的 entry
Account shards 把 ledger 按 account 分区,让写入扩展又不破坏配平
Async + audit
PSP adapter 通过又慢的外部 provider 做 authorize 和 capture,配合重试和 DLQ
Reconciliation 重新拉取 provider 记录,并和 ledger 重新配平
Audit log 为合规保留一条 append-only 的事件 trail,记录每一次状态转换
瓶颈
Ledger 写入负载
Idempotency-store 负载
PSP 相关的异步 backlog
Ledger 存储增长
Reconciliation 延迟
为什么会变
决策权衡
有出处支撑的规则
这些是模型背后那些经得起时间考验的 system design 论断。而 slider 的具体阈值,则被刻意标注为教学用的假设。
Idempotency key 让重试的支付请求可以安全重放
Stripe 让 client 在请求上带一个 idempotency key,于是网络故障引发的重试会返回原始结果,而不是再创建一次 charge。
Stripe Docs异步地 capture 资金,因为 provider 又慢又可能失败
一个 PaymentIntent 会经过 requires_action、processing 和 succeeded 状态;把 capture 当成异步的 state machine,就能容忍 provider 的 latency 和失败。
Stripe Docsappend-only 的 double-entry ledger 是不可变的 source of truth
把钱建模成往 account 上配平记账,让 ledger 可审计、能自校验,于是余额是推算出来的,而不是就地改动的。
martinfowler.comEvent sourcing 保留一条完整、可重放的状态变更审计 trail
把每一次状态转换都持久化成一个不可变事件,就得到了一份完整的审计 log,还能通过重放历史来重建余额。
martinfowler.com教学用假设
- Correctness 才是瓶颈;单节点的吞吐和存储预算是保守的教学数字,不是厂商上限。
- 每笔支付都按一次配平的 double-entry 写入建模;idempotency store 大约每次尝试(含重试)看到一次查找。
- PSP 相关的 backlog 按「支付速率 × provider latency」估算,也就是在等外部 provider 的 in-flight capture 数。