Sunday 的面试指南

秒杀系统怎么设计?如何限流、扣库存并避免超卖?

🧑‍💻 面试官:一个商品有 100 件库存,活动开始后大量请求一起进来,怎么设计?

🙋‍♂️ 我:先限流,用 Redis 原子扣库存,再通过队列创建订单。

🧑‍💻 面试官:库存扣了,消息没发出去呢?订单超时,库存返还了,支付成功的回调又来了呢?

🙋‍♂️ 我:还需要预占记录、订单状态和补偿,不能只做一次减法。

🧑‍💻 面试官:Redis 故障以后,数据库还有库存。可以直接让所有请求打数据库继续买吗?

这道题不是只考「扣减是否原子」,还要讲清「准入、预占、成交」之间的关系:请求进来不等于买到,预占成功也不等于付款完成。

面试速答(60 秒版)

秒杀首先需要保护系统容量。静态资源尽量提前分发,入口做身份校验、重复请求限制与限流,不让所有请求直接进入库存和数据库。

库存检查与预占必须在定义好的范围内原子完成,同时记录请求或预占 ID,避免重复请求反复扣减。Redis 可以承担高频准入和预占,但数据库仍要有不超卖的约束与最终状态管理。

预占以后,再可靠推进订单创建、支付和超时释放。队列可以削峰,却不能消除消息重复、投递失败及状态竞争,需要幂等、补偿和对账。

最后要明确故障策略:缓存或队列故障时不能无界打到数据库;对于库存一致性与可用性的取舍,应由同一套权威库存和状态规则决定,而不是临时绕过。

秒杀:准入、预占、成交分开

知识点详解:一次请求怎样从排队变成真正买到?

先让能处理的请求进来

活动页面、商品介绍和图片,可以提前准备并通过缓存或 CDN 分发。它们没有必要都挤到交易链路。

真正购买请求则需要按系统容量准入,并检查用户、活动时间和重复提交。限流能减少压力,但不是防机器人和公平性的完整方案,还可能需要针对账户、设备及异常行为的规则。

队列也不是无限容量的保险箱。消费者速度固定,生产持续更快,积压就会增长。需要队列上限、等待时长、过载拒绝和可查询的状态。

原子预占,要把“判断”和“修改”放在一起

如果先读库存大于零,再分开扣减,多个请求可能同时读到最后一件,随后都成功进入订单流程。

可以在库存服务的事务或原子操作中一起完成校验、预占和必要的请求去重。Redis 脚本可以使对应操作在其执行范围内原子完成,但要注意 Cluster 的键分布及原子范围。

Redis INCR 文档展示了计数及限流中的原子操作,也提醒组合命令可能存在竞态。单条命令原子,不等于整条业务链路原子。

因此,分布式锁不是唯一答案。首先明确竞争资源、库存权威和操作边界,再选工具;锁本身也不能替代订单与消息的一致性。

预占成功以后,还欠用户一笔订单

预占只表示暂时留了一件,并不表示订单已经成功,更不表示付款完成。

一种教学方案,是保存可追踪的预占记录,由可靠投递过程推进订单创建。消费者按预占 ID 幂等创建,完成以后把状态关联回来。

如果消息发送结果不确定,不能直接再扣一件;应该查询已有预占,重试推进同一笔记录。如果预占已经失败或释放,也不能把迟到的消息当成新购买成功。

缓存里的高速计数还需要与权威记录对账。数据库可用条件更新等方式限制最终库存不低于允许范围;它应是完整方案里的防线,而不是缓存故障时无界承接全部请求。

超时释放与支付成功,为什么会打架?

订单等待支付时预占库存;到期后准备取消;此时支付回调可能同时到达。

不能一边无条件返还库存,另一边无条件改成已支付。需要通过订单状态的条件迁移,判断谁已取得有效状态,并只执行对应的库存动作。

当前状态合法推进示例需要限制的动作
已预占创建待支付订单重复扣减库存
待支付成功支付或超时取消同时认定两种结果
已取消按规则处理迟到支付直接复活已释放的库存
已支付履约或退款流程再次按超时返还

具体规则可能是拒绝、退款,或重新确认库存后另行处理。关键在于规则事先明确,状态迁移与外部支付结果可核对。

补偿还需要幂等,不能一次超时任务重复跑,就返还多件库存。

同一订单,不能既成交又返还

故障时,宁可排队或拒绝,也不要绕过保护

Redis 不可用以后,如果所有流量直接查库,缓存故障就可能升级成数据库故障。

可以选择关闭活动、收紧准入、受控切换或只接受明确容量内的请求。若切换库存权威,还要先确认旧预占怎样迁移、旧请求怎样停止,避免两个来源各自放行。

压测时不仅看每秒请求数,还要模拟重复请求、发送确认丢失、队列积压、消费者崩溃和迟到支付。最后检查库存、预占、订单与支付是否能对上。

故障时,不要无界打库

面试官继续追问

Redis 原子扣减,就能保证永不超卖吗?

不能单独作这个承诺。原子范围内没有竞态,不代表故障切换、库存初始化、重复补偿和数据库落单都正确。需要说明权威与完整故障模型。

请求进入队列,就可以提示购买成功吗?

不应该。通常只能提示受理或排队,再查询实际结果。提前说成功,会把未完成的业务结果承诺给用户。

每个请求都加锁,是不是最稳?

不一定。可能把吞吐压低,也增加租约和故障处理问题。应根据原子操作、唯一约束和状态迁移设计竞争控制,不把锁当成系统设计的总答案。

面试速记卡

  • 准入:先保护容量,队列也要有积压与等待边界。
  • 预占:校验、去重与修改在明确范围内原子完成。
  • 成交:预占、订单和支付分别记录,不提前宣告成功。
  • 释放:条件迁移与幂等补偿,处理迟到支付。
  • 故障:不无界打库,压测后核对库存与最终状态。
简历汪永久免费在线制作简历,模板直接套用、导出无水印,永久免费、下载免费,不需要付费解锁任何功能。去写简历