架构 · 高频 5/5
如何设计一个秒杀系统?
秒杀系统要先削峰,再隔离核心链路,最后用库存预扣、异步下单、限流降级和幂等机制保证系统不被瞬时流量压垮。
简短答案
秒杀系统要先削峰,再隔离核心链路,最后用库存预扣、异步下单、限流降级和幂等机制保证系统不被瞬时流量压垮。
详细解析
秒杀的核心矛盾是瞬时流量远大于真实库存。设计时应把读流量、排队流量和写库流量分层处理。
关键设计
- 入口层:验证码、风控、用户限频、静态资源 CDN。
- 应用层:本地缓存商品信息,令牌桶或滑动窗口限流。
- 库存层:Redis 预扣库存,扣减成功再进入 MQ。
- 订单层:消费 MQ 异步创建订单,使用唯一键和业务流水保证幂等。
- 兜底层:库存回补、超时关单、降级提示和监控告警。
面试回答模板
我会先说明秒杀不是简单加机器,而是把流量削峰、库存扣减、订单创建和支付链路拆开。入口做限流和风控,库存用 Redis 做原子预扣,成功后写入 MQ,订单服务异步落库,并用幂等键避免重复下单。最后补充监控、降级和库存回补机制。
代码示例
Long stock = redisTemplate.opsForValue().decrement(stockKey);
if (stock == null || stock < 0) {
redisTemplate.opsForValue().increment(stockKey);
throw new SoldOutException();
}
messageQueue.send(new CreateOrderCommand(userId, skuId, requestId));
易错点
- 直接让所有请求打到数据库扣库存。
- 只说 Redis,不说明库存和数据库最终一致性。
- 忽略重复点击、消息重复消费和支付回调幂等。
常见追问
为什么不用数据库直接扣库存?
数据库可以保证强一致性,但在秒杀峰值下容易成为瓶颈。更常见做法是 Redis 预扣削峰,再通过异步订单和补偿机制保证最终一致。
如何避免超卖?
库存扣减必须是原子操作,数据库层要有条件更新或唯一约束兜底,消息消费也要幂等。