【從一台 Server 到分散式架構】第 30 篇:限流、排隊與降級實戰——開賣與直播場景
課程平台接到一個大合作:某位知名講師要在平台上開一堂限量課程,只有 500 個名額,全台粉絲期待已久。
開賣時間訂在晚上八點整。
小明在下午四點接到一個訊息:「預計會有超過 10 萬人同時搶課。」
他對著螢幕盯著看了五秒,然後開始冷靜地想:「好。我們要怎麼讓系統活過這五分鐘。」
先理解問題的本質
平常,課程平台的 QPS 大約是 500-1000。
晚上八點整,10 萬人同時點「立即購買」,就是一瞬間的 10 萬 QPS——是平常的 100 倍。
這種流量不是「持續的高流量」,而是一個「瞬間的尖峰」。通常幾分鐘後,大部分想搶的人已經搶完或知道沒搶到,流量就會回落。
應對這種場景的策略不是「擴充到能承受 10 萬 QPS」——那代價太高,而且大多數時間都在閒置。
應對策略是:讓系統能控制流量,而不是被流量淹沒。
防護層一:前端限速——讓瀏覽器別打那麼快
最前面的防護是在前端。
用戶點了「立即購買」之後,按鈕要立刻變成灰色、不可再點,防止用戶瘋狂連點送出多個請求。
這很簡單,但能砍掉一大部分重複請求——因為用戶在等待回應時,焦慮會讓他們不斷點擊。
防護層二:CDN 與靜態資源快取——減輕伺服器壓力
開賣前,用戶會不斷刷新課程頁面確認時間。
課程的基本資訊(頁面 HTML、圖片、說明文字)不需要每次都打到後端——這些靜態內容可以放 CDN 快取。
讓大量的「純查看流量」打 CDN,不打後端,保留後端資源給「真正的購買請求」。
防護層三:Rate Limiting——每個 IP / 用戶只能每秒幾次
在 API Gateway 層設速率限制:
- 每個 IP 每秒最多 5 次請求
- 每個登入用戶每秒最多 2 次購買請求
10 萬個用戶,每人每秒最多打 2 次,實際進到後端的請求就被壓在可控範圍。
第 13 篇介紹過幾種限流算法,這裡最常用的是 Token Bucket:
- 每個用戶有一個令牌桶,每秒補充 N 個令牌
- 每次請求消耗一個令牌
- 沒有令牌就拒絕(回傳 429 Too Many Requests)
用 Redis 記錄每個用戶的令牌狀態(原子操作保證計數準確)。
防護層四:虛擬排隊——讓用戶「排隊等候」而不是「直接被拒絕」
速率限制砍掉的請求,直接回傳錯誤,體驗很糟——用戶不知道自己有沒有機會。
更好的做法是虛擬排隊系統:
- 請求進來 → 不直接處理,而是加入佇列,回傳「你排在第 XXXX 號」
- 用戶看到排隊畫面,知道自己還有機會,不會馬上放棄或重新整理
- 佇列依序出列,後端按可承受的速率逐批處理
最關鍵的一環:庫存扣減的原子性
500 個名額,10 萬個人搶——最後的最後,庫存扣減必須是原子操作,不能賣超。
錯誤的做法:
查詢庫存 → 還有 1 個 → 扣減庫存 → 建立訂單
(多個請求同時執行這段邏輯,同時看到「還有 1 個」,同時扣減 → 賣超)
正確的做法一:資料庫悲觀鎖(Pessimistic Lock)
BEGIN;
SELECT inventory FROM courses WHERE id = :course_id FOR UPDATE;
-- 取得鎖,其他請求在這裡等待
UPDATE courses SET inventory = inventory - 1 WHERE id = :course_id AND inventory > 0;
COMMIT;FOR UPDATE 讓只有一個事務能同時操作這筆記錄。缺點:高並發時大量請求在等鎖,吞吐量受限。
正確的做法二:Redis 原子操作(更常用於高並發)
-- Lua script 在 Redis 執行,原子性保證
local stock = redis.call('GET', key)
if tonumber(stock) > 0 then
redis.call('DECR', key)
return 1 -- 搶到了
else
return 0 -- 沒了
end用 Redis 做庫存的「搶佔層」:先在 Redis 原子扣減,搶到了才進資料庫建訂單。Redis 扣減極快,失敗立即回傳「售罄」。
防護層五:降級——非核心功能在高峰時關掉
即使做了限流和排隊,高峰時後端壓力還是高。
這時要確保核心功能(購買)能用,非核心功能可以暫時降級:
| 功能 | 高峰期處理 |
|---|---|
| 課程購買 | 正常服務(核心) |
| 即時聊天室 | 正常服務(直播相關) |
| 課程搜尋 | 回傳快取結果(不打 Elasticsearch) |
| 學習進度同步 | 暫緩,排入佇列 |
| 推薦系統 | 回傳預先計算的固定結果 |
| AI 學習助理 | 暫時停用或排入長隊 |
| 後台統計報表 | 不可用 |
這就是第 13 篇說的優雅降級(Graceful Degradation):讓最重要的事情先活下去。
直播場景:另一種高流量特性
直播場景和搶購不一樣:
- 搶購:瞬間高峰,持續幾分鐘
- 直播:持續的高並發,幾小時內大量用戶同時在線
直播的挑戰:
- 彈幕 / 聊天:每秒幾千條留言,每個在線用戶都要看到
- 觀看人數:要即時顯示,但精確計數成本高
彈幕的架構和即時通知一樣(第 17 篇):WebSocket + Pub/Sub。
但有一個額外的考量:彈幕不需要送給「每個在線用戶」全部——可以做前端降採樣:
- 伺服器把所有彈幕廣播出去
- 前端只顯示其中 30%(隨機抽樣),避免畫面被淹沒
觀看人數的計數,同樣用 Redis 計數 + 定期批次刷新:可以接受「顯示的人數比實際多或少一點」,但不需要精確到每個加入或離開都立刻更新。
整體防護層設計
小結
高流量防護的設計哲學是:
不要讓流量決定你的系統命運,要讓你決定流量進入的速率。
每一層防護,都在做同一件事:讓真正重要的請求能夠完成,讓系統不被超出承受能力的流量壓垮。
核心工具回顧:
- Rate Limiting(第 13 篇):Token Bucket / Leaky Bucket 截流
- 虛擬排隊:佇列 + 排隊號碼,體驗友善地削峰
- 庫存原子扣減:Redis Lua script,防止賣超
- 優雅降級(第 13 篇):高峰時保核心、捨非核心
- WebSocket + Pub/Sub(第 17 篇):即時更新排隊狀態、直播彈幕
下一篇(也是最後一篇),我們來談一件很實際的事:如何用這套思維去拆解系統設計面試題——面試官問你「設計一個 XXX 系統」時,怎麼有條理地回答。