1. 系统架构设计
用户请求 → 网关层 → 秒杀服务(预检查+发消息) → RabbitMQ → 订单消费者(扣库存+下单)
- 关键角色:
- 生产者:接收用户秒杀请求,校验后发送消息到MQ。
- 消费者:从MQ消费消息,执行库存扣减和订单创建。
2. 具体实现步骤
步骤1:用户请求预处理
- 前端限流:按钮防重复点击(例如点击后禁用按钮)。
- 网关层过滤:
- 用户鉴权、黑名单拦截。
- 令牌桶或漏桶算法限流,避免请求直接压垮后端。
步骤2:库存预检查(Redis缓存)
- 预减库存:
java
// Redis中存储商品库存(原子操作) Long remain = redisTemplate.opsForValue().decrement("stock:" + itemId); if (remain < 0) { // 库存不足,回滚库存(避免超卖) redisTemplate.opsForValue().increment("item:stock:" + itemId); return "秒杀失败"; }
- 优势:Redis单线程+原子操作,避免数据库锁竞争。
步骤3:生成消息并发送到RabbitMQ
- 消息内容:用户ID、商品ID、秒杀时间等。
- 确保消息可靠性:
- 生产者确认机制(Publisher Confirms):保证消息到达Broker。
- 消息持久化:设置
delivery_mode=2
,防止RabbitMQ宕机丢失消息。
java
MessageProperties props = new MessageProperties(); props.setDeliveryMode(MessageDeliveryMode.PERSISTENT); Message message = new Message(msgBody.getBytes(), props); rabbitTemplate.send("seckill_exchange", "seckill.route", message);
步骤4:消费者异步处理订单
- 消费者逻辑:
- 幂等性校验:通过唯一ID(如用户ID+商品ID+时间)检查是否已处理。
- 数据库扣库存:
sql
UPDATE item SET stock = stock - 1 WHERE id = #{itemId} AND stock > 0;
- 创建订单:若扣库存成功,插入订单表(订单号需唯一)。
- 事务管理:保证扣库存和订单创建在一个事务中。
步骤5:异常处理与重试
- 消费者ACK机制:
- 处理成功:手动发送
basicAck
确认消息。 - 处理失败:
basicNack
重回队列或进入死信队列(DLX)。
java
channel.basicConsume(queue, false, (tag, msg) -> { try { processOrder(msg); channel.basicAck(tag, false); } catch (Exception e) { // 重试3次后进入死信队列 channel.basicNack(tag, false, retryCount < 3); } });
- 处理成功:手动发送
- 死信队列:用于收集多次重试失败的消息,人工介入处理。
3. 优化策略
3.1 流量削峰
- 队列拆分:按商品ID分队列,避免单队列拥堵。
- 消费者集群:动态调整消费者数量(基于队列堆积监控)。
3.2 数据一致性
- 最终一致性:通过异步消息保证数据库和Redis库存最终一致。
- 定时对账:定期校验Redis库存与数据库实际库存。
3.3 防止作弊
- 风控规则:在生产者端校验用户行为(例如同一IP/用户限购)。
4. 典型问题及解决方案
问题1:消息重复消费
- 解决方案:
- 消费者端幂等性设计(唯一索引、Redis标记已处理消息)。
- 消息表去重:消费前检查数据库是否存在相同订单。
问题2:数据库性能瓶颈
- 解决方案:
- 库存扣减使用数据库行级锁(
SELECT ... FOR UPDATE
)或乐观锁。 - 订单表分库分表(按用户ID哈希)。
- 库存扣减使用数据库行级锁(
问题3:Redis与数据库库存不一致
- 解决方案:
- 消费成功后异步同步库存到数据库。
- 采用分布式事务(如Seata)保证原子性(牺牲部分性能)。
5. 总结
- 核心价值:通过RabbitMQ将同步操作异步化,解决高并发下的数据库压力。
- 关键点:
- 预减库存快速拦截请求。
- 消息队列缓冲+消费者异步处理。
- 幂等性+重试机制保证数据一致性。
- 适用场景:秒杀、抢购、限时促销等高并发场景。
通过以上方案,系统可支持万级QPS的秒杀场景,同时保持稳定性和可扩展性。