秒殺系統是電商、票務等場景的高併發、高可用、高性能的典型分佈式系統,核心挑戰在於瞬時流量洪峯、庫存超賣、接口防刷和數據一致性。以下是一套完整的秒殺系統設計方案,從架構分層、核心技術到關鍵細節逐一拆解。
一、 秒殺系統核心痛點
- 瞬時高併發:秒殺開始瞬間,QPS 可能達到平時的數十倍甚至百倍,極易壓垮數據庫。
- 庫存超賣:多用户同時扣減庫存,若未做好併發控制,會出現庫存為負的情況。
- 接口防刷:惡意用户通過腳本高頻請求,佔用資源導致正常用户無法參與。
- 數據一致性:秒殺訂單、庫存、支付狀態需要保證最終一致。
- 用户體驗:高併發下避免頁面卡頓、請求超時、結果返回慢。
二、 秒殺系統整體架構(分層設計)
採用分層削峯的思路,從前端到後端逐層攔截流量,避免直接衝擊核心數據庫。
用户層 → CDN/靜態化層 → 接入層 → 業務層 → 數據層
1. 前端/CDN 層:第一道削峯屏障
核心目標:減少無效請求,降低後端壓力
-
頁面靜態化
- 秒殺商品頁面(圖片、文案、秒殺按鈕)全部靜態化,部署到 CDN 節點,用户訪問時直接從 CDN 加載,不經過後端服務。
- 秒殺按鈕前端置灰邏輯:未到秒殺時間時按鈕置灰,通過前端時間戳控制(需後端同步時間,防止客户端篡改時間)。
-
請求限流
- 前端添加按鈕點擊防抖:限制用户在 1s 內只能點擊 1 次,避免重複提交請求。
- 前端驗證碼/滑塊驗證:過濾掉大部分腳本機器人請求。
-
預加載
- 秒殺開始前,提前加載商品庫存、秒殺規則等非實時數據到前端緩存,減少秒殺時的請求量。
2. 接入層:流量分發與限流
核心目標:攔截惡意請求,均勻分發流量
-
負載均衡
- 使用 Nginx 或雲廠商的 SLB 做負載均衡,將請求分發到多個後端應用實例,避免單實例過載。
- Nginx 配置限流模塊(如
limit_req_zone):基於 IP 維度限制請求頻率(例如:單 IP 每秒最多 5 次請求),超出則直接返回 503。
-
防刷策略
- 黑名單機制:維護惡意 IP/用户名單,Nginx 直接攔截黑名單請求。
- Token 驗證:用户進入秒殺頁面時,後端生成一個唯一 Token 下發給前端,秒殺請求必須攜帶有效 Token,且 Token 只能使用 1 次,防止重複請求。
3. 業務層:核心邏輯與異步解耦
核心目標:快速處理請求,避免同步阻塞
這一層是秒殺系統的核心,需重點解決庫存鎖定和訂單創建的性能問題,採用“限流 + 緩存 + 異步”三板斧。
(1) 服務限流與熔斷
- 接口級限流:使用 Sentinel 或 Hystrix 對秒殺接口做限流,設置每秒最大處理能力(如 1000 QPS),超出部分直接返回“秒殺火爆,請稍後再試”,避免服務雪崩。
- 熔斷降級:當後端數據庫/緩存出現異常時,觸發熔斷機制,直接返回降級結果,保護核心服務不被拖垮。
(2) 緩存層:核心數據緩存(Redis 為主)
秒殺系統的核心是“用緩存抗併發”,儘量避免操作數據庫。
-
庫存緩存
- 將商品庫存數量預加載到 Redis 中,使用 Redis String 類型存儲(如
seckill:stock:1001 → 500)。 -
扣減庫存時,直接操作 Redis,使用 Lua 腳本保證扣減的原子性,防止超賣:
-- 庫存 key、用户購買數量 local stockKey = KEYS[1] local buyNum = ARGV[1] -- 獲取當前庫存 local currentStock = tonumber(redis.call('get', stockKey)) if currentStock == nil or currentStock < tonumber(buyNum) then -- 庫存不足,返回 0 return 0 end -- 扣減庫存 redis.call('decrby', stockKey, buyNum) -- 返回 1 表示成功 return 1Lua 腳本的優勢是原子性執行,避免了“查庫存 → 扣庫存”的分步操作導致的併發問題。
- 將商品庫存數量預加載到 Redis 中,使用 Redis String 類型存儲(如
-
用户限購緩存
- 限制單用户/單 IP 只能購買 1 件商品,使用 Redis Set 類型存儲已秒殺成功的用户 ID(如
seckill:users:1001 → {user1, user2})。 - 在 Lua 腳本中增加用户校驗邏輯:若用户已在 Set 中,直接返回 0,避免重複下單。
- 限制單用户/單 IP 只能購買 1 件商品,使用 Redis Set 類型存儲已秒殺成功的用户 ID(如
-
熱點數據緩存
- 秒殺商品的基本信息(名稱、價格、圖片)緩存到 Redis,業務層直接從 Redis 獲取,不查詢數據庫。
(3) 異步化處理:訂單創建與支付
秒殺的核心是“搶庫存”,而不是“創建訂單”,訂單創建是耗時操作,需異步處理。
- 庫存預扣減成功後:不直接操作數據庫創建訂單,而是將訂單信息(用户 ID、商品 ID、數量)發送到消息隊列(如 RocketMQ、Kafka)。
- 消費端異步處理:消息隊列的消費端從隊列中取出訂單信息,再執行創建訂單 → 扣減數據庫庫存 → 生成支付鏈接的邏輯。
- 返回結果給用户:庫存扣減成功後,立即返回“秒殺成功,請儘快支付”,無需等待訂單創建完成,提升用户體驗。
異步化的優勢:
- 同步請求快速響應,避免因數據庫操作阻塞導致的請求超時;
- 消息隊列起到削峯作用,消費端可以根據數據庫處理能力調整消費速度。
4. 數據層:最終一致性保障
核心目標:保證庫存、訂單數據的一致性,避免數據錯亂
-
數據庫選型
- 訂單庫、庫存庫建議使用 MySQL,並做分庫分表(如按用户 ID 哈希分表),降低單表數據量,提升讀寫性能。
- 庫存表設計:需包含
商品 ID、總庫存、已售庫存、版本號(樂觀鎖字段)。
-
庫存一致性保障
-
Redis 庫存與數據庫庫存雙寫一致性:
- 秒殺前:將數據庫庫存同步到 Redis;
- 秒殺中:Redis 扣減庫存,消費端異步扣減數據庫庫存;
- 秒殺後:定時任務校驗 Redis 庫存與數據庫庫存,若不一致則以數據庫為準修正 Redis。
-
數據庫樂觀鎖防超賣:消費端扣減數據庫庫存時,使用版本號控制:
UPDATE stock_table SET sold = sold + 1, version = version + 1 WHERE goods_id = 1001 AND version = #{version} AND sold + 1 <= total;若更新影響行數為 0,説明庫存不足,直接丟棄該訂單消息。
-
-
數據兜底方案
- 消息隊列的死信隊列:消費端處理訂單失敗時(如數據庫異常),將消息轉入死信隊列,後續人工介入處理。
- 日誌記錄:秒殺的每一步操作(庫存扣減、訂單創建)都記錄詳細日誌,便於問題排查和數據對賬。
三、 關鍵技術點與注意事項
1. 防止超賣的核心手段
- Redis Lua 腳本原子扣減(核心):保證“查庫存 + 扣庫存 + 用户校驗”的原子性。
- 數據庫樂觀鎖(兜底):防止 Redis 與數據庫數據不一致導致的超賣。
- 庫存預扣減:秒殺開始前凍結部分庫存,只允許秒殺庫存參與活動,避免與正常銷售渠道衝突。
2. 高可用保障
- 服務集羣化:所有核心服務(接入層、業務層、緩存層)均部署多實例,避免單點故障。
- Redis 集羣:使用 Redis 主從 + 哨兵模式或 Redis Cluster,保證緩存服務的高可用,防止 Redis 宕機導致秒殺失敗。
- 降級預案:當緩存/數據庫出現嚴重故障時,觸發全鏈路降級,直接返回“秒殺活動暫時關閉”,保護系統不崩潰。
3. 性能壓測與優化
- 秒殺上線前,必須進行全鏈路壓測:使用 JMeter 或 Locust 模擬數十萬用户併發請求,驗證系統的瓶頸(如 Nginx 限流是否生效、Redis 能否扛住 QPS、數據庫寫入速度是否達標)。
-
優化點:
- 關閉 MySQL 的自動提交事務,批量提交訂單;
- Redis 開啓管道(Pipeline)模式,提升批量操作性能;
- 業務層去除不必要的日誌打印和數據校驗。
四、 秒殺系統架構總結
秒殺系統的設計核心是 “分層削峯、緩存優先、異步解耦、最終一致”,通過前端靜態化、CDN、Nginx 限流攔截大部分流量,Redis 扛住核心併發,消息隊列異步處理訂單,數據庫最終保障數據一致性。
整個系統的性能瓶頸通常在數據庫寫入,因此要儘量減少直接操作數據庫的次數,讓緩存和消息隊列承擔更多壓力。