分佈式系統架構5:限流設計模式
這是小卷對分佈式系統架構學習的第5篇文章,今天來學習限流器和限流設計模式
1.為什麼要限流?
任何一個系統的運算、存儲、網絡資源都不是無限的,當系統資源不足以支撐外部超過預期的突發流量時,就應該要有取捨,建立面對超額流量自我保護的機制,而這個機制就是微服務中常説的“限流”
2.四種限流設計模式
説到限流,大家直接的想法就是Sentinel,但是Sentinel限流的原理可能很多人沒去深入理解,或者限流到底是怎麼做的?具體如何進行限流,業界內也有一些常見設計模式。
2.1流量計數器模式
流量計數器是一種最簡單的限流方式,通過記錄固定時間窗口內的請求次數來判斷是否達到限流閾值。如果請求次數超過限制值,則拒絕後續請求。
實現方式:
- 將時間劃分為固定的時間窗口(如 1 秒、1 分鐘)。
- 每個窗口維護一個計數器,記錄當前時間窗口內的請求次數。
- 如果計數器值超過限流閾值,直接拒絕請求;否則增加計數器。
固定窗口邊界問題:
- 在窗口邊界的兩端,可能存在短時間內超量請求的“臨界問題”
比如場景設定:一秒內的TPS大於80時,就限流。
存在問題:即使每一秒的統計流量都沒有超過 80 TPS,也不能説明系統沒有遇到過大於 80 TPS 的流量壓力。比如説系統在連續2秒內都收到60TPS的請求,但是請求發生的時間分別在第1秒的後0.5秒,以及第2秒的前0.5秒。這樣系統實際曾在1秒內發生超過80 TPS的請求。
- 即使連續若干秒統計流量超過閾值,也不能説明流量壓力一定超過系統承受能力
假設 10 秒的時間片段中,前 3 秒的 TPS 平均值到了 100,而後 7 秒的平均值是 30 左右,此時系統是否能夠處理完這些請求而不產生超時失敗?答案是可以的
存在缺陷:造成上面2個問題得原因是流量計數器模式是對時間點進行離散的統計
2.2滑動窗口模式
概念:時間軸上,一個固定大小的窗口隨時間平滑滾動。任何時刻,靜態地通過窗口內觀察到的信息,都等價於一段長度與窗口大小相等的信息。主要是通過記錄多個較小時間窗口(子窗口)的請求次數,實現更精細化的限流控制。
假設:準備觀察的時間片段為 10 秒,以 1 秒作為統計精度,那可以得到一個長度為 10 的數組。設定限流閾值是最近 10 秒內收到的請求不超過 500 個,那麼就需要統計10個子數組的請求總數,是否超過閾值。
優點:
- 解決了固定窗口邊界問題
缺點:
- 只適用於否決式限流,超過閾值的流量就必須失敗
2.3漏桶模式
漏桶可以簡單的理解:小學水池應用題,一個水池,每秒以 X 升速度注水,同時又以 Y 升速度出水,問水池啥時候裝滿。
概念:將請求視為流入漏桶的水,漏桶以固定速率“漏水”。當請求流量超過漏桶的處理能力時,多餘的請求會被丟棄或排隊。其核心思想是平滑請求流量
實現方式:
- 維護一個隊列(或計數器),用來模擬漏桶。
- 新請求到來時,將請求放入桶中。
- 按固定速率處理桶中的請求。
- 如果桶已滿,則拒絕新請求。
缺點:
- 比較難確定桶的大小和水流出的速度
2.4令牌桶算法
和漏桶一樣是基於緩衝區的限流算法,簡單理解就是去銀行辦事時在排隊機號取號的場景。
概念:通過固定速率向桶中添加令牌,請求到來時需要先消耗令牌才能被處理。如果桶中沒有足夠的令牌,請求會被拒絕。與漏桶算法不同,令牌桶允許一定的突發流量。
實現方式
- 維護一個桶,桶中存儲令牌。
- 按固定速率(比如限流是1秒100次請求,那麼間隔10ms時間放入令牌)向桶中添加令牌,直到桶滿為止。
- 請求到來時從桶中取出令牌,如果沒有令牌就馬上失敗或者進入降級邏輯。
實際開發的時候,不需要專門做放令牌到桶裏這件事,只需要在獲取令牌前,比較一下時間戳與當前時間,就能算出需要放入多少令牌,下面是示例代碼:
private long lastTime = System.currentTimeMillis();
private int tokens = 0; // 當前令牌數
private static final int LIMIT = 100; // 桶容量
private static final int REFILL_RATE = 10; // 令牌添加速率(令牌/秒)
public synchronized boolean tryAcquire() {
long now = System.currentTimeMillis();
// 添加令牌
tokens = Math.min(LIMIT, tokens + (int) ((now - lastTime) / 1000) * REFILL_RATE);
lastTime = now;
if (tokens > 0) {
tokens--;
return true;
}
return false;
}
3.分佈式限流
上面介紹的4種限流算法都只適用於單機限流,或者把系統當做整體來限流。實際應用中仍然需要精細的每個服務的限流。
概念:過將限流邏輯分散到多個節點,同時使用一致性算法保證全侷限流的一致性。它結合了本地限流和集中式限流的優點。
實現方式
-
基於 Redis + Lua 腳本
- 使用 Redis 腳本實現分佈式限流,在 Redis 中存儲全局的請求計數器
-
基於一致性算法
- 使用分佈式一致性算法(如 Raft、Paxos)維護全局流量狀態
-
分佈式網關
- 通過 API 網關(如 Kong、Nginx、Spring Cloud Gateway)實現流量的統一調度和限流。
缺點:
- 實現複雜度高,且網絡通信和一致性操作帶來額外延遲。當流量大時,限流本身會降低系統處理能力
總結
今天學習了4種限流設計模式:流量計數器模式、滑動窗口模式、漏桶模式、令牌桶模式,後面2種都是基於緩衝區的限流算法。簡單瞭解了下分佈式限流的概念。限流本身是有代價的,實際開發中需要權衡方案的代價和收益。後續有時間補充Sentinel的限流原理和其中用了哪些設計模式。