系统设计 35:三场限时设计与约束改变复盘
限时系统设计的复盘应记录“哪个新约束迫使哪个决策变化”,而不是统计最终画了多少组件。短链、派单、票务都可能从单库开始,流量增长后的变化却不同:短链先遇到热读,派单先遇到空间候选与资源竞争,票务先遇到集中争抢和支付确认。
本篇提供三份原创的四十五分钟书面设计练习,每份包含时间段、实际设计稿、约束注入和复盘。这里没有进行或声称进行三场真实持续四十五分钟的面试;时间是可供读者执行的议程,附带程序只核对议程总和与容量反例,不把脚本耗时伪装成面试时长。
练习一:可以撤销的短链
0–5 分钟,明确目标。 创建、重定向、过期和撤销是范围;公开搜索、点击计费与恶意链接扫描只记为后续需求。链接 ID 唯一,创建超时重试不能生成两份,同一个幂等键换目标要拒绝。撤销确认后开始的新读取不返回目标,已在途响应和用户复制的 URL 无法撤回。拟定读 p99 100 ms、创建 p99 300 ms,均为教学目标。
5–13 分钟,容量与接口。 基准读 2000 request/s、写 20 request/s,读写比 100:1,响应按 500 B,出口 1 MB/s。每条元数据 400 B,保留三十天、三份:20×86400×400×30×3=62.208 GB;索引与备份另计。接口是 POST /links、GET /{code}、DELETE /links/{code}。表中保留 code, target, state, version, owner,请求表按 (owner, idempotency_key) 唯一。
13–25 分钟,最小方案。 单应用、单数据库与唯一约束足以开始,先测读取路径。创建在目标与幂等映射同事务提交后确认;重定向先查当前状态,再返回可控缓存策略。不能仅因为 GET 常见便使用永久重定向且长时间缓存;这里有撤销契约。HTTP 方法的幂等性与应用幂等键分别讨论,参考 RFC 9110。
25–40 分钟,注入十倍流量。 假设原数据库经未来压测可承载 5000 read/s,现读峰变为 20000;这个 5000 是练习给定能力,不是真实测量。旧方案利用率达到四倍容量,不能继续宣称延迟目标成立。缓存不可变 target 可降低正文读成本,但必须保留当前撤销状态的可靠判定;若权威状态检查仍然瓶颈,普通缓存并没有完成扩展。
可重新协商“撤销最多十秒生效”,换取带 TTL 的近端状态缓存;若不可放宽,就考虑按 code 分片权威状态、降低返回载荷,或采用经过验证的撤销传播与读取栅栏。选择必须说明代价。备选公开永久短链可以长缓存,但那是另一个产品契约,不能悄悄替换题目。
40–45 分钟,复核。 故障时间线是创建已提交、响应丢失、同键重试回原 code;删除后缓存还在、读取必须拒绝。容量复盘发现新增缓存后目标正文负载下降,授权路径并未消失。追问若撤销变成合规删除,应补正文、日志和备份保留边界,不能只改一条重定向记录。
练习二:一辆司机不能接受两个有效派单
0–5 分钟,定义资源竞争。 输入乘客起终点、司机位置与接单状态,输出匹配候选并发起有限时邀约。排除真实地图路由和支付。一个司机同一时刻至多有一份有效指派;超时旧邀约迟到确认不能覆盖新指派。拟定候选查询 p99 100 ms、派单决策 p99 500 ms,位置允许五秒陈旧,指派写入必须按权威状态检查。
5–13 分钟,估算。 基准 100 次派单/s、二十万在线司机、每五秒一次位置更新,位置入口 200000/5=40000 update/s。每条 100 B 是 4 MB/s 原始有效载荷;保留一天两份约 691.2 GB。若只存最新位置,逻辑正文仅 20 MB,但仍有索引与更新负载。由此先决定位置历史是否必需,不能把最新状态与轨迹留存混成同一存储需求。
POST /rides 带业务幂等键,POST /offers/{offer_id}/accept 带司机身份及邀约版本。数据模型 drivers(id, state, assignment_version)、offers(id, ride_id, driver_id, expires_at, epoch)、rides(id, state, assigned_driver)。空间索引只负责候选;权威数据库条件更新决定最终归属。
13–25 分钟,给出候选与提交的分层。 最小匹配器从周边单元查候选,剔除过期位置,再尝试给司机创建邀约。两个乘客可能同时得到同一候选,最终提交条件必须包含司机仍空闲、邀约仍有效、当前版本一致。用户取消也必须产生新版本,不能只发一条尽力而为的“停止匹配”消息。
25–40 分钟,注入十倍派单。 派单变为 1000/s,原匹配器给定处理能力 300/s,积压净增长 700/s。按空间单元分片匹配器能扩大候选计算能力,但热点火车站仍可能集中在一个格子;扩大单元数量也可能让边界查询跨更多分片。应对高密区域单独细分并限定候选数,资源提交仍按司机 ID 归属,避免空间单元迁移制造双重所有权。
备选全局匹配优化可能提升总体匹配质量,但需要更大的批次与等待预算;若当前首要目标是低延迟接单,小窗口局部策略更容易解释。若业务改为“每三十秒集中匹配,容忍等待”,全局优化才可能成为更合适的方向。不能把算法质量和状态一致性合为同一个保证。
40–45 分钟,回看迟到确认。 邀约 O1 到期、司机被 O2 占用、O1 的 accept 才到。数据库按版本拒绝 O1,客户端显示邀约失效;不能用“请求本身成功送达”当作司机已接单。故障恢复从已提交指派重建匹配器状态,位置索引可以陈旧重建,指派账本不能丢。追問多地域时,应先按司机归属单写,避免两个地域独立判断空闲。
练习三:集中开票与迟到支付
0–5 分钟,区分座位与付款。 用户排队、查看余票、限时占座、支付和查询订单。排除真实支付渠道接入。每个座位最多有一个有效销售订单;支付回调重复不能重复记账;占座到期后迟到支付必须有确定处理,不能静默抢回已经卖给别人的座位。读余票允许短暂陈旧,占座结果必须权威。
5–13 分钟,给出数字与契约。 基准占座 500 request/s、读取 5000 request/s,十倍热点占座为 5000/s。库存库给定安全提交能力 800/s;这是设计练习假设。五分钟占座上限与持续输入相乘,基准在途占座可达十五万份,但若活动只有五万席,实际受库存上限约束,不能机械采用 Little 定律忽略拒绝。单订单 1 KiB、五万席、三份约 153.6 MB,热点问题是并发争抢而非总体容量。
接口 POST /holds 返回 hold_id, expires_at,POST /payments 使用幂等键,回调按支付 ID 去重。表中座位状态 FREE、HELD、SOLD;订单带座位、持有人、占座版本与截止时间。支付已受理与订单已确认分开存储,因为外部响应丢失时不能假定没有扣款。
13–25 分钟,决定确认点。 余票缓存只是展示,占座使用数据库条件更新。占座成功在事务提交后响应,支付完成需要核对当前持有人和版本;回调重复返回已记录结果。若付款在旧占座到期后确认且席位已被他人占有,记录退款待办或人工核对,不把 SOLD 反向改回旧订单。
25–40 分钟,注入十倍请求及更强正确性。 5000/s 输入对 800/s 提交能力是 6.25 倍。增加应用节点不能使同一热门座位更容易提交,反而可能扩大数据库锁竞争。前置有界排队和签名入场凭证限制进入占座区的速率,重复轮询做退避;库存不变量仍由最终提交路径保证,排队服务失效不能放开全部请求直冲数据库。
若新约束要求“付款成功即保证有席位”,就需要渠道授权与捕获分离、库存保持时间与支付状态核对等业务调整,不能仅提高缓存锁 TTL。若渠道无法支持所需流程,应明确存在付款后退款的补偿边界。这是产品能力与外部协议的交界,不是增加一个事务注解就能解决。
40–45 分钟,复盘。 最危险时间线是占座到期、新用户占座、旧支付回调抵达;恢复检查由座位账本、订单账本和支付核对三者共同完成。未达成销售的支付也必须可查询,不能因缺少成功订单而删除。追问排队公平性时需定义同一用户多设备、重连和凭证转卖的行为,再决定排队键与风控。
flowchart LR
S[短链十倍读] -->|正文热读| C[缓存与权威撤销分离]
D[派单十倍请求] -->|空间计算及争抢| P[空间并行与司机条件提交]
T[票务十倍抢购] -->|集中库存竞争| Q[有界准入与座位条件提交]
C -->|验证不变量| R[约束改变评审]
P -->|验证不变量| R
Q -->|验证不变量| R
复盘记录比完整组件图更重要
三份练习采用同一时间预算,却交付不同的扩展动作。短链读写比决定缓存是否有价值;派单位置更新比订单多几个数量级,候选与资源归属必须分开;票务数据总量很小但争抢极集中,重点是准入与条件提交。任何一题只回答“分库分表、缓存、消息队列”,都没有说明哪个约束先触发它。
sequenceDiagram
participant I as 评审者
participant D as 设计稿
participant C as 容量核算
participant V as 不变量检查
I->>D: 输入峰值乘十
D->>C: 保持旧容量会怎样
C-->>D: 三题分别超容量 4 / 3.33 / 6.25 倍
D->>V: 缓存、并行或准入是否改变确认
V-->>D: 保留撤销、司机独占、座位独占
D-->>I: 新路径、代价、恢复与未验证项
Google SRE 的非抽象大规模设计方法强调把设计落到资源和失败条件。本篇的题目、数字和时间分配是独立练习,既不是课程答案,也不是实际面试评分标准。书面评审可以检查推导,却不能代替口头表达、现场澄清与真实限时完成能力。
运行 python3 examples/system-design/labs/35/run.py,断言议程 5+8+12+15+5=45,三种基准负载低于给定容量、十倍后高于容量,原方案继续满足目标的假设被拒绝。输出明确记录 actual_45min_sessions_performed=false;原始证据在 examples/system-design/evidence/35/。这是容量反例和书面设计验收,没有进行性能测试,也没有伪造时间消耗。
[PATTERN] 约束改变后先重算资源与确认边界,再选择扩展动作。保留下来的不变量、被放弃的保证和新增的成本,应同时进入复盘。
实验附件与导航
可运行实验源码 · 本次原始结果系列导读;容量和数据承诺分别沿用系列的方法,本文数字为独立教学假设。






