設想這樣一種情況:某個行為異常的用戶端每秒送出 1,000 個請求,後端迅速飽和;下游服務失去回應;佇列不斷堆積,最終引發連鎖故障。你該如何保護 API 閘道,使其免受流量高峰與服務故障的衝擊?兩種經過驗證的模式可以提供幫助:速率限制與斷路器。GitHub 將已驗證 API 使用者限制為每小時 5,000 次請求。斷路器則會在服務發生故障時停止繼續向其送出請求。當用戶端超出限制時,伺服器會回傳 HTTP 429。本指南將介紹如何使用權杖桶演算法和 Redis 計數器來設定速率限制,並實作一個具備 Closed、Open 與 Half-Open 三種狀態的斷路器。讀完後,你將能夠透過 YAML 或程式碼在閘道上設定這兩種策略,防止系統過載並實現優雅復原。

速率限制與斷路器的基礎原理

速率限制和斷路器解決的是不同的問題,而一個具備韌性的 API 閘道通常兩者都需要。速率限制控制進入系統的流量規模;斷路器則決定請求是否還應該到達已經出現故障的相依服務。下表展示了兩者的核心差異。

面向速率限制斷路器
主要目標控制進入系統的請求量防止下游相依故障帶來更大影響
作用方向入站(用戶端到 API)出站(API 到下游服務)
觸發條件請求數超過門檻下游失敗率超過門檻
典型回應回傳帶有重試提示的 HTTP 429立即失敗或回傳降級結果
狀態模型無狀態:基於計數器與時間視窗有狀態:Closed、Open、Half-Open

何時使用速率限制

當某個用戶端可能搶占其他用戶端資源時,就應該使用速率限制。一個擁有大量使用者的公開 API 需要公平存取,因此你可以在時間視窗內限制每個用戶端的請求數。同樣的邏輯也適用於保護登入介面免受暴力破解,以及阻止爬蟲耗盡你的系統容量。

你還應透過設定速率限制來控制成本。突發的使用高峰可能會迅速消耗基礎設施預算。根據 API key、OAuth 權杖或使用方案設定限制,可以讓你為免費方案提供較低上限,為付費方案提供更高吞吐能力。這樣可以讓佇列保持穩定,也讓所有使用者的延遲更可預測。

何時使用斷路器

當下游服務開始失敗時,就該啟用斷路器模式。呼叫外部 ML API、支付處理服務或庫存服務時,都可能發生逾時並長時間占用執行緒。如果沒有保護機制,每個請求都會一直等待直到逾時,連線池被耗盡,故障也會在微服務之間擴散。

斷路器模式會監控失敗率升高、慢呼叫和逾時等現象。一旦失敗超過門檻,斷路器就會切換到 Open 狀態,並立即回傳降級回應。經過一段冷卻時間後,它會進入 Half-Open 狀態,並以有限請求測試服務是否恢復。這個狀態機既能隔離故障,也能支援自動復原。在微服務架構中,只要某個不穩定相依可能引發連鎖故障,就應考慮使用它。

在 API 閘道上設定速率限制

現實中的 API 已經展示了速率限制的典型方式。GitHub 將已驗證使用者限制為每小時 5,000 次請求。這些數值反映了各平台自身的容量與公平性目標。你也可以在自己的 API 閘道上用類似邏輯設定速率限制。閘道會統計傳入請求,一旦某個用戶端超過門檻,就拒絕其額外請求。

Redis 常被用於儲存這些請求計數器。集中式儲存可以讓多個閘道實例之間保持計數一致。當用戶端超過限制時,閘道會回傳 HTTP 429 Too Many Requests。回應中還可以包含 X-RateLimit-LimitX-RateLimit-RemainingX-RateLimit-Reset 等標頭資訊,用於告知用戶端何時可以重試。這種方式既適用於針對單一路由的限制,也適用於跨所有路由的全域限制。

如何在權杖桶與漏桶之間做選擇

在速率限制設計中,最常見的是兩種演算法,它們對流量的整形方式各不相同。下表給出了比較。

演算法突發流量處理能力最佳使用情境
權杖桶允許在桶容量範圍內出現可控突發面向開發者的 API,允許偶發突發流量
漏桶嚴格控制輸出速率,不允許突發需要平滑、均勻流量的脆弱下游系統

權杖桶的參數包括權杖填充速率和桶容量。填充速率表示每秒新增多少權杖,桶容量表示桶中最多可存放多少權杖。你需要根據後端對突發流量的承受能力來選擇這些值。

如果用戶端需要偶爾突發存取,選擇權杖桶更合適;如果下游服務要求穩定、可預測的流量,漏桶更適合。如果流量本身具有突發性,權杖桶或滑動視窗計數器通常效果更好;如果流量較均勻,或者你需要嚴格限制輸出速率,那麼漏桶是更優選擇。

使用 YAML 設定單一路由與全域限制

你可以依路由定義速率限制,也可以在整個閘道範圍內定義全域限制。依路由的限制只作用於特定端點;全域限制則對閘道中的所有路由設定統一上限。Redis 作為共享計數儲存,用於在分散式環境中統一執行限制。

下面是一個引用 Redis 的 RequestRateLimiter 過濾器 YAML 設定範例:

spring:
  cloud:
    gateway:
      routes:
      - id: auditflow_service_route
        uri: http://localhost:8080
        predicates:
        - Path=/api/v1/audit/**
        filters:
        - name: RequestRateLimiter
          args:
            key-resolver: "#{@ipAddressKeyResolver}"
            redis-rate-limiter.replenishRate: 10
            redis-rate-limiter.burstCapacity: 20
            redis-rate-limiter.requestedTokens: 1

replenishRate 用於設定每秒補充多少權杖;burstCapacity 定義可用權杖的最大數量;requestedTokens 表示每個請求會消耗多少權杖。key-resolver 則決定限流器基於哪個識別進行統計,例如 IP 位址或使用者 ID。

這段設定將限制套用到 /api/v1/audit/** 路徑。你可以將類似過濾器加入其他路由,並設定不同的值。全域限制則會使用跨所有路由共享的 key。當超出限制時,閘道會回傳帶有 Retry-After 標頭的 HTTP 429,明確告知用戶端何時再次嘗試。

斷路器模式是速率限制的互補機制,它處理的是服務故障,而不是請求量。速率限制控制進入系統的請求數;斷路器則阻止請求繼續到達已故障的相依服務。兩者配合,才能同時防範流量高峰與連鎖故障。

在 API 閘道上實作斷路器

現在開始實作斷路器模式。它會阻止請求繼續送往故障中的下游服務。你的 API 閘道會執行一個入站策略來檢查斷路器狀態,也會執行一個出站策略來更新該狀態。這個狀態機會在三種狀態之間切換:closed、open 和 half-open。它與速率限制形成互補:速率限制在入口處控制請求量;斷路器則在呼叫鏈中段阻斷流量,但前提是某個服務已經發生故障。

失敗門檻與冷卻時間

斷路器依賴多個關鍵參數。失敗門檻定義了連續失敗多少次後切換到 open 狀態。為了便於快速測試,你可以把該值設為 3;在正式環境中,5 次連續失敗通常是較平衡的選擇。這個設定既能避免因為短暫網路抖動而誤判,也能在真正故障出現時及時偵測。逾時時間,或者說冷卻時間,決定斷路器在 open 狀態停留多久後才嘗試恢復。正式環境中 60 秒的冷卻時間通常比較合適——足夠長,可以讓大多數服務供應商故障得到恢復;又足夠短,不至於在短暫異常之後長時間阻塞流量。如果恢復嘗試失敗,retry time period 會重新開始計時。

在斷路器設定中,如果門檻設得過低,就容易出現誤報。斷路器會因正常的小波動而被觸發,而這些問題原本會自行恢復;如果門檻設得過高,又會導致偵測過慢,系統在受到真實損害後才開始保護。因此,你必須根據各自專案的容錯能力來設定這些值。

利用 Half-Open 狀態進行健康恢復

當逾時計時器到期後,斷路器會切換到 half-open 狀態。在這個狀態下,斷路器只允許少量探測請求送往下游服務,目的是測試問題是否已經解決。如果這些探測請求全部成功,斷路器就會回到 closed 狀態,系統恢復正常運作,同時失敗計數器也會重設;如果其中任意一個探測請求失敗,斷路器就會立即重新回到 open 狀態,並重新啟動逾時計時器。這種方式可以避免「驚群效應」,否則一個尚未完全恢復的服務可能突然再次收到大量請求。

half-open 的逾時設定需要格外謹慎。如果 retry time period 設定得過短,你可能會反覆衝擊尚未恢復的服務;如果設定得過長,又會無謂地阻塞本可正常通過的流量。測試時的最佳實務是從保守值開始,例如使用滾動視窗統計失敗次數,如 60 秒內失敗 5 次,再根據實際觀察逐步調整。

下表總結了狀態機的轉換邏輯。

狀態轉換觸發條件相關參數
Closed 到 Open滾動視窗內的失敗次數或失敗比例超過門檻failureThreshold、requestVolumeThreshold
Open 到 Half-Open逾時計時器到期Timeout 或 cooldown period
Half-Open 到 Closed所有探測請求均成功successThreshold
Half-Open 到 Open任意一個探測請求失敗successThreshold

斷路器與速率限制是協同運作的。速率限制在最前端控制輸入流量;斷路器則在呼叫鏈中切斷故障傳播。當斷路器處於 open 狀態時,不要繼續重試,否則只會導致重試風暴和更嚴重的連鎖故障。

結合兩種模式打造高韌性 API

執行順序與策略優先權

現在你已經擁有兩種策略,而它們的執行順序非常關鍵。速率限制應先在閘道層執行,這樣可以在請求占用任何下游資源之前,就先攔截過度活躍的用戶端。斷路器則應在呼叫鏈更後面的位置執行,用於阻止請求繼續送往已知故障中的服務。

下表說明了每種模式如何為系統韌性做出貢獻,以及為什麼這樣的順序更合理。

模式主要韌性作用為什麼必須與另一種模式配合使用
速率限制依消費者、IP 或 API key 限制請求速率應先於重試執行,這樣重試不會消耗限流配額
斷路器打開斷路以阻止重試風暴重試應發生在斷路器內部;如果斷路器已打開,繼續重試毫無意義
組合順序速率限制,然後重試,然後斷路器確保重試不會耗盡配額,也避免持續衝擊故障服務

實際落地時可以遵循一個清晰的順序:先加入速率限制,因為它是最簡單、效益也最大的模式;然後為每個後端連線設定合適的逾時時間;接著為關鍵或高故障風險的後端實作斷路器;再為讀多寫少的端點增加快取降級;最後藉由閘道可觀測性資料持續監控與迭代最佳化。

透過可觀測性支援漸進式恢復

你無法最佳化看不見的問題。應持續追蹤每個相依的重試次數,觀察斷路器打開事件及其持續時間,統計因限流而被拒絕或延遲的請求數量,測量平均回應時間、P95 與 P99 延遲,監控每個端點及每個建置版本的錯誤率,比較每分鐘請求數與 429 回應數,記錄每個服務的斷路器打開事件,並在某個特定相依的重試次數突然上升時發出警示。

開始時應保持保守。設定較低的限制值和較低的失敗門檻,先觀察系統行為再逐步調整。透過測量峰值連線數、請求量與錯誤率來建立基準,再把初始門檻設為峰值的兩倍,並採用適度的異常偵測。持續監控一到兩週內的溢出與驅逐情況:如果正常流量下沒有發生溢出,可適度收緊門檻;如果合法請求被拒絕,則應適度放寬。在預發佈環境中執行壓力測試,以驗證系統在模擬過載情境下的保護能力。當流量模式或服務容量發生變化時,應重複這一最佳化週期。

速率限制控制請求量,斷路器處理服務故障。兩者結合,才能建構一個高韌性的 API 閘道。現在,你已經掌握了這兩項關鍵能力。

門檻應依據壓力測試結果和故障目標來設定,而不是憑感覺猜測。只有內部限制,才能真正定義你的系統究竟能承受多少負載。

從保守值開始,觀察可觀測性資料,再隨著真實流量模式逐步調校。斷路器獎勵的是耐心,而不是激進。

今天就檢查你的閘道設定吧。透過 YAML 或程式碼套用這些模式。下一次流量高峰或服務故障不會等你準備好。趁它到來之前,先把你的請求處理機制建立完善。

常見問題

速率限制應該先於斷路器執行嗎?

是的。應先在閘道層執行速率限制,這樣可以在 API 呼叫占用下游容量之前,先限制高頻用戶端。斷路器則在呼叫鏈後續階段執行。這樣的順序可以防止重試消耗你的限流配額,也能避免故障服務繼續承受更多負載。

我該如何為斷路器選擇失敗門檻?

可以從 5 次連續失敗和 60 秒冷卻時間開始。觀察錯誤率和佇列深度:如果感覺偵測太慢,就降低門檻;如果正常小波動就會觸發斷路器,就提高門檻。請透過壓力測試來調校,而不是憑直覺決定。

HTTP 429 對用戶端意味著什麼?

HTTP 429 表示用戶端送出了過多請求。閘道會拒絕超出的請求,並回傳 Retry-After 標頭。你應該在文件中說明這個回應標頭的含義,這樣用戶端就會選擇退避重試,而不是繼續衝擊你的端點。

我真的需要同時使用這兩種模式嗎?

需要。速率限制控制入站流量上限;斷路器隔離故障相依。前者決定有多少流量進入系統,後者決定這些流量是否應該繼續到達已故障的服務。兩者結合,才能同時防止過載與連鎖故障。