一、引言
再強大的系統,也怕流量短事件內集中爆發,就像銀行怕擠兌一樣,所以,高并發另一個必不可少的模塊就是限流。限流是一種通過控制請求的速率或數量來保護系統免受過載的技術。流控的精髓是限制單位時間內的請求量,最大程度保障系統的可靠性及可用性。
二、限流算法
限流是在高并發環境下,為了保護系統的穩定性和可用性而引入的一種策略。通過限制并發請求的數量或頻率,可以防止系統被過多的請求壓垮或耗盡資源。
常見的流控算法包括:固定窗口、滑動窗口、漏桶、令牌桶、滑動日志等算法。
2.1?固定窗口算法(計數器)
固定窗口限流算法(Fixed Window Rate Limiting Algorithm)是一種最簡單的限流算法,其原理是在固定時間窗口(單位時間)內限制請求的數量。
原理
固定窗口是最簡單的流控算法。即,給定時間窗口,維護一個計數器用于統計訪問次數,并實現以下規則:
- 如果訪問次數小于閾值,則允許訪問,訪問次數+1;
- 如果訪問次數超出閾值,則限制訪問,訪問次數不增;
- 如果超過了時間窗口,計數器清零,并重置清零后的首次成功訪問時間為當前時間。
適用場景
- 保護后端服務免受大流量沖擊,避免服務崩潰;
- 對 API 調用進行限制,保證公平使用;
- 防止惡意用戶對服務進行洪水攻擊;
實現方式
public class FixedWindowRateLimiter {private static int counter = 0; // 統計請求數private static long lastAcquireTime = 0L;private static final long windowUnit = 1000L; // 假設固定時間窗口是1000msprivate static final int threshold = 10; // 窗口閥值是10public synchronized boolean tryAcquire() {long currentTime = System.currentTimeMillis(); // 獲取系統當前時間if (currentTime - lastAcquireTime > windowUnit) { // 檢查是否在時間窗口內counter = 0; // 計數器清零lastAcquireTime = currentTime; // 開啟新的時間窗口}if (counter < threshold) { // 小于閥值counter++; // 計數器加1return true; // 獲取請求成功}return false; // 超過閥值,無法獲取請求}
}
使用了一個靜態的counter變量來記錄請求數量,lastAcquireTime變量記錄上一次獲取請求的時間戳。windowUnit表示固定時間窗口的長度,threshold則表示在時間窗口內的請求數閥值。
tryAcquire()方法使用了synchronized關鍵字來實現線程安全,在方法中進行以下操作:
- 獲取當前系統時間 currentTime。
- 檢查當前時間是否距離上一次獲取請求的時間超過了時間窗口長度 windowUnit。如果超過了時間窗口,則將計數器 counter 清零,并更新 lastAcquireTime 為當前時間,表示進入新的時間窗口。
- 如果計數器 counter 小于閥值 threshold,則將計數器加1,并返回true表示獲取請求成功。
- 如果計數器已經達到或超過閥值,則返回false表示無法獲取請求。
優劣分析
優點
- 固定窗口算法非常簡單,易于實現和理解。
- 性能高
缺點
- 存在明顯的臨界問題
- eg:比如: 假設限流閥值為5個請求,單位時間窗口是1s,如果我們在單位時間內的前0.8-1s和1-1.2s,分別并發5個請求。雖然都沒有超過閥值,但是如果算0.8-1.2s內的,則并發數高達10,已經超過單位時間1s不超過5閥值的定義了。
?2.2?滑動窗口算法
為了解決臨界突變問題,可以引入滑動窗口。即:把大的時間窗口拆分成若干粒度更細的子窗口,每個子窗口獨立統計,按子窗口時間滑動,統一限流。當滑動窗口的格子周期劃分的越多,那么滑動窗口的滾動就越平滑,限流的統計就會越精確。
原理
將單位時間周期分為n個小周期,分別記錄每個小周期內接口的訪問次數,并且根據時間滑動刪除過期的小周期。它可以解決固定窗口臨界值的問題。
假設單位時間還是1s,滑動窗口算法把它劃分為5個小周期,也就是滑動窗口(單位時間)被劃分為5個小格子。每格表示0.2s。每過0.2s,時間窗口就會往右滑動一格。然后呢,每個小周期,都有自己獨立的計數器,如果請求是0.83s到達的,0.8~1.0s對應的計數器就會加1。
假設我們1s內的限流閥值還是5個請求,0.8~1.0s內(比如0.9s的時候)來了5個請求,落在黃色格子里。
時間過了1.0s這個點之后,又來5個請求,落在紫色格子里。如果是固定窗口算法,是不會被限流的,但是滑動窗口的話,每過一個小周期,它會右移一個小格。過了1.0s這個點后,會右移一小格,當前的單位時間段是0.2~1.2s,這個區域的請求已經超過限定的5了,已觸發限流啦,實際上,紫色格子的請求都被拒絕。
實現方式
import java.util.LinkedList;
import java.util.Queue;
public class SlidingWindowRateLimiter {private Queue<Long> timestamps; // 存儲請求的時間戳隊列private int windowSize; // 窗口大小,即時間窗口內允許的請求數量private long windowDuration; // 窗口持續時間,單位:毫秒public SlidingWindowRateLimiter(int windowSize, long windowDuration) {this.windowSize = windowSize;this.windowDuration = windowDuration;this.timestamps = new LinkedList<>();}public synchronized boolean tryAcquire() {long currentTime = System.currentTimeMillis(); // 獲取當前時間戳// 刪除超過窗口持續時間的時間戳while (!timestamps.isEmpty() && currentTime - timestamps.peek() > windowDuration) {timestamps.poll();}if (timestamps.size() < windowSize) { // 判斷當前窗口內請求數是否小于窗口大小timestamps.offer(currentTime); // 將當前時間戳加入隊列return true; // 獲取請求成功}return false; // 超過窗口大小,無法獲取請求}
}
代碼解讀
在以上代碼中,使用了一個Queue(隊列)來存儲請求的時間戳。構造函數中傳入窗口大小 windowSize 和窗口持續時間 windowDuration。tryAcquire()方法使用了synchronized關鍵字來實現線程安全,在方法中進行以下操作:
- 獲取當前系統時間戳 currentTime。
- 從隊列中刪除超過窗口持續時間的時間戳,確保隊列中只保留窗口內的時間戳。
- 判斷當前窗口內請求數是否小于窗口大小 windowSize。
如果小于窗口大小,將當前時間戳加入隊列,并返回true表示獲取請求成功。如果已經達到或超過窗口大小,表示請求數已滿,返回false表示無法獲取請求。
使用這個滑動窗口限流算法,可以限制在一定時間窗口內的請求頻率,超過窗口大小的請求會被限制。您可以根據實際需求和業務場景進行調整和使用。
適用場景
同固定窗口的場景,且對流量限制要求較高的場景,需要更好地應對突發流量。
優劣分析
優勢
- 簡單易懂
- 精度高(通過調整時間窗口的大小來實現不同的限流效果)
- 可擴展性強(可以非常容易地與其他限流算法結合使用)
劣勢
- 突發流量無法處理(無法應對短時間內的大量請求,但是一旦到達限流后,請求都會直接暴力被拒絕。這樣我們會損失一部分請求,這其實對于產品來說,并不太友好),需要合理調整時間窗口大小。
2.3 漏桶算法
基于(出口)流速來做流控。在網絡通信中常用于流量整形,可以很好地解決平滑度問題。
特點
- 可以以任意速率流入水滴到漏桶(流入請求)
- 漏桶具有固定容量,出水速率是固定常量(流出請求)
- 如果流入水滴超出了桶的容量,則流入的水滴溢出(新請求被拒絕)
原理
思想:將數據包看作是水滴,漏桶看作是一個固定容量的水桶,數據包像水滴一樣從桶的頂部流入桶中,并通過桶底的一個小孔以一定的速度流出,從而限制了數據包的流量
工作原理:對于每個到來的數據包,都將其加入到漏桶中,并檢查漏桶中當前的水量是否超過了漏桶的容量。如果超過了容量,就將多余的數據包丟棄。如果漏桶中還有水,就以一定的速率從桶底輸出數據包,保證輸出的速率不超過預設的速率,從而達到限流的目的。
代碼實現
public class LeakyBucketRateLimiter {private long capacity; // 漏桶容量,即最大允許的請求數量private long rate; // 漏水速率,即每秒允許通過的請求數量private long water; // 漏桶當前水量private long lastTime; // 上一次請求通過的時間戳public LeakyBucketRateLimiter(long capacity, long rate) {this.capacity = capacity;this.rate = rate;this.water = 0;this.lastTime = System.currentTimeMillis();}public synchronized boolean tryAcquire() {long now = System.currentTimeMillis();long elapsedTime = now - lastTime;// 計算漏桶中的水量water = Math.max(0, water - elapsedTime * rate / 1000);if (water < capacity) { // 判斷漏桶中的水量是否小于容量water++; // 漏桶中的水量加1lastTime = now; // 更新上一次請求通過的時間戳return true; // 獲取請求成功}return false; // 漏桶已滿,無法獲取請求}
}
代碼解讀
在以上代碼中,capacity表示漏桶的容量,即最大允許的請求數量;rate表示漏水速率,即每秒允許通過的請求數量。water表示漏桶中當前的水量,lastTime表示上一次請求通過的時間戳。tryAcquire()方法使用了synchronized關鍵字來實現線程安全,在方法中進行以下操作:
- 獲取當前系統時間戳 now。
- 計算從上一次請求通過到當前的時間間隔 elapsedTime。
- 根據漏水速率和時間間隔,計算漏桶中的水量。
- 判斷漏桶中的水量是否小于容量。
如果小于容量,漏桶中的水量加1,更新上一次請求通過的時間戳,并返回true表示獲取請求成功。如果已經達到或超過容量,漏桶已滿,返回false表示無法獲取請求。
適用場景
一般用于保護第三方的系統,比如自身的系統需要調用第三方的接口,為了保護第三方的系統不被自身的調用打垮,便可以通過漏斗算法進行限流,保證自身的流量平穩的打到第三方的接口上。
優劣分析
優勢
- 可以平滑限制請求的處理速度,避免瞬間請求過多導致系統崩潰或者雪崩。
- 可以控制請求的處理速度,使得系統可以適應不同的流量需求,避免過載或者過度閑置。
- 可以通過調整桶的大小和漏出速率來滿足不同的限流需求,可以靈活地適應不同的場景。
劣勢
- 需要對請求進行緩存,會增加服務器的內存消耗。
- 對于流量波動比較大的場景,需要較為靈活的參數配置才能達到較好的效果。
- 但是面對突發流量的時候,漏桶算法還是循規蹈矩地處理請求,這不是我們想看到的啦。流量變突發時,我們肯定希望系統盡量快點處理請求,提升用戶體驗嘛。
2.4?令牌桶算法
基于(入口)流速來做流控的一種限流算法。
原理
該算法維護一個固定容量的令牌桶,每秒鐘會向令牌桶中放入一定數量的令牌。當有請求到來時,如果令牌桶中有足夠的令牌,則請求被允許通過并從令牌桶中消耗一個令牌,否則請求被拒絕。
?實現方式
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledThreadPoolExecutor;import java.util.concurrent.TimeUnit;
public class TokenBucketRateLimiter {private long capacity; // 令牌桶容量,即最大允許的請求數量private long rate; // 令牌產生速率,即每秒產生的令牌數量private long tokens; // 當前令牌數量private ScheduledExecutorService scheduler; // 調度器public TokenBucketRateLimiter(long capacity, long rate) {this.capacity = capacity;this.rate = rate;this.tokens = capacity;this.scheduler = new ScheduledThreadPoolExecutor(1);scheduleRefill(); // 啟動令牌補充任務}private void scheduleRefill() {scheduler.scheduleAtFixedRate(() -> {synchronized (this) {tokens = Math.min(capacity, tokens + rate); // 補充令牌,但不超過容量}}, 1, 1, TimeUnit.SECONDS); // 每秒產生一次令牌}public synchronized boolean tryAcquire() {if (tokens > 0) { // 判斷令牌數量是否大于0tokens--; // 消耗一個令牌return true; // 獲取請求成功}return false; // 令牌不足,無法獲取請求}
}
代碼解讀
capacity表示令牌桶的容量,即最大允許的請求數量;rate表示令牌產生速率,即每秒產生的令牌數量。tokens表示當前令牌數量,scheduler是用于調度令牌補充任務的線程池。在構造方法中,初始化令牌桶的容量和當前令牌數量,并啟動令牌補充任務scheduleRefill()。
scheduleRefill()方法使用調度器定期執行令牌補充任務,每秒補充一次令牌。在補充任務中,通過加鎖的方式更新令牌數量,確保線程安全。補充的令牌數量為當前令牌數量加上產生速率,但不超過令牌桶的容量。
tryAcquire()方法使用synchronized關鍵字來實現線程安全,在方法中進行以下操作:
1.判斷令牌數量是否大于0。
- 如果令牌數量大于0,表示令牌足夠,消耗一個令牌,并返回true表示獲取請求成功。
- 如果令牌數量為0,表示令牌不足,返回false表示無法獲取請求。
Guava的RateLimiter限流組件,就是基于令牌桶算法實現的。
適用場景
一般用于保護自身的系統,對調用者進行限流,保護自身的系統不被突發的流量打垮。如果自身的系統實際的處理能力強于配置的流量限制時,可以允許一定程度的流量突發,使得實際的處理速率高于配置的速率,充分利用系統資源。
優劣分析
優勢
- 穩定性高:令牌桶算法可以控制請求的處理速度,可以使系統的負載變得穩定。
- 精度高:令牌桶算法可以根據實際情況動態調整生成令牌的速率,可以實現較高精度的限流。
- 彈性好:令牌桶算法可以處理突發流量,可以在短時間內提供更多的處理能力,以處理突發流量。
劣勢
- 實現復雜:相對于固定窗口算法等其他限流算法,令牌桶算法的實現較為復雜。對短時請求難以處理:在短時間內有大量請求到來時,可能會導致令牌桶中的令牌被快速消耗完,從而限流。這種情況下,可以考慮使用漏桶算法。
- 時間精度要求高:令牌桶算法需要在固定的時間間隔內生成令牌,因此要求時間精度較高,如果系統時間不準確,可能會導致限流效果不理想。
2.5?滑動日志算法(比較冷門)
滑動日志限速算法需要記錄請求的時間戳,通常使用有序集合來存儲,我們可以在單個有序集合中跟蹤用戶在一個時間段內所有的請求。
原理
滑動日志算法可以用于實現限流功能,即控制系統在單位時間內處理請求的數量,以保護系統免受過載的影響。以下是滑動日志算法用于限流的原理:
- 劃分時間窗口:將時間劃分為固定的時間窗口,例如每秒、每分鐘或每小時等。
- 維護滑動窗口:使用一個滑動窗口來記錄每個時間窗口內的請求次數。這個滑動窗口可以是一個固定長度的隊列或數組。
- 請求計數:當一個請求到達時,將其計數加一并放入當前時間窗口中。
- 滑動:隨著時間的流逝,滑動窗口會根據當前時間窗口的長度,移除最舊的請求計數,并將新的請求計數添加到最新的時間窗口中。
- 限流判斷:在每個時間窗口結束時,統計滑動窗口中的請求計數總和,并與預設的閾值進行比較。如果總請求數超過閾值,則觸發限流處理。
- 限流處理:一旦觸發限流,可以采取不同的處理策略,如拒絕請求、延遲處理、返回錯誤信息等。具體的限流策略可以根據實際情況進行選擇。
通過滑動日志算法進行限流,可以實現對單位時間內的請求進行精確控制。它基于實時統計的方式,能夠動態地適應請求流量的變化,并且在內存使用上比較高效。同時,通過調整時間窗口的長度和閾值的設置,可以靈活地控制限流的精度和靈敏度。
import java.util.LinkedList;
import java.util.List;
public class SlidingLogRateLimiter {private int requests; // 請求總數private List<Long> timestamps; // 存儲請求的時間戳列表private long windowDuration; // 窗口持續時間,單位:毫秒private int threshold; // 窗口內的請求數閥值public SlidingLogRateLimiter(int threshold, long windowDuration) {this.requests = 0;this.timestamps = new LinkedList<>();this.windowDuration = windowDuration;this.threshold = threshold; }public synchronized boolean tryAcquire() {long currentTime = System.currentTimeMillis(); // 獲取當前時間戳// 刪除超過窗口持續時間的時間戳while (!timestamps.isEmpty() && currentTime - timestamps.get(0) > windowDuration) {timestamps.remove(0);requests--;}if (requests < threshold) { // 判斷當前窗口內請求數是否小于閥值timestamps.add(currentTime); // 將當前時間戳添加到列表requests++; // 請求總數增加return true; // 獲取請求成功}return false; // 超過閥值,無法獲取請求}
}
代碼解讀:
在以上代碼中,requests表示請求總數,timestamps用于存儲請求的時間戳列表,windowDuration表示窗口持續時間,threshold表示窗口內的請求數閥值。
在構造函數中傳入窗口內的請求數閥值和窗口持續時間。
tryAcquire()方法使用了synchronized關鍵字來實現線程安全,在方法中進行以下操作:
- 獲取當前系統時間戳 currentTime。
- 刪除超過窗口持續時間的時間戳,同時更新請求總數。
- 判斷當前窗口內請求數是否小于閥值。
如果小于閥值,將當前時間戳添加到列表,請求總數增加,并返回true表示獲取請求成功。如果已經達到或超過閥值,表示請求數已滿,返回false表示無法獲取請求。
使用這個滑動日志限流算法,可以限制在一定時間窗口內的請求頻率,超過閥值的請求會被限制。您可以根據實際需求和業務場景進行調整和使用。
適用場景
優勢
- 滑動日志能夠避免突發流量,實現較為精準的限流;
- 更加靈活,能夠支持更加復雜的限流策略 如多級限流,每分鐘不超過100次,每小時不超過300次,每天不超過1000次,我們只需要保存最近24小時所有的請求日志即可實現。
劣勢
- 占用存儲空間要高于其他限流算法。
三、常用工具
3.1?RateLimiter(單機)
基于令牌桶算法實現的一個多線程限流器,它可以將請求均勻的進行處理,當然他并不是一個分布式限流器,只是對單機進行限流。它可以應用在定時拉取接口數。通過aop、filter、Interceptor 等都可以達到限流效果。
用法
以下是一個基本的 RateLimiter 用法示例:
import com.google.common.util.concurrent.RateLimiter;
public class RateLimiterDemo {public static void main(String[] args) {// 創建一個每秒允許2個請求的RateLimiterRateLimiter rateLimiter = RateLimiter.create(2.0);while (true) {// 請求RateLimiter一個令牌rateLimiter.acquire();// 執行操作doSomeLimitedOperation();}}private static void doSomeLimitedOperation() {// 模擬一些操作System.out.println("Operation executed at: " + System.currentTimeMillis());}
}
在這個例子中,RateLimiter.create(2.0) 創建了一個每秒鐘只允許2個操作的限速器。rateLimiter.acquire() 方法會阻塞當前線程直到獲取到許可,確保調用 doSomeLimitedOperation() 操作的頻率不會超過限制。
RateLimiter 還提供了其他的方法,例如tryAcquire(),它會嘗試獲取許可而不會阻塞,立即返回獲取成功或失敗的結果。還可以設置等待時間上限,比如 tryAcquire(long timeout, TimeUnit unit) 可以設置最大等待時間。
Guava的RateLimiter非常靈活,它支持平滑突發限制(SmoothBursty)和平滑預熱限制(SmoothWarmingUp)等多種模式,可以根據特定的應用場景來選擇合適的限流策略。
3.2?sentinel(單機或者分布式)
Sentinel是阿里巴巴開源的一款面向分布式系統的流量控制和熔斷降級組件。它提供了實時的流量控制、熔斷降級、系統負載保護和實時監控等功能,可以幫助開發者保護系統的穩定性和可靠性。
單機模式
- DefaultController:是一個非常典型的滑動窗口計數器算法實現,將當前統計的qps和請求進來的qps進行求和,小于限流值則通過,大于則計算一個等待時間,稍后再試;
- ThrottlingController:是漏斗算法的實現,實現思路已經在源碼片段中加了備注;
- WarmUpController:實現參考了Guava的帶預熱的RateLimiter,區別是Guava側重于請求間隔,類似前面提到的令牌桶,而Sentinel更關注于請求數,和令牌桶算法有點類似;
- WarmUpRateLimiterController:低水位使用預熱算法,高水位使用滑動窗口計數器算法排隊。
集群模式
Sentinel 集群限流服務端有兩種啟動方式:
- 嵌入模式(Embedded)適合應用級別的限流,部署簡單,但對應用性能有影響
- 獨立模式(Alone)適合全局限流,需要獨立部署
用法
Sentinel的用法主要包括以下幾個方面:
(1)引入依賴:在項目中引入Sentinel的相關依賴,可以使用Maven或Gradle進行依賴管理。例如,在Maven項目的pom.xml文件中添加以下依賴:
<dependency><groupId>com.alibaba.csp</groupId><artifactId>sentinel-core</artifactId><version>1.8.2</version>
</dependency>
(2)配置規則:根據實際需求,配置Sentinel的流量控制規則、熔斷降級規則等。可以通過編程方式或配置文件方式進行規則的配置。例如,可以在啟動類中使用注解方式配置流量控制規則:
@SentinelResource(value = "demo", blockHandler = "handleBlock")
public String demo() {// ...
}
(3)啟動Agent:在應用啟動時,啟動Sentinel的Agent,開啟對系統的流量控制和熔斷降級功能的保護。可以通過命令行啟動Agent,或者在代碼中進行啟動。例如,在Spring Boot的啟動類中添加如下代碼:
public static void main(String[] args) {System.setProperty("csp.sentinel.dashboard.server", "localhost:8080"); // 設置控制臺地址System.setProperty("project.name", "your-project-name"); // 設置應用名稱com.alibaba.csp.sentinel.init.InitExecutor.doInit();SpringApplication.run(YourApplication.class, args);
}
(4)監控和管理:使用Sentinel的控制臺進行實時監控、配置管理等操作。可以通過瀏覽器訪問Sentinel的控制臺界面,查看系統的運行情況和流量控制情況。通過控制臺,可以對規則進行動態修改,查看監控數據和告警信息。
3.3 Nginx(分布式)
Nginx從網關這一層面考慮,可以作為最前置的網關,抵擋大部分的網絡流量,因此使用Nginx進行限流也是一個很好的選擇,在Nginx中,也提供了常用的基于限流相關的策略配置。
用法
Nginx 提供了兩種限流方法:一種是控制速率,另一種是控制并發連接數。
控制速率
我們需要使用 limit_req_zone 用來限制單位時間內的請求數,即速率限制,因為Nginx的限流統計是基于毫秒的,我們設置的速度是 2r/s,轉換一下就是500毫秒內單個IP只允許通過1個請求,從501ms開始才允許通過第2個請求。
控制速率優化版
上面的速率控制雖然很精準但是在生產環境未免太苛刻了,實際情況下我們應該控制一個IP單位總時間內的總訪問次數,而不是像上面那樣精確到毫秒,我們可以使用 burst 關鍵字開啟此設置。
burst=4意思是每個IP最多允許4個突發請求
控制并發數
利用 limit_conn_zone 和 limit_conn 兩個指令即可控制并發數
其中 limit_conn perip 10 表示限制單個 IP 同時最多能持有 10 個連接;limit_conn perserver 100 表示 server 同時能處理并發連接的總數為 100 個。
注意:只有當 request header 被后端處理后,這個連接才進行計數。
四、小結
算法 | 簡介 | 核心思想 | 優點 | 缺點 | 開源工具/中間件 | 適用業務場景 |
固定窗口限流 | 在固定的時間窗口內計數請求,達到閾值則限流。 | 將時間分割成固定大小的窗口,每個窗口內獨立計數。 | 實現簡單,性能較好。 | 可能會有時間窗口切換時的突發流量。 | Nginx、Apache、RateLimiter (Guava) | 需要簡單限流,對流量突增不敏感的場景。 eg: 電商平臺在每日定時秒殺活動開始時,用于防止瞬時高流量沖垮系統。 |
滑動窗口限流 | 在滑動的時間窗口內計數請求,達到閾值則限流。 | 將時間分割為多個小窗口,統計近期內的總請求數。 | 平滑請求,避免固定窗口算法中的突發流量。 | 實現比固定窗口復雜,消耗資源較多。 | Redis、Sentinel | 對流量平滑性有較高要求的場景。 eg: 社交媒體平臺的消息發送功能,用于平滑處理高峰期的消息發送請求,避免服務短暫的超負荷。 |
令牌桶限流 | 以恒定速率向桶中添加令牌,請求消耗令牌,無令牌時限流。 | 以一定速率生成令牌,請求必須擁有令牌才能執行。 | 允許一定程度的突發流量,平滑處理請求。 | 對突發流量的容忍可能導致短時間內資源過載。 | Guava、Nginx、Apache、 Sentinel | 對突發流量有一定要求,且需要一定程度的平滑處理的場景。 eg: 視頻流媒體服務,允許用戶在網絡狀況良好時快速緩沖視頻,同時在網絡擁堵時平滑地降低請求速率。 |
漏桶算法 | 漏桶以固定速率出水,請求以任意速率流入桶內,桶滿則溢出(限流)。 | 以恒定的速率處理請求,超過該速率的請求被限制。 | 輸出流量穩定,能夠限制流量的最大速率。 | 無法應對突發流量,可能導致請求等待時間過長。 | Apache、Nginx | 適用于需要嚴格控制處理速率,對請求響應時間要求不高的場景。 eg: API網關對外提供服務的接口,需要確保后端服務的調用速率不超過其最大處理能力,防止服務崩潰 |
滑動日志限流 | 使用滑動時間窗口記錄請求日志,通過日志來判斷是否超出速率限制。 | 記錄最近一段時間內的請求日志,實時判斷請求是否超限。 | 能夠更細粒度地控制請求速率,比固定窗口更公平。 | 實現復雜,存儲和計算請求日志成本較高。 | - | 對實時性要求高,且需要精確控制請求速率的高級限流場景。 eg: 高頻交易系統,需要根據實時交易數據精確控制交易請求速率,防止因超負荷而影響整體市場的穩定性。 |