過載保護 (Overload Protection)

一句話定位

過載保護的核心思想是:讓系統在壓力下優雅降級,而不是全面崩潰
設計良好的系統,在 10 倍正常流量下應該還能服務其中 30%,而不是讓 100% 的請求一起失敗。
它不是單一工具,而是多層次防線的組合:每一層都有它能擋住的、也有它擋不住的東西。面試時展示「為什麼要多層」比背名詞更有說服力。

為什麼過載是核心問題:雪崩

流量不是平滑的曲線,它有尖峰、突發、惡意攻擊、節慶促銷、KOL 突然分享你的連結。當流量超過系統能承受的上限,若無任何保護機制,結果通常是雪崩

請求堆積在佇列 → 延遲上升 → 執行緒池耗盡 → 記憶體膨脹 → GC 瘋狂
   → DB 連線池塞滿 → 下游服務被拖慢 → 整個系統停止回應

關鍵災難點:不只是超出容量的那部分請求失敗,而是「所有」請求都失敗了。10% 的超量流量可以打掛 100% 的服務。


六層防線的心智模型

過載保護工具可拆成六個層次,每層解決的問題不同:

層次 手段 思路 解決什麼
預防 Rate Limiting、Concurrency Limiting 在入口把過多流量擋掉 防濫用、保護內部資源
吸收 Queue-based Load Leveling 用佇列平滑瞬間峰值 不拒絕,只是延後處理
擴容 Auto-scaling 從根本增加容量 跟上需求
自保 Load Shedding、Request Prioritization 主動丟棄部分請求 上面手段都跟不上時的最後防線
隔離 Bulkhead 把資源分艙 局部過載不蔓延成全面崩潰
協同 Backpressure 壓力訊號沿鏈路向上傳播 讓整條鏈路一起減速
外部流量
   ↓
[預防] Rate Limiting ── 擋惡意 / 超量的單一客戶端
   ↓
[預防] Concurrency Limiting ── 保護後端資源不被耗盡
   ↓
[吸收] Queue-based Load Leveling ── 吸收突發、平滑流量
   ↓  ← [擴容] Auto-scaling 持續在這層加機器
後端服務處理
   ↓
[隔離] Bulkhead ── 隔離不同依賴,防止局部過載蔓延
   ↓
[自保] Load Shedding + Request Prioritization ── 最後防線:選擇性丟棄
   ↑
[協同] Backpressure ── 整條鏈路協同減速訊號
面試不必把六層全列出

重點是理解為什麼需要多層:每一層能擋的東西不同。Rate limiting 擋不住「總流量合法但超容量」,那要靠 load shedding;load shedding 太粗暴,所以加 request prioritization 決定丟誰。


Rate Limiting(速率限制)

最基本的入口防線:限制每個客戶端在單位時間內能發出的請求數。解決兩個問題:① 防止單一客戶端(或攻擊者)耗盡整個系統資源;② 確保不同客戶端之間的公平使用

四種演算法對照

演算法 機制 允許突發 邊界突發問題 記憶體 適用
Fixed Window 固定視窗 每分鐘重置計數器,超過上限就拒絕 (邊界 2 秒可放行 2 倍量) 最低 最簡單、要求不嚴格
Sliding Window 滑動視窗 滾動時間窗計算請求數,無固定重置點 消失 較高(存每筆時間戳) 通用、邊界精確
Token Bucket 令牌桶 固定速率放令牌、桶有上限、每請求耗一令牌 最符合真實場景
Leaky Bucket 漏桶 任意速率進、固定速率流出處理 嚴格控制輸出速率(呼叫外部 API)

Fixed Window 的邊界突發:客戶端可在第 59 秒發 100 個、第 61 秒再發 100 個 → 跨視窗的 2 秒內實際放行 200 個。

Token Bucket(最常用)            Leaky Bucket(強制平滑)
  令牌以固定速率滴入                  請求任意速率進
       ↓                                  ↓
  ┌─────────┐ 上限 100             ┌─────────┐
  │ ●●●●●●● │ ← 累積可應對突發     │ ▓▓▓▓▓▓▓ │
  └─────────┘                      └────┬────┘
   每請求耗 1 令牌,空了就拒              ↓ 固定速率流出(不允許突發)
面試預設選 Token Bucket

它允許「閒置時累積令牌、突發時一次放行」,最貼近真實流量。Leaky Bucket 反而強制平滑、不允許突發,只在你需要嚴格保護下游(如限制對第三方 API 的呼叫速率)時才選。

分散式 Rate Limiting

單機計數很簡單,但多台伺服器各自計數就失效——客戶端把請求分散打到不同機器就能繞過限制。

解法:集中式計數器(通常用 Redis)
  Server 1 ┐
  Server 2 ┼──→ Redis(INCR + EXPIRE / Lua script 原子操作)
  Server 3 ┘        ↑ 保證計數無 race condition

被限流時的正確回應

回傳 429 Too Many Requests,並用 header 告知客戶端何時可重試:

HTTP/1.1 429 Too Many Requests
Retry-After: 30
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1700000060

這讓客戶端能配合退避(backoff),而不是盲目重試加重伺服器壓力。詳見 01-Networking/07-Fault-Handling


Concurrency Limiting(並發限制)

Rate limiting ≠ Concurrency limiting,兩者管的維度不同

  • Rate limiting 管的是速率(每秒幾個請求)。
  • Concurrency limiting 管的是同時在處理中的請求數量。

假設每個請求平均 500ms。系統有 100 條執行緒 → 安全並發上限就是 100。Rate limiting 說「每秒最多 200 個」,但若每個請求要花 1 秒,同時就有 200 個在跑,遠超執行緒池容量 → 雪崩。Concurrency limiting 更直接對應系統資源。

實作上常用 Semaphore:取得失敗(已達上限)立刻拋 OverloadError,而不是排隊等待。兩者通常一起用:rate limiting 擋突發流量,concurrency limiting 保護內部資源不被耗盡。

Adaptive Concurrency Limiting(Netflix 開源)

根據延遲動態調整並發上限:延遲升高(系統開始過載)→ 自動降低並發數;延遲下降 → 逐步放寬。比靜態寫死的值更能適應真實狀況。本質與 Adaptive Auto-scaling 的回授思路一致。


Queue-based Load Leveling(佇列削峰)

Rate / Concurrency limiting 都是拒絕多餘請求。但有時你不想拒絕,只是需要時間處理

用戶請求 → API → 訊息佇列(Kafka / SQS)→ 工作服務(以自己能承受的速率慢慢處理)
                      ↑ 吸收瞬間尖峰        ↑ 後端看到的是平滑流量

非同步任務特別有效:影片轉碼、Email 發送、報表生成、訂單確認——這些不需要在用戶等待的幾秒內完成。

維度 說明
效果 佇列把尖峰吸收進去,後端只看到自己能承受的平滑流量
代價 處理從同步變非同步,用戶拿到「已收到,處理中」而非即時結果
適用界線 影片上傳等幾分鐘轉碼 OK;用戶付款後等 30 秒才收確認不合理
監控訊號 佇列深度持續增長 = 消費跟不上生產 → auto-scaling 的觸發訊號

詳見 09-Messaging-Coord/02-Queue10-Design-Patterns/03-Long-Running-Tasks


Auto-scaling(自動擴縮容)

前面手段都是在現有容量內管理流量;Auto-scaling 從根本上增加容量,需求增加時橫向擴展、減少時縮回省成本。

觸發指標對照

指標 準確度 說明
CPU 使用率 最常見(>70% 擴容),但 I/O 密集服務 CPU 低卻可能已過載
RPS 直接反映流量
佇列深度 消費者服務根據積壓量決定要不要加機器
P99 延遲 最高 最能反映用戶體驗——P99 超閾值代表系統開始承壓

Reactive vs Predictive

Reactive(反應式):流量來了 → CPU 升高 → 才觸發擴容
   ⚠ 啟動新實例 + 健康檢查 + 加入 LB 池 需要 1~3 分鐘
     這段空窗期過載仍在 → 需要 load shedding 撐住

Predictive(預測式):根據歷史模式提前擴
   電商知道每天 9 點流量上來 → 8:50 就開始擴(AWS/GCP 提供 ML 預測)

最佳實務:Predictive 處理已知模式 + Reactive 處理預期外突發
擴容要快,縮容要慢

縮容太激進,一個小流量回升又觸發擴容 → 振盪(oscillation)。典型設定:擴容條件觸發 1 分鐘後執行,縮容需持續 10 分鐘才執行

延伸:02-Distributed-Systems/03-Scalability11-Infrastructure/03-Container(K8s HPA)。


Load Shedding(負載卸除)

當 auto-scaling 來不及、佇列也有深度上限、rate limiting 只擋外部攻擊——這些都跟不上時,最後防線是主動丟棄部分請求,保護核心功能不崩潰。

選項 A:處理 30% 的請求,其他 70% 快速失敗(明確錯誤)  ← Load Shedding 選這個
選項 B:試圖處理 100%,結果系統崩潰,100% 全失敗

部分請求快速失敗,遠比讓所有請求一起慢死要好。

在哪裡做 Load Shedding

位置 成本 決策資訊 取捨
入口(API Gateway / LB) 最低(不碰後端) 少(只有請求速率、連線數) 便宜但不精確
服務內部 較高(請求已進來) 多(CPU、記憶體、延遲) 處理前先檢查健康,超閾值立刻回 503、不跑業務邏輯

服務內部典型門檻:CPU > 85%Memory > 90% → 回傳 503 Service Unavailable

該丟棄什麼請求(不是隨機丟)

策略 理由
重試請求優先丟 X-Retry-Count 代表已讓系統處理過,這次過載可能就是它造成的;保護第一次嘗試的新請求
低優先級請求先丟 分析請求、監控上報、非關鍵推薦,比用戶核心操作可更快丟
讀取可比寫入更早丟(視場景) 讀取有快取保底;寫入一旦失敗可能有資料遺失風險

Request Prioritization(請求優先級)

Load shedding 決定「要不要丟」,request prioritization 決定「丟誰」。

過載時不是所有請求平等:

優先級 請求類型 理由
最高 付費用戶的核心操作 直接影響收入與 SLA
一般用戶的核心操作 業務關鍵功能
非核心功能(推薦、搜尋) 可降級或跳過
後台任務、分析請求 可延遲處理
最低 監控、日誌上報 不影響用戶

實作:請求進入服務時,依用戶身份 / 請求類型打上優先級標記,過載時低優先級先被丟(如用 priority queue,超容量時丟棄堆中優先級最低者)。


Bulkhead(艙壁隔離)

名字來自輪船設計:把船艙分成多個隔艙,一個艙進水不會讓整艘船沉沒。在系統設計裡,把不同工作負載 / 租戶 / 服務依賴隔離到獨立資源池,確保一部分過載不會耗盡整個系統。

三種常見實現

① 執行緒池隔離
   ┌── Thread Pool A(DB 操作,40 條)──┐
   ┌── Thread Pool B(支付 API,10 條)─┐
   支付 API 變慢 → 只耗盡 Pool B 的 10 條 → DB 的 Pool A 完全不受影響
   (沒有 bulkhead:支付的慢會吃光「共用」執行緒池,讓 DB 操作也跟著超時)
   → Netflix Hystrix 即以執行緒池隔離 + 熔斷器為核心
實現 邊界 例子
執行緒池 每個下游依賴一個池 支付池掛掉不影響 DB 池
租戶隔離 大客戶獨立、小客戶共用 + concurrency limit 多租戶 SaaS:大企業獨立實例 / 分片,小客戶共用但限並發
連線池 讀寫分離不同池 read_pool=80(replica)/ write_pool=20(primary),大量讀不耗盡寫連線
Bulkhead 粒度怎麼定

太細 → 管理成本高、資源浪費(一池閒著另一池排隊);太粗 → 隔離沒意義。依「故障影響的相關性」劃分:把「如果它掛掉,你希望不影響哪些功能」的依賴隔離成獨立 bulkhead(支付掛掉不該影響商品瀏覽)。從 3~5 個 bulkhead 起步,再依實際故障模式調整。

連線池隔離與讀寫分離詳見 05-Database-Advanced/03-Replication10-Design-Patterns/01-Scaling-Reads


Backpressure(背壓)

前面所有機制都是承壓的那一側自己做決定。Backpressure 不同:它讓下游主動告知上游「我撐不住了,慢一點」,讓壓力訊號沿鏈路向上傳播。

無 backpressure:上游不斷打向下游 → 佇列無限增長 → 崩潰
有 backpressure:下游發訊號 → 上游主動放慢 → 整條鏈路協同
層級 機制
TCP 接收緩衝區(receive window)快滿 → 通知發送方縮小傳送視窗 → 自動放慢(網路層背壓)
gRPC / HTTP-2 flow control:每個 stream 與 connection 都有流量控制視窗;接收方可暫停接收,客戶端自然放慢;streaming 中服務端用處理速度自然施加背壓
應用層 服務端回 429 + Retry-After,或依佇列深度超閾值主動回 busy,讓客戶端退避
Backpressure vs Load Shedding——常考的區別

  • Load shedding:直接丟棄請求。
  • Backpressure不丟棄任何東西,只是讓上游放慢、整條管線流速降下來。

Backpressure 更溫和,但需要上游配合(支援背壓訊號),不是所有情況都能用。TCP/UDP 對比見 01-Networking/02-TCP-vs-UDP


面試話術

主動說明過載策略是加分項

設計任何大流量系統時,主動講多層保護:「我們在 API Gateway 做 rate limiting(每用戶每分鐘 1000 個,Redis + 滑動視窗)。核心訂單服務設 concurrency limiting(最多 50 並發,超過立刻回 503 讓客戶端退避)。不需即時的任務(通知、報表)放進 SQS 讓後端慢慢消費,即使尖峰後端也不崩。」

常見情境的標準答法:


常見 Deep Dive


自我測驗重點

問題 重點
過載不保護會怎樣 雪崩——不只超量部分失敗,所有請求都失敗
四種限流演算法 Fixed/Sliding/Token Bucket/Leaky;面試預設 Token Bucket
Fixed Window 缺點 邊界突發(跨視窗 2 秒可放行 2 倍)
Rate vs Concurrency limiting 速率(每秒幾個) vs 同時處理中的數量
分散式 rate limiting 集中式 Redis 計數器(INCR+EXPIRE / Lua 原子操作)
被限流回什麼 429 + Retry-After,讓客戶端退避
Queue load leveling 代價 同步變非同步;付款等 30 秒不合理、轉碼等幾分鐘 OK
Auto-scaling 指標 / 節奏 P99 最準;擴容快、縮容慢防振盪
Reactive 空窗怎麼撐 預測性擴容 + load shedding + 佇列緩衝
Load shedding 丟誰 重試請求 / 低優先級 / 讀取(視場景)先丟
Shedding vs Prioritization 要不要丟 vs 丟誰
Bulkhead 分艙隔離;執行緒池 / 租戶 / 連線池;依故障相關性定粒度
Backpressure vs Load shedding 放慢不丟 vs 直接丟;背壓需上游配合(TCP / HTTP-2 flow control)