系统设计 32:用观测与成本证伪架构假设
架构图写着“p99 小于 200 ms”,监控却只收成功请求的延迟;十个请求里有一个超时,图表仍然可能全部变绿。可观测性首先是为设计结论提供反例渠道,其次才是把组件指标画成仪表盘。 本篇沿内容分享的发布、读取和异步通知路径定义测量契约。讨论对象是候选后端方案,不涉及监控产品选型或真实生产成本报价。关键不变量是撤销后不能返回内容;服务目标是读取三十天有效请求成功率 99.9%、服务端 p99 小于 200 ms、通知入队后 99% 在五秒内完成。目标和实验观察分别记录。 五类指标对应五种反驳 Google SRE 的监控章节提出延迟、流量、错误和饱和度四类信号,并区分成功与失败延迟。本题还需要业务正确性:返回了已撤销内容的请求可能是 HTTP 200,低延迟、高可用,却仍违反设计。 设计主张 测量位置与分母 能推翻它的观测 读取足够快 网关收到请求至写完响应,超时单列 p99 超目标或超时比例上升 通知不会持续积压 入队至最终处理,包含仍未完成者 最老任务年龄增长 故障不被重试放大 同一逻辑请求下游尝试总数 尝试数增长而成功数不增 撤销后不可见 撤销确认...
系统设计 31:多地域切换与数据恢复的不同边界
地域切换解决的是服务入口和写入权限的变化;数据恢复解决的是哪些已确认记录仍然存在。DNS 已切到另一座城市,既不能证明旧地域停止写入,也不能证明最后一笔订单没有丢失。多地域设计必须分别给出这三个答案。 本文把教学分享服务扩展到两个地域,采用每个账户只有一个写入归属地域的候选方案。它不描述任何公司的内部架构,也不实现跨地域共识协议。正常情况下归属地域提交元数据,另一地域异步接收日志;只有公开且允许陈旧的统计可以就近读取。 从业务损失反推复制确认 需求卡先区分内容和权限:普通草稿允许灾难后丢失最近五秒更新,撤销权限的成功确认不允许在恢复后被当作从未发生。前者可以接受有界的数据损失目标,后者需要更强的提交条件,或在无法确认撤销状态时拒绝访问。不能用“系统 RPO 五秒”抹平两个业务对象之间的差异。 RPO 是恢复点目标,用时间表达可接受的最近数据损失范围;RTO 是恢复时间目标,从业务中断开始,直到指定业务功能恢复。目标必须说明故障范围:本题只讨论单地域失效、备用地域及控制面仍可用;两个地域同时永久丢失、账号密钥泄露和逻辑误删不由地域复制自动处理。Google SRE 的数据完整性章...
系统设计 30:任务租约和过期持有者栅栏
一个日报任务在 10 秒到期,工作者 A 取走后停止响应;16 秒工作者 B 重领并完成。A 恰好又恢复,拿着旧结果写入数据库。如果系统只检查“任务 ID 相同”,最后保存的可能是 A 的过期数据。延时队列解决“什么时候可领”,租约和栅栏解决“谁的完成确认还有效”,两者不是同一个问题。 每个周期是独立的一次执行 教学任务 report 的周期实例键是 (task_id,slot),其中 slot 来自固定时区下的计划触发时间,不由工作者的当前时钟临时生成。同一键至多保留一个有效结果;过期工作者可以消耗计算资源,不能覆盖已被后继租约提交的结果。任务执行并不保证恰好一次;若副作用发生在邮件、账务等外部系统,仍需要那些系统支持幂等键或结果核对。误触发、超时和未执行都必须可查询。 教学 SLO:未来 30 天 99% 已到期实例在调度时间后 10 秒内被领取(只对调度器可用且下游有容量的实例,测量计划时间与数据库租约时间);99.9% 到期实例在 5 分钟内达到成功或明确的死信状态。禁止用“已经领到”替代“业务动作完成”。跨时区夏令时规则、需要人工签核的任务及任意第三方 HTTP 的恰...
系统设计 29:排行榜的迟到修正与热点代价
如果一场比赛的第 60–120 秒排行榜在第 120 秒立刻封榜,一条第 69 秒发生、但第 121 秒才入站的得分应该去哪?忽略它能让榜单快速稳定,却会让账本与排行榜对不上。排行榜设计首先需要决定:读的是实时暂定榜,还是可纠错的最终榜。 本题假设每个被验证的得分事件 event_id 对同一窗口只计一次;窗口采用事件时间半开区间 [60,120) 秒。相同分数采用并列密集排名,展示顺序再按玩家 ID 字典序稳定排序。删除作弊得分也是修正事件,不是默默改计数。只有权限通过的已确认事件进入榜单,未验证事件不参与计算。 先把榜单合同写全 教学 SLO:暂定榜近 30 天 99% 请求在入口 200 毫秒内返回,且带 computed_through 位点;已受理得分在 5 秒内进入暂定榜(处理完成时间减本地持久化时间的 99%)。终榜在窗口结束后等待 2 分钟迟到宽限,再按相同事件集批量重算并公布版本。以上都是目标,不是实验测出的性能。更晚到达的事件放到修正队列,由业务规则决定是否发新版本。跨赛季汇总、奖金发放和反作弊算法不在本题范围内;有奖金时不能把该暂定榜当财务最终排名。 假设...
系统设计 28:订单支付与邮件收据的三种确认
支付接口没回包,数据库写着 PENDING,用户却看到扣款:客服该查哪一个真相?订单库不是支付机构的账本,邮件“已接收”也不是收件人“已收到”。本篇处理支付受理后回包丢失、重复与乱序回调,并用合成收据状态表示本地接收与退信;邮件是旁路通知,退信不能回滚已确认的付款。 先钉住不可逆的动作 请求键 request-1 只对应一个本地订单 o1,订单金额为 2500 分;一次订单只有一个稳定的网关扣款键 charge-o1。只有确认网关对应扣款成功,订单才从 PENDING 单向转到 PAID;乱序到达的 pending 不能把 PAID 降回去。CANCELLED 只允许在确认没有成功扣款时进入;退款是独立状态机而非把历史付款改为未发生。收据意图与本地订单状态在同一事务提交;本地记录 PAID 不等于用户收件箱已出现邮件。 教学 SLO:入口合法请求 99.9% 在 2 秒内返回订单 ID 和当前状态(30 天窗口,入口接收到响应发出);待核查支付 99% 在 10 分钟内被网关查询或人工队列接管(只测本地状态变化,排除外部长期不可用)。不承诺“2 秒内扣款”或邮件投递成功。税务发...
系统设计 27:通知系统的逻辑去重与送达边界
用户提交订单两次刷新页面,只应该看到一条“订单已受理”的逻辑通知;短信渠道返回超时,却无法据此判断短信没有发出。前一句是自己数据库能控制的去重,后一句是跨渠道的确认缺口。把两句话合成“通知恰好送达一次”,会让重试策略无法落地。 这里设计的是本地假渠道支持的教学方案,不代表任何邮件、短信平台的实际送达语义。业务事件先落地,再根据收件人偏好、安静时段与渠道错误决定投递时机。 需求从收件人开始 输入为 event_id, recipient_id, template_version。教学契约规定同一事件、收件人、模板版本最多生成一条逻辑通知;渠道改变不是新事件,但一次明确的用户主动重发要带新请求键。偏好以创建通知时的版本作为路由快照;取消订阅则是更强的安全约束,投递前还需重新查最新授权。营销退订不能因旧的排队任务而失效。 假设目标:事件受理成功响应意味着事件及通知意图已经持久化,不意味着外部送达;近 30 天的 99.9% 合法事件在受理后 60 秒内进入终态(渠道接受、永久拒绝或待人工核查),计时从入口收到请求至本地状态记录,按自然月窗口统计。安静时段里的营销通知不计入这一目标,应...
系统设计 26:占座到期与支付回调的库存边界
同一个座位的最后一张票,甲乙同时点击购买。排队系统可以让请求平稳进入,却不能代替库存判断;缓存中“锁住 A1”也无法回答这样的问题:甲占座到期释放给乙后,甲的支付成功回调才到,应该给谁出票?答案取决于座位与订单的权威状态,而不是谁最先收到回调。 本文只讨论实名/价格之外的单座位库存、限时占座、支付结果和重复回调。支付调用是本地模拟;并发与唯一约束由实际 SQLite 文件中的事务验证,不把它外推为真实票务或第三方支付的端到端保证。 谁能作出“卖出”承诺 接口按状态划分:POST /queue/entries {event_id} 返回有期限的准入票据;POST /holds {seat_id, request_key} 返回 order_id, expires_at 或明确的不可占用;GET /orders/{id} 查询权威状态;POST /payment-callbacks {event_id, order_id, result} 仅供验证签名的支付系统调用。排队票据限制到达率,不是库存所有权;...
系统设计 25:司机指派竞争与迟到确认
两名乘客同时命中同一位最近司机,地理查询都没有错,却只能有一单得到有效指派。附近搜索解决“谁可能接单”;派单解决“谁独占接单权”。如果将位置索引中的司机从“可用”改为“忙碌”就算确认,两个请求仍可能在旧副本中各拿到一次成功。 本文用一位本地模拟司机、十二个并发乘客请求和真实 SQLite 写事务划清派单边界;不假设任何网约车公司的内部实现。规模和服务目标均为教学假设,非实测性能。 把一次派单拆成两个确认 乘客请求 POST /trips {pickup, destination, request_key};服务返回 trip_id、状态。派单内部接口 offer(trip_id, driver_id, offer_token, deadline) 把“占有接单机会”写入权威存储;司机本地模拟回调 POST /offers/{token}/ack。状态路径为 requested → offered → accepted → started → completed,在发车前可 cancelled,超时可 expired。本次实验只实现至 ac...
系统设计 24:空间网格的边界与位置新鲜度
在坐标 (1.00, 0.50) 搜索 50 米内的人:两位好友分别位于网格边界左右各 10 米。如果只查请求所在的网格,就会漏掉其中一位;如果不检查位置更新时间,昨天路过的人仍会显示“附近”。商家搜索和附近好友都需要空间候选集,但好友查询还要处理权限与位置时效,不能仅换个表名复用餐馆检索。 本篇用固定二维公里坐标讲解候选筛选,再把真实地理距离、权限变化和动态更新明确列为未验证边界。所有流量与延迟是教学假设。 哪些结果可以出现 商家侧需要按半径查营业且公开的商家;好友侧必须先有双方有效可见关系,才可返回对方的近似位置。读请求不变量:结果必须在请求半径内、查询时尚未过期、用户具备查看权限;空间索引只生成候选,不能替代最后的距离、ACL 和时间过滤。写请求不变量:同一主体只接受版本更高的位置,旧事件不能覆盖新坐标。删除/隐藏位置后,新请求必须在约定传播窗口内停止返回;缓存失效前不能承诺强撤销。 教学 SLO 设为商家查询 p99 不超过 200 ms,好友位置从客户端提交到可查询 p95 不超过 10 s,撤销权限后新请求 5 s 内不再返回。这里未做负载、网络或缓存试验,三个数字...
系统设计 23:爬虫的访问边界与可恢复队列
一条首页链接指向 /calendar/0,第二页指向 /calendar/1,后面每天都有“下一天”。抓到一百万页不等于建好了搜索入口:如果抓取进程重启后忘了待访问 URL,或者不同写法的同一 URL 占满队列,站点还没抓完,预算已经耗尽。爬虫首先是有边界的调度系统,其次才是 HTTP 客户端。 本篇只抓进程内搭建的合成站点,不请求真实网站。规模与 SLO 都是教学目标,实验只验证有限队列及失败行为,不测生产爬虫吞吐。 先问允许抓什么 需求是发现站点内页面、保留有用内容供后续索引,同时限制对站点的访问速率。核心不变量有三条:未被策略允许的 URL 不发请求;一个规范化 URL 在本轮最多标成一个待处理任务;被确认完成的任务在恢复后不再作为新任务反复抓取。最后一条并不承诺网络请求恰好一次:响应到达与“已完成”提交之间崩溃,恢复只能重抓,再由索引侧按 URL 和内容版本幂等处理。 教学 SLO:允许的源站请求相邻发起时间间隔至少 30 ms;站点出现暂时性 503 时最多重试两次;调度进程故障后五分钟内恢复已提交的任务状态。实验的请求间隔断言容忍 2 ms 服务端观测偏差,不验证五...















