一、前言:为什么同步秒杀扛不住高并发?
在传统秒杀实现中,用户点击“立即抢购”后,系统会同步完成以下操作:
看似合理,但在万人并发场景下:
- 数据库连接池耗尽
- Redis CPU 打满
- 接口响应时间从 50ms 暴涨到 5s+
- 大量请求超时或失败
根本问题:所有操作都在 HTTP 请求线程中同步执行,系统成为“瓶颈漏斗”。
本文将介绍异步秒杀架构,通过消息队列削峰填谷,轻松应对 10 倍流量冲击。
二、异步秒杀核心思想:快进快出 + 异步处理
✅ 核心原则:
前端请求只做“资格校验 + 入队”,后续操作全部异步化!
架构对比:
| 请求耗时 | 200~1000ms | 20~50ms |
| DB 压力 | 高(实时写) | 低(异步消费) |
| 系统吞吐 | 低(受 DB 限制) | 高(仅受 MQ 限制) |
| 用户体验 | 卡顿、超时 | 秒级响应 |
三、异步秒杀整体架构图
用户请求
↓
[网关层] → 限流、鉴权
↓
[秒杀服务]
├── 1. 校验用户资格(登录、一人一单)
├── 2. Redis 扣减预库存(Lua 原子)
└── 3. 发送消息到 MQ(如 RabbitMQ/Kafka)
↓
[订单消费者]
├── 1. 再次校验(兜底)
├── 2. 创建订单 & 优惠券记录
├── 3. 扣减 DB 库存(最终一致)
└── 4. 发送通知(短信/站内信)
🔑 关键点:HTTP 请求在第 3 步就返回成功!
四、详细实现步骤(Spring Boot + RabbitMQ)
步骤 1:定义秒杀消息体
public class SeckillMessage {
private Long userId;
private Long couponId;
private String requestId; // 幂等 ID
// getters/setters
}
步骤 2:秒杀入口(快进快出)
@RestController
public class SeckillController {
@Autowired
private SeckillService seckillService;
@PostMapping("/seckill/{couponId}")
public Result<String> seckill(@PathVariable Long couponId,
@RequestHeader("userId") Long userId) {
// 1. 快速校验(Redis 判断是否已领取)
if (seckillService.hasReceived(userId, couponId)) {
return Result.fail("您已领取过该优惠券");
}
// 2. Redis Lua 原子扣减预库存
if (!seckillService.tryDecreaseStock(couponId)) {
return Result.fail("手慢了,已抢光!");
}
// 3. 发送异步消息(核心!)
String requestId = UUID.randomUUID().toString();
SeckillMessage message = new SeckillMessage(userId, couponId, requestId);
rabbitTemplate.convertAndSend("seckill.exchange", "seckill.route", message);
// 4. 立即返回!不等订单创建
return Result.success("提交成功,请稍后查看领取记录");
}
}
✅ 优势:整个 HTTP 请求 < 50ms,用户体验极佳!
步骤 3:异步消费者(可靠处理)
@Component
@RabbitListener(queues = "seckill.queue")
public class SeckillConsumer {
@Autowired
private CouponRecordService recordService;
@RabbitHandler
public void handleSeckillMessage(SeckillMessage message) {
try {
// 1. 幂等校验(防止重复消费)
if (recordService.existsByRequestId(message.getRequestId())) {
return;
}
// 2. 兜底校验(Redis 状态可能变化)
if (recordService.hasReceived(message.getUserId(), message.getCouponId())) {
return; // 已领取,跳过
}
// 3. 创建领取记录 & 扣减 DB 库存
recordService.createCouponRecord(
message.getRequestId(),
message.getUserId(),
message.getCouponId()
);
// 4. 发送通知(可选)
notificationService.sendSuccessMsg(message.getUserId());
} catch (Exception e) {
// 记录日志,可配置死信队列重试
log.error("秒杀异步处理失败", e);
}
}
}
五、关键技术点解析
1️⃣ 预库存 vs 真实库存
- Redis 预库存:用于快速拦截超卖(高性能)
- DB 真实库存:用于持久化和对账(强一致)
- 两者允许短暂不一致,通过对账 Job修复
2️⃣ 幂等性保障
- 消息携带 requestId(全局唯一)
- 消费前检查是否已处理,避免重复发券
3️⃣ 失败补偿机制
- 消费失败 → 进入死信队列(DLQ)
- 定时任务扫描 DLQ,人工或自动重试
4️⃣ 用户体验优化
- 前端提示:“提交成功,正在处理…”
- 跳转到“我的优惠券”页,轮询状态(或 WebSocket 推送)
六、异步秒杀的优势总结
| 性能 | QPS 提升 5~10 倍(从 1000 → 10000+) |
| 稳定性 | DB 不再是瓶颈,系统更健壮 |
| 扩展性 | 可水平扩展消费者实例 |
| 容错性 | 消费失败可重试,不丢失请求 |
| 资源利用率 | 高峰流量被“平滑”处理 |
七、适用场景与注意事项
✅ 适合场景:
- 优惠券秒杀
- 限量商品抢购
- 抽奖活动
- 报名/预约系统
⚠️ 注意事项:
- 不适用于强一致性场景(如支付)
- 需要设计状态查询接口(用户需知道是否成功)
- MQ 必须高可用(集群部署 + 持久化)
- 监控消息积压(如 Prometheus + Grafana)
八、结语
感谢您的阅读!如果你有任何疑问或想要分享的经验,请在评论区留言交流!
网硕互联帮助中心






评论前必须登录!
注册