🚀從0到1構建高并發秒殺系統:實戰 RocketMQ 異步削峰與Redis預減庫存
📖一、 簡介
在電商、搶票等高并發場景中,秒殺系統面臨著“高并發、庫存稀缺、易超賣、系統易崩”的嚴峻挑戰。傳統的同步處理架構難以支撐海量請求并發下的性能與一致性要求。
本文從實戰出發,系統性地講解如何基于 Redis + RocketMQ + MySQL + Spring Boot 構建一個高性能、高可用、強一致性的秒殺系統。通過 接口限流、Redis 原子扣減庫存、RocketMQ 異步削峰、數據庫冪等落庫 等機制,徹底解決了高并發下的核心問題,如超賣、重復下單、系統崩潰等。
你將看到:
- 🧠 秒殺系統面臨的本質問題與設計原則
- 🧱 架構層次的分層與職責劃分
- ?? Redis + Lua 腳本實現庫存預扣與并發控制
- 🚀 RocketMQ 異步下單實現削峰填谷與解耦
- 💡 MySQL 樂觀鎖 + 冪等設計實現最終一致性
- 🛠? 全程配套詳細注釋代碼、架構圖與數據庫設計
本文不僅提供了完整可運行的思路,還具備工程級可落地性。適合架構師、后端工程師在面對實際高并發場景時作為參考與實踐藍圖。
🧠 二、秒殺系統的挑戰與本質
“秒殺”業務場景常出現在電商、搶票、預約系統中,具有以下挑戰:
維度 | 問題 | 說明 |
---|---|---|
并發性 | 高并發訪問 | 瞬時請求高達數十萬甚至上百萬 |
數據一致性 | 超賣/重復下單 | 庫存是關鍵共享資源 |
系統穩定性 | 容易雪崩 | 單點性能瓶頸可能導致系統掛掉 |
響應速度 | 秒級反饋 | 用戶希望秒殺是否成功即時反饋 |
核心:限流 + 削峰 + 異步 + 緩存
🔧 三、系統總體架構設計
🔨 四、核心技術選型與職責
技術組件 | 作用 |
---|---|
Redis | 緩存庫存、原子扣減、用戶狀態標記 |
RocketMQ | 削峰填谷、異步解耦 |
MySQL | 最終訂單存儲、庫存持久化 |
Spring Boot | 微服務框架 |
Guava RateLimiter | 接口級限流 |
📦 五、秒殺系統核心模塊詳細設計
1?? 接口限流 + 秒殺入口
@RestController
@RequestMapping("/seckill")
public class SeckillController {// 每秒只允許100個請求通過private final RateLimiter rateLimiter = RateLimiter.create(100);@Autowiredprivate SeckillService seckillService;@PostMapping("/{itemId}")public ResponseEntity<String> seckill(@PathVariable Long itemId) {// 通過令牌桶控制請求速率if (!rateLimiter.tryAcquire()) {return ResponseEntity.status(HttpStatus.TOO_MANY_REQUESTS).body("請求過多,請稍后重試");}// 模擬用戶獲取(實際從登錄信息中取)Long userId = 1001L;boolean success = seckillService.processSeckill(itemId, userId);if (success) {return ResponseEntity.ok("請求成功,正在排隊中...");} else {return ResponseEntity.badRequest().body("庫存不足或已搶完");}}
}
2?? Redis 原子扣減庫存(Lua 腳本)+ RocketMQ 消息發送
@Service
public class SeckillServiceImpl implements SeckillService {private static final String STOCK_KEY_PREFIX = "seckill:stock:";@Autowiredprivate RedisTemplate<String, Object> redisTemplate;@Autowiredprivate RocketMQTemplate rocketMQTemplate;@Overridepublic boolean processSeckill(Long itemId, Long userId) {String stockKey = STOCK_KEY_PREFIX + itemId;// Lua 腳本:原子檢查庫存并扣減String luaScript = "if (tonumber(redis.call('get', KEYS[1])) > 0) then " +" return redis.call('decr', KEYS[1]) " +"else return -1 end";// 執行腳本,防止并發引起庫存超賣DefaultRedisScript<Long> redisScript = new DefaultRedisScript<>();redisScript.setScriptText(luaScript);redisScript.setResultType(Long.class);Long result = redisTemplate.execute(redisScript, Collections.singletonList(stockKey));if (result == null || result < 0) {// 庫存不足return false;}// 構建訂單消息并發送到 MQSeckillOrderMessage message = new SeckillOrderMessage(userId, itemId);rocketMQTemplate.convertAndSend("seckill-topic", message);return true;}
}
3?? RocketMQ 消費者:監聽秒殺訂單消息,落庫處理
@Component
@RocketMQMessageListener(topic = "seckill-topic", consumerGroup = "seckill-consumer-group")
public class SeckillConsumer implements RocketMQListener<SeckillOrderMessage> {@Autowiredprivate SeckillOrderService orderService;@Overridepublic void onMessage(SeckillOrderMessage msg) {try {// 調用下單服務進行庫存校驗和訂單落庫orderService.createOrder(msg.getUserId(), msg.getItemId());} catch (Exception e) {// 消息重試或記錄異常用于補償System.err.println("消費失敗: " + e.getMessage());}}
}
4?? 下單服務:冪等校驗 + 數據庫存儲 + 樂觀鎖扣減
@Service
public class SeckillOrderServiceImpl implements SeckillOrderService {@Autowiredprivate SeckillOrderRepository orderRepository;@Autowiredprivate StockRepository stockRepository;@Transactionalpublic void createOrder(Long userId, Long itemId) {// 冪等校驗:防止重復下單(可用唯一索引或Redis SET)if (orderRepository.existsByUserIdAndItemId(userId, itemId)) {return;}// 扣減數據庫庫存,使用樂觀鎖 versionint updated = stockRepository.decreaseStock(itemId);if (updated == 0) {throw new RuntimeException("庫存不足,數據庫扣減失敗");}// 寫入訂單記錄SeckillOrder order = new SeckillOrder(userId, itemId, LocalDateTime.now());orderRepository.save(order);}
}
5?? 數據庫表結構設計(庫存 + 訂單)
📌 商品庫存表(帶 version 樂觀鎖)
CREATE TABLE stock (id BIGINT PRIMARY KEY AUTO_INCREMENT,item_id BIGINT NOT NULL UNIQUE,stock INT NOT NULL,version INT NOT NULL DEFAULT 0
);
📌 庫存扣減 SQL(樂觀鎖)
UPDATE stock
SET stock = stock - 1, version = version + 1
WHERE item_id = ? AND version = ? AND stock > 0;
📌 訂單表
CREATE TABLE seckill_order (id BIGINT PRIMARY KEY AUTO_INCREMENT,user_id BIGINT NOT NULL,item_id BIGINT NOT NULL,create_time DATETIME NOT NULL,UNIQUE KEY uniq_user_item (user_id, item_id)
);
📊 六、性能優化與高可用建議
方向 | 建議 |
---|---|
限流 | 接口層限流(Guava)、網關限流(Sentinel) |
削峰 | 使用 RocketMQ 異步下單,防止數據庫擊穿 |
冪等 | Redis SETNX、唯一索引、分布式鎖 |
日志與監控 | Prometheus + Grafana、日志追蹤鏈路 |
高可用 | RocketMQ 主從部署、Broker 宕機自動切換 |
? 七、總結
通過將 Redis 與 RocketMQ 結合,構建出一個具備如下特性的高并發秒殺系統:
- 并發控制得當:接口限流 + Redis 原子性操作
- 系統抗壓能力強:消息削峰,MQ異步下單
- 數據一致性高:數據庫落庫有冪等保障
- 用戶體驗更好:請求秒級響應,后臺異步處理
📘 八、后續可拓展點
- 秒殺結果異步通知(短信 / WebSocket)
- Redis 秒殺狀態標識(用戶是否成功)
- RocketMQ 事務消息提升可靠性
- 異常消息記錄 + 自動補償機制