Lesson 26 : 系統韌性的守護者-限流、熔斷與背壓的設計模式
當這幾個名詞出現後,代表我們進到了一個高併發/大流量的系統了。在這個章節中,我們一起來看看如何透過一些方式來避免高併發導致我們的系統crash掉。
限流(Rate Limiting)
相信大家對這個名詞並不陌生,限流其實就是字面上的含意,限制流量。
限流的目的是保護「接收方」,確保系統不會因為瞬間的高併發請求而癱瘓。
限流通常發生在 API Gateway 或服務的最前端。它像是一個夜店門口的保全,控制每分鐘可以進場的人數,當超過限制時,通常回傳 HTTP 429 Too Many Requests
常見演算法
令牌桶 (Token Bucket): 系統以固定速率產生令牌,並存於有限容量的桶中,請求需取得令牌才能通過,允許一定程度的突發流量
漏桶 (Leaking Bucket): 無論請求多快,輸出速率永遠固定。主要用於平滑流量。
固定/滑動視窗: 計算一段時間內的請求總數,簡單但可能在視窗切換點出現兩倍流量。
| 演算法 | 核心邏輯 | 優點 | 缺點 | 適用場景 |
|---|---|---|---|---|
| 令牌桶 (Token Bucket) | 桶子存令牌,有令牌就放行。 | 允許突發流量 (Burst),靈活性高。 | 實作稍微複雜一點點。 | Google API、大多數微服務 Gateway。 |
| 漏桶 (Leaking Bucket) | 請求進桶子,固定速率流出。 | 絕對平滑,強行保護後端穩定。 | 沒辦法處理突發需求,請求會排隊很久。 | 資料庫寫入保護、電信信令控制。 |
| 固定視窗 (Fixed Window) | 每分鐘重算一次。 | 實作最簡單,記憶體佔用極低。 | 臨界點問題:例如 0:59 進 100 個,1:01 進 100 個,瞬間其實進了 200 個。 | 簡單的 API 配額限制。 |
| 滑動視窗 (Sliding Window) | 視窗隨時間滾動。 | 解決了臨界點翻倍的問題。 | 儲存每個請求的時間點,較耗記憶體。 | 較精細的用戶級別限流。 |
關於平滑流量
如果一個系統每秒只能處理 10 個請求:
非平滑狀態: 第 1 秒突然衝進來 50 個請求,系統瞬間崩潰或發生延遲,而後面的 4 秒卻空空如也。
平滑後(漏桶算法): 這 50 個請求會先進入一個「桶子」排隊,系統以每秒 10 個的固定速度緩慢釋放。雖然請求處理完畢的總時間變長了,但後端系統始終保持在負載範圍內,不會被打掛。
補充
在Java Web 開發中,tomcat的預設值是200個connection,Web Server 透過執行緒池 (Thread Pool) 來實現流量控制。
然而在PHP-FPM 的流量控制邏輯是透過一個主進程管理多個子進程(Worker Processes)在排隊處理任務,並透過 pm.max_children 旯管理最大子進程數量。
實務上, PHP-FPM (pm.max_children) 與 Tomcat (maxThreads) 本質上是併發數限制 + 請求排隊機制,當 Worker 滿了,請求進入作業系統的 Backlog 佇列,如果連佇列都滿了,就會回傳錯誤。
PHP的限流
在 PHP 的世界裡,我們通常不會單靠 PHP-FPM 來擋流量,而是會配合:
Nginx
limit_req:在最前端就限制每秒請求數。Opcache:減少編譯時間,讓「漏水」的速度變快。
Queue (Laravel Job):遇到大流量時,把耗時任務丟到後台處理,讓 PHP-FPM 趕快空出來接下一個請求。
熔斷 (Circuit Breaker)
保護「呼叫方」,防止故障蔓延。通常運用在Server to Server的系統中,防止服務雪崩。
情境
當 A 服務呼叫 B 服務,而 B 服務反應過慢或持續報錯時,A 服務應該「切斷」對 B 的請求,直接回傳預設的錯誤(Fallback),而不是讓執行緒(Thread)全部卡在那裡等待超時。
熔斷的三種狀態
| 狀態 | 表現行為 | 轉換條件 |
|---|---|---|
| Closed | 流量正常通過。 | 錯誤率低於門檻。 |
| Open | 請求直接攔截,執行 Fallback。 | 錯誤率或超時率超過設定值(如 50%)。 |
| Half-Open | 放行少量請求測試。 | 經過一段「冷卻時間」後自動進入。 |
實作補充
如果是Java的開發者,可以使用:Resilience4j
而Laravel的開發者,可以使用:laravel-circuit-breaker
背壓 (Backpressure)
協調「生產者」與「消費者」的速度差異。
在非同步系統(如 Queue)中,當生產者發送數據的速度快過消費者處理的速度時,如果沒有背壓,消費者的Buffer會溢位導致 Out of Memory。
背壓不僅存在於 Queue,也廣泛應用於網路層(TCP Flow Control)
常見策略
控制速率 (Signaling): 消費者主動告訴生產者:「我現在只能處理 10 個,別傳太快。」
緩衝 (Buffer): 先存在記憶體,但有上限。
丟棄 (Drop): 處理不完就直接丟掉最新的(或最舊的)。
總結
| 模式 | 誰保護誰 | 觸發時機 | 典型案例 |
|---|---|---|---|
| 限流 | 伺服器保護自己 | 請求量超過預設閾值 | 阻擋惡意爬蟲、API 配額限制 |
| 熔斷 | 呼叫方保護自己與系統 | 依賴的外部服務不穩定時 | 第三方支付 API 回應超時 |
| 背壓 | 消費者保護自己 | 處理速度跟不上輸入速度時 | 數據流處理 (Data Streaming) |
限流(Rate Limiting):發生在入口(Ingress),保護系統 熔斷(Circuit Breaker):發生在服務間(Service-to-Service),防止雪崩 背壓(Backpressure):發生在非同步系統(Async Pipeline),避免資源耗盡

