# Lesson 26 : 系統韌性的守護者-限流、熔斷與背壓的設計模式

當這幾個名詞出現後，代表我們進到了一個高併發/大流量的系統了。在這個章節中，我們一起來看看如何透過一些方式來避免高併發導致我們的系統crash掉。

# 限流(Rate Limiting)

相信大家對這個名詞並不陌生，限流其實就是字面上的含意，限制流量。

限流的目的是保護「接收方」，確保系統不會因為瞬間的高併發請求而癱瘓。

限流通常發生在 API Gateway 或服務的最前端。它像是一個夜店門口的保全，控制每分鐘可以進場的人數，當超過限制時，通常回傳 `HTTP 429 Too Many Requests`

## 常見演算法

1.  令牌桶 (Token Bucket)： 系統以固定速率產生令牌，並存於有限容量的桶中，請求需取得令牌才能通過，允許一定程度的突發流量
    
2.  漏桶 (Leaking Bucket)： 無論請求多快，輸出速率永遠固定。主要用於平滑流量。
    
3.  固定/滑動視窗： 計算一段時間內的請求總數，簡單但可能在視窗切換點出現兩倍流量。
    

| 演算法 | 核心邏輯 | 優點 | 缺點 | 適用場景 |
| --- | --- | --- | --- | --- |
| 令牌桶 (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 來擋流量，而是會配合：

1.  Nginx `limit_req`：在最前端就限制每秒請求數。
    
2.  Opcache：減少編譯時間，讓「漏水」的速度變快。
    
3.  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](https://github.com/gabrielanhaia/laravel-circuit-breaker)

# 背壓 (Backpressure)

協調「生產者」與「消費者」的速度差異。

在非同步系統（如 Queue）中，當生產者發送數據的速度快過消費者處理的速度時，如果沒有背壓，消費者的Buffer會溢位導致 Out of Memory。

背壓不僅存在於 Queue，也廣泛應用於網路層(TCP Flow Control)

## 常見策略

1.  控制速率 (Signaling)： 消費者主動告訴生產者：「我現在只能處理 10 個，別傳太快。」
    
2.  緩衝 (Buffer)： 先存在記憶體，但有上限。
    
3.  丟棄 (Drop)： 處理不完就直接丟掉最新的（或最舊的）。
    

# 總結

| **模式** | **誰保護誰** | **觸發時機** | **典型案例** |
| --- | --- | --- | --- |
| **限流** | 伺服器保護自己 | 請求量超過預設閾值 | 阻擋惡意爬蟲、API 配額限制 |
| **熔斷** | 呼叫方保護自己與系統 | 依賴的外部服務不穩定時 | 第三方支付 API 回應超時 |
| **背壓** | 消費者保護自己 | 處理速度跟不上輸入速度時 | 數據流處理 (Data Streaming) |

限流（Rate Limiting）：發生在入口（Ingress），保護系統 熔斷（Circuit Breaker）：發生在服務間（Service-to-Service），防止雪崩 背壓（Backpressure）：發生在非同步系統（Async Pipeline），避免資源耗盡
