在 API 閘道中設定速率限制與斷路器

設想這樣一種情況:某個行為異常的用戶端每秒送出 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-Limit、X-RateLimit-Remaining 和 X-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: 1replenishRate 用於設定每秒補充多少權杖;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 標頭。你應該在文件中說明這個回應標頭的含義,這樣用戶端就會選擇退避重試,而不是繼續衝擊你的端點。
我真的需要同時使用這兩種模式嗎?
需要。速率限制控制入站流量上限;斷路器隔離故障相依。前者決定有多少流量進入系統,後者決定這些流量是否應該繼續到達已故障的服務。兩者結合,才能同時防止過載與連鎖故障。
