文章目录19. 100 件商品、10 万人秒杀如何绝不超卖且尽量不少卖具体示例20. 设计亿级优惠券领取系统如何防重复、防超发具体示例21. 演唱会选座如何避免同一座位被两个人买到具体示例22. 支付回调重复、乱序、超时订单如何保持正确具体示例23. 订单由几十个状态组成如何设计可演进的状态机具体示例24. 多仓库存如何扣减既要就近履约又不能超卖具体示例25. 设计账户转账系统如何保证钱不多不少具体示例26. 设计高并发短链接系统。具体示例27. 多机房下如何做全局限流同时保持低延迟具体示例19. 100 件商品、10 万人秒杀如何绝不超卖且尽量不少卖容量与目标。先区分“库存正确”与“请求都同步完成”。入口 10 万 QPS但有效订单最多 100 个系统应在最前面把无效流量压缩掉。一人一单、库存预占时长、支付超时和回补规则必须明确。主链路。活动配置和库存预热到 Redis网关按用户、设备、活动限流并校验资格。Lua 原子执行“是否买过 库存大于零 扣减 记录用户”成功后写入可靠队列。订单消费者以(activity_id, user_id)唯一索引创建订单数据库扣库存再用stock 0条件更新兜底。支付超时由延迟消息关闭订单并回补回补流水也必须幂等。MySQLOrder ServiceMQRedis LuaGatewayUserMySQLOrder ServiceMQRedis LuaGatewayUseralt[无库存或重复][预扣成功]秒杀请求(request_id)限流/资格/防刷原子检查一人一单并预扣库存快速失败order event至少一次投递唯一键创建订单条件扣减查询订单结果为什么。Redis 把 10 万并发压缩到至多 100 个有效下游写MQ 削平瞬时峰值数据库约束负责最终正确。不要使用一个全局分布式锁把所有请求串行化。异常。Redis 扣了但消息没发使用 Outbox/Stream、事务消息或库存预扣流水补偿扫描。发送超时可能已成功必须复用同一事件 ID。回补与支付回调竞争时以订单状态机条件更新决定唯一结果。具体示例具体设定。商品sku123库存 10010 万人同时点抢购一人只能买一件。落地例子。Redis Lua 原子判断sismember buyers uid、stock 0成功则decr stock、sadd buyers uid、写 stream/order_event。订单服务用(activity_id, uid)唯一键防重复MySQL 扣库存用update stock set availableavailable-1 where sku123 and available0兜底。面试可讲。Redis 是削峰和预扣DB 是最终正确兜底MQ 是削平后续建单峰值。不要用一个大锁串行所有请求也不要只靠缓存不落流水。20. 设计亿级优惠券领取系统如何防重复、防超发模型。coupon_template存总量与规则coupon_user以(template_id, user_id)唯一coupon_stock保存可用量和版本领取流水保存 request_id。模板配置与库存分开便于高频扣减与低频规则更新。链路。资格和频控前置固定总量券使用 Redis Lua 原子扣库存与用户去重再异步落券。数据库唯一键和条件更新双重兜底。无限量券不需要全局库存热点只做资格与一人一券。发券成功后返回可查询的领取记录不要求请求一直等待异步结束。分片。用户券包按 user_id 分片运营按券模板查询全量领券用户走反向索引或离线数仓。热点模板库存可分段或分桶但需要中心配额控制避免总额超发。失效。券状态用UNUSED - LOCKED - USED/UNUSED状态机支付锁券带租约超时释放。退款是否退券由业务规则决定所有回调使用订单号幂等。具体示例具体设定。一张新人券总量 100 万活动页峰值 20 万 QPS每个用户只能领一次。落地例子。coupon_user(template_id, user_id)唯一Redis Lua 同时做库存扣减和用户去重成功后写领取事件。落库失败进入补偿扫描重复消费因唯一键返回已领取。面试可讲。防超发靠库存原子扣减和 DB 条件兜底防重复靠用户维度唯一键。无限量券和限量券要分开设计别把所有券都做成库存热点。21. 演唱会选座如何避免同一座位被两个人买到一致性边界。座位是不可分资源适合数据库条件更新或行锁不适合只靠最终一致缓存。状态为AVAILABLE - HELD - SOLDHold 带hold_token和expire_at。预占。用户选座时执行条件更新只有 AVAILABLE 或已过期的 HELD 才能写入新的 hold_token。多座连选按固定 seat_id 顺序锁定任一失败则回滚降低死锁。支付回调只能把匹配 hold_token 的 HELD 改为 SOLD。过期释放。延迟队列触发释放但最终以数据库expire_at条件为准延迟消息可能早到、晚到或重复。页面座位图可读缓存提交时仍回权威库校验不能相信前端颜色。热点。场次分片隔离单场内部按区域拆库要谨慎因为跨区域连座会跨事务。热门场次宁可排队和限流也不要牺牲座位正确性。具体示例具体设定。座位show_id7, seatA-10用户有 10 分钟支付时间。落地例子。座位状态AVAILABLE - HELD - SOLD。选座时执行update seat set statusHELD, hold_token?, expire_at? where seat_id? and (statusAVAILABLE or (statusHELD and expire_at now()))。支付成功只能把匹配hold_token的 HELD 改为 SOLD。面试可讲。前端座位图只是缓存提交时必须回权威库条件更新。延迟消息释放座位只是加速最终仍以expire_at条件判断为准。22. 支付回调重复、乱序、超时订单如何保持正确状态机。明确合法转移例如CREATED - PAYING - PAID - FULFILLED关闭为终态状态更新带当前状态条件和 version旧事件不能让状态倒退。支付单号建立唯一索引。幂等。回调验签后先记录 payment_event 唯一流水再在同一事务中更新订单重复事件命中唯一键后查询并返回已有结果。业务处理成功后再向支付平台响应成功避免“先 ACK 后事务失败”。乱序与超时。查询支付结果作为权威兜底。关单任务与支付成功竞争时数据库条件更新只有一个获胜若平台已支付但订单关闭进入退款或人工补偿状态不能直接忽略。所有外部调用带业务幂等键。为什么。网络层不存在可靠的“只回调一次”。正确答案不是过滤重复请求而是让任意重复和乱序都无法破坏业务状态。具体示例具体设定。订单order_noO1支付平台可能连续回调两次PAY_SUCCESS关单任务也可能同时执行。落地例子。回调先写payment_event(platform_txn_id unique)再条件更新订单where status in (CREATED,PAYING)才能改 PAID。关单任务只允许CREATED/PAYING - CLOSED。如果关单和支付同时到DB 条件更新只有一个成功失败方查订单现态再决定退款或忽略。面试可讲。支付回调天然至少一次且可能乱序。正确性靠幂等流水和状态机条件更新不靠“平台不会重复回调”。23. 订单由几十个状态组成如何设计可演进的状态机显式建模。状态、事件、守卫条件和副作用分离数据库只保存当前状态、version 与关键时间点状态变更流水保存 from/to/event/operator/request_id。禁止业务代码到处直接改 status。执行。命令进入状态机引擎校验当前状态与事件是否合法在本地事务中更新状态并写 Outbox库存、通知、积分等副作用订阅事件完成。跨服务流程用 Saga 编排超时和补偿也是明确事件。并发。使用版本号 CAS 或条件更新防止支付、取消同时覆盖。重复命令按 request_id 返回首次结果。新旧版本并存时订单记录 workflow_version保证老订单继续按老规则推进。为什么。显式状态机让合法路径可审计、可测试事件驱动把主事务与外部副作用解耦。追问应包括终态、人工干预、补偿失败和版本迁移。具体示例具体设定。订单有待支付、已支付、已发货、退款中、已退款、已关闭等几十个状态多个团队都会改订单状态。落地例子。建order_state_transition(from_state, event, to_state, guard)代码只能通过状态机执行PAY_SUCCESS、CANCEL、REFUND_APPLY等事件。状态变更写order_event(order_id, from, to, event, request_id)同一 request_id 幂等。面试可讲。不允许业务代码到处update statusxx。状态机把合法路径、并发条件、副作用和版本迁移都收口后面加新状态才不会把老链路改乱。24. 多仓库存如何扣减既要就近履约又不能超卖模型。库存拆为 physical、reserved、available按(sku_id, warehouse_id)保存库存流水不可变。订单先由路由服务按地址、时效、成本和可用量选择候选仓。扣减。单仓预占使用条件更新available qty跨仓拆单时每个子单独立预占失败则 Saga 释放已占部分。缓存只用于路由粗选最终扣减回库存库。热点 SKU 在仓内可分桶配额但需要周期再平衡。一致性。入库、退货、取消都写库存流水并幂等聚合库存由流水重算对账。超时预占通过租约与延迟任务释放支付成功把 reserved 转为 sold。取舍。强行做跨仓分布式强事务会降低可用性实际更适合短本地事务 可补偿流程。要说明少卖与超卖的优先级和人工调账路径。具体示例具体设定。用户买 3 件 SKU华东仓有 2 件华北仓有 5 件期望就近发货但不能超卖。落地例子。路由先选华东 2 件、华北 1 件分别执行update inventory set availableavailable-qty, reservedreservedqty where sku? and warehouse? and availableqty。如果第二个仓扣减失败Saga 释放第一个仓的 reserved。面试可讲。库存路由可以用缓存粗选但扣减必须回库存库条件更新。跨仓不强行做大事务采用短事务加可补偿流程。25. 设计账户转账系统如何保证钱不多不少账务原则。余额不是唯一真相必须有不可变双边流水每笔转账有全局 transaction_id借方、贷方金额相等金额用整数最小货币单位或 DECIMAL。同库。按固定账户顺序加锁在单事务中校验余额、扣借方、加贷方、写两条分录和转账状态条件更新防止余额为负。死锁可有限重试但复用同一 transaction_id。跨库。使用冻结/确认/解冻的 TCC 或 Saga先冻结借方资金再记贷方最终确认所有阶段幂等并有状态机。协调器故障后由扫描任务继续推进不能依赖人工“猜”。高价值系统定期做总账、分户账、渠道账三方对账。为什么。账务正确性需要可审计和可恢复不能只看最终余额。分布式锁不是跨库资金事务的充分保证。具体示例具体设定。A 给 B 转 100 元A、B 可能在同库也可能在不同分库。落地例子。同库时一事务内写两条分录A-10000分B10000分同时更新余额transaction_id唯一。跨库时先冻结 A 资金再给 B 入账最后确认扣除冻结每一步都有状态和幂等键。面试可讲。账务不能只看余额要有不可变流水借贷平衡可审计。跨库转账失败后靠状态机和扫描任务继续推进或补偿不靠人工猜。26. 设计高并发短链接系统。估算与编码。根据日新增量与保存年限计算短码空间。分布式 ID 经 Base62 编码可避免在线查重为防遍历可做可逆置换或随机码 唯一索引重试。自定义短码单独做唯一约束。存储。link(short_code, long_url, owner, expire_at, status)按 short_code 哈希分片Redis 缓存热点空结果短 TTL 防穿透。跳转链路只做风控、查询和 302点击统计、地域设备分析异步写 MQ。高可用。多地域部署时短码生成不冲突映射数据按就近读复制写后读不到时临时回主或带版本等待。恶意链接需要域名信誉、内容安全和访问限流。取舍。302 便于修改目标与统计301 客户端缓存更强但失去控制。深挖时要回答热点短码、过期清理、批量创建和防枚举。具体示例具体设定。每天新增 1 亿短链热门短链峰值 50 万 QPS 跳转。落地例子。分布式 ID987654321转 Base62 得到短码14q60P表link(short_code, long_url, expire_at, status)按 short_code hash 分片。跳转服务先读 Redis未命中读 DB 回填点击统计异步写 MQ。面试可讲。跳转主链路要极短只做查询、风控和 302。统计、画像、地域分析都异步。自定义短码才需要唯一索引重试普通短码用 ID 编码避免在线查重。27. 多机房下如何做全局限流同时保持低延迟先定义精度。全局严格计数意味着每个请求跨机房协调延迟和可用性代价很高。多数场景允许少量超发适合分层配额。方案。全局控制器按机房权重和历史流量下发令牌配额机房再分给实例本地令牌桶完成请求路径扣减。额度接近耗尽时续租控制面失联时只允许小额 emergency quota并限制有效期。配额随扩缩容回收实例崩溃的剩余额度通过租约过期。严格场景。资源创建、资金等少数接口使用一致性计数服务或单一权威分区不要让所有普通 API 都承担这个成本。限流维度包括租户、用户、接口、设备与下游资源避免一个大客户耗尽共享池。为什么。把跨机房同步从每个请求变成低频控制面操作能在误差上界可控的前提下保持本地低延迟。还需处理时钟漂移、突发借额和故障恢复后的流量洪峰。具体示例具体设定。某开放 API 全局限制 100 万 QPSA 机房 60%B 机房 40%网络抖动时也要低延迟。落地例子。控制面每秒给 A 60 万令牌、B 40 万令牌各机房本地令牌桶扣减。A 流量突增可以向控制面借 5% emergency quota租约 3 秒控制面失联时只能用小额保底额度防止无限超发。面试可讲。严格全局计数会让每个请求跨机房延迟和可用性都差。多数 API 适合分层配额误差有上界资金类少数接口才用强一致计数。