【從一台 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):讓最重要的事情先活下去。


直播場景:另一種高流量特性

直播場景和搶購不一樣:

  • 搶購:瞬間高峰,持續幾分鐘
  • 直播:持續的高並發,幾小時內大量用戶同時在線

直播的挑戰:

  1. 彈幕 / 聊天:每秒幾千條留言,每個在線用戶都要看到
  2. 觀看人數:要即時顯示,但精確計數成本高

彈幕的架構和即時通知一樣(第 17 篇):WebSocket + Pub/Sub。

但有一個額外的考量:彈幕不需要送給「每個在線用戶」全部——可以做前端降採樣

  • 伺服器把所有彈幕廣播出去
  • 前端只顯示其中 30%(隨機抽樣),避免畫面被淹沒

觀看人數的計數,同樣用 Redis 計數 + 定期批次刷新:可以接受「顯示的人數比實際多或少一點」,但不需要精確到每個加入或離開都立刻更新。


整體防護層設計


小結

高流量防護的設計哲學是:

不要讓流量決定你的系統命運,要讓你決定流量進入的速率。

每一層防護,都在做同一件事:讓真正重要的請求能夠完成,讓系統不被超出承受能力的流量壓垮。

核心工具回顧:

  • Rate Limiting(第 13 篇):Token Bucket / Leaky Bucket 截流
  • 虛擬排隊:佇列 + 排隊號碼,體驗友善地削峰
  • 庫存原子扣減:Redis Lua script,防止賣超
  • 優雅降級(第 13 篇):高峰時保核心、捨非核心
  • WebSocket + Pub/Sub(第 17 篇):即時更新排隊狀態、直播彈幕

下一篇(也是最後一篇),我們來談一件很實際的事:如何用這套思維去拆解系統設計面試題——面試官問你「設計一個 XXX 系統」時,怎麼有條理地回答。

相關推薦

2026-04-07
【從一台 Server 到分散式架構】第 31 篇:從面試題反推架構——用本系列思維拆解系統設計考題
「設計一個 URL 短網址服務」「設計一個訊息系統」——系統設計面試問的不是背答案,而是思考方式。這篇整理了一套可以重複使用的面試框架,並用兩道例題示範如何從需求出發,一步步推導出架構、說清楚取捨。
2026-04-05
【從一台 Server 到分散式架構】第 29 篇:用同樣的思維看 ChatGPT——AI 聊天系統架構
ChatGPT 看起來像一個聊天視窗,背後卻有幾個特殊的設計挑戰:回應是串流的、推理非常耗資源、每輪對話要記住上下文、系統要支援幾千萬用戶同時使用。這篇用熟悉的架構思維,拆解 AI 聊天系統的關鍵設計。
2026-04-04
【從一台 Server 到分散式架構】第 28 篇:用同樣的思維看 Twitter——社群動態與時序設計
Twitter 的核心功能看起來很簡單:發推文、看動態、按讚留言。但「動態牆」背後藏著一個棘手的設計問題:你追蹤 200 個人,每個人都可能隨時發文——你打開 App 時,那條時序動態要怎麼快速組出來?這篇來看社群動態系統的兩種策略:推(Fan-out on Write)與拉(Fan-out on Read)。
2026-04-03
【從一台 Server 到分散式架構】第 27 篇:用同樣的思維看 Uber——即時位置與派車系統
Uber 的核心問題看起來簡單:乘客在哪、司機在哪、把最近的司機派過去。但背後有幾個棘手的設計挑戰:海量的司機位置每秒更新、要在幾秒內完成司機配對、派單要公平且不重複。這篇用熟悉的架構思維,拆解 Uber 的即時調度系統。