日期:2026-07-11
标签:#面试 #八股 #后端 #订单 #库存 #场景题
一句话答案
使用业务幂等键和唯一约束阻止重复请求,并通过“同用户同商品仅允许一个有效未支付订单”、短时库存预占和超时关单回补控制库存占用。
面试口语版
前端按钮防重复只能改善体验,服务端仍要做幂等。客户端生成 requestId,服务端以用户、商品、活动和 requestId 建唯一约束;对于业务意义上的重复订单,再限制同一用户同一商品只能存在一笔有效待支付订单,重复请求直接返回原订单。库存预占设置合理过期时间,延迟队列触发关单,状态条件更新成功后再释放库存。对恶意占库存还要加用户购买频率限制、风险评分和黑名单。
原理拆解
- 请求幂等:同一个 requestId 只能创建一次。
- 业务去重:
userId + skuId + activityId + activeStatus表达同一购买意图。 - 库存状态:可用库存 → 预占库存 → 已售库存,取消时预占回退。
- 超时关单:延迟消息触发,数据库扫描作为兜底。
- 风控限额:限制同用户/IP/设备的未支付订单数和下单频率。
关键细节
- MySQL 难以直接对“部分状态”建唯一索引时,可维护 active 标识或单独的购买资格表。
- 关单与支付并发必须通过订单状态机和条件更新处理。
- MQ 可能重复投递,释放库存应带业务流水并保证幂等,不能重复回补。
- 不要无限延长待支付时间,热门库存应使用更短有效期。
面试官追问
- 用户确实想买两件怎么办?
- 延迟消息丢失如何关单?
- 订单创建成功但库存预占失败怎么办?
- 如何防止库存重复回补?
面试官追问参考答案
1. 用户确实想买两件怎么办?
把购买数量作为同一订单明细,而不是让用户并发创建两笔未支付订单;服务端校验活动限购和单次最大数量后一次预占。若业务允许多次购买,可把去重维度加入客户端 requestId,并单独限制用户未支付订单总数和占用库存量。
2. 延迟消息丢失如何关单?
延迟消息只是触发器,数据库中的 expireTime 才是事实。消费者幂等关单,同时定时任务按到期时间和状态扫描漏单,可用时间轮或分片索引降低扫描成本;支付前也应再次检查订单是否已过期。
3. 订单创建成功但库存预占失败怎么办?
将订单保持创建中或直接标记失败/关闭,不能暴露为可支付待支付订单。更常见是先成功预占库存再创建订单,若创建失败按业务流水补偿库存;跨系统步骤用状态机、可靠事件和定时对账最终收敛。
4. 如何防止库存重复回补?
建立唯一库存流水,例如 orderId + operationType,数据库插入流水和库存增加在同一事务中,唯一键阻止重复执行。Redis 回补也用 Lua 检查补偿标记后再增加,数据库库存作为最终对账依据。
学习清单
- 区分请求幂等和业务去重。
- 能说明预占、确认和释放库存的状态流转。