高併發系統的限流演算法與實現
開發高併發系統時有三把利器用來保護系統:快取、降級和限流。
-
快取:快取的目的是提升系統訪問速度和增大系統處理容量。
-
降級:降級是當伺服器壓力劇增的情況下,根據當前業務情況及流量對一些服務和頁面有策略的降級,以此釋放伺服器資源以保證核心任務的正常執行。
-
限流:限流的目的是通過對併發請求進行限速,或者對一個時間視窗內的請求進行限速來保護系統,一旦達到限制速率則可以進行拒絕服務、排隊或等待、降級等處理。
限流是限制系統的輸入和輸出流量,以達到保護系統的目的,而限流的實現主要是依靠限流演算法,限流演算法主要有4種:
-
固定時間視窗演算法(計數器)
-
滑動時間視窗演算法
-
令牌桶演算法
-
漏桶演算法
1. 固定時間視窗演算法
又稱計數器演算法。固定時間視窗演算法就是統計記錄單位時間內進入系統或者某一介面的請求次數,在限定的次數內的請求則正常接收處理,超過次數的請求則拒絕掉或者改為非同步處理等限流措施。
時間視窗長度如果為1分鐘,如圖。
此演算法在單機還是分散式環境下實現都非常簡單,使用redis的incr原子自增性即可輕鬆實現。
單機虛擬碼如下。
class CounterDemo {
public long timeStamp = getNowTime();
public int reqCount = 0;
public final int limit = 100; // 時間視窗內最大請求數
public final long interval = 1000; // 時間視窗ms
public boolean grant() {
long now = getNowTime();
if (now < timeStamp + interval) {
// 在時間視窗內
reqCount++;
// 判斷當前時間視窗內是否超過最大請求控制數
return reqCount <= limit;
} else {
timeStamp = now;
// 超時後重置
reqCount = 1;
return true;
}
}
}
演算法特點
-
實現簡單。
-
時間視窗固定,每個視窗開始時計數為零,這樣後面的請求不會受到之前的影響,做到了前後請求隔離。
-
因為兩個時間視窗之間沒有任何聯絡,所以呼叫者可以在一個時間視窗的結束到下一個時間視窗的開始這個非常短的時間段內發起兩倍於閾值的請求。所以固定時間視窗演算法無法限制視窗間突發流量。
2. 滑動時間視窗演算法
滑動時間視窗演算法其實是固定時間視窗演算法的優化,主要是為了解決固定時間視窗演算法無法限制視窗間突發流量的缺點。
上面的計數器的單位時間是1分鐘,而在使用滑動時間視窗,可以把1分鐘分成6格,每格時間長度是10s,每一格又各自管理一個計數器,單位時間用一個長度為60s的視窗描述。一個請求進入系統,對應的時間格子的計數器便會+1,而每過10s,這個視窗便會向右滑動一格。只要視窗包括的所有格子的計數器總和超過限流上限,便會執行限流措施。
由此可見,當滑動視窗的格子劃分的越多,那麼滑動視窗的滾動就越平滑,限流的統計就會越精確。
演算法特點
-
因為視窗順延,所以可以抵禦視窗間突發流量(對比固定時間視窗演算法)。
-
假如限流10萬次/小時,如果某個呼叫者在前10分鐘呼叫了10萬次那麼他必須再等待1小時才能發起下一次正常請求。所以沒有做到前後請求隔離。
阿里開源的Sentinel,採用的是滑動視窗演算法進行限流,可以閱讀相關程式碼,加深對滑動時間視窗演算法的理解。
3. 漏桶演算法(leaky bucket)
漏桶演算法其實很簡單,可以粗略的認為就是注水漏水過程,往桶中以一定速率流出水,以任意速率流入水,當水超過桶流量則丟棄,因為桶容量是不變的,保證了整體的速率。這個從桶底流出去的水就是系統正常處理的請求,從旁邊流出去的水就是系統拒絕掉的請求。
單機虛擬碼如下。
class LeakyDemo {
public long timeStamp = getNowTime();
public int capacity; // 桶的容量
public int rate; // 水漏出的速度
public int water; // 當前水量(當前累積請求數)
public boolean grant() {
long now = getNowTime();
water = max(0, water - (now - timeStamp) * rate); // 先執行漏水,計算剩餘水量
timeStamp = now;
if ((water + 1) < capacity) {
// 嘗試加水,並且水還未滿
water += 1;
return true;
} else {
// 水滿,拒絕加水
return false;
}
}
}
演算法特點
-
因為流出的速度是一定的,可以抵禦突發流量,做到更加平滑的限流,而且不允許流量突發。
4. 令牌桶演算法(Token Bucket)
令牌桶演算法是比較常見的限流演算法之一,Google開源專案Guava中的RateLimiter使用的就是令牌桶演算法。流程如下:
-
所有的請求在處理之前都需要拿到一個可用的令牌才會被處理。
-
根據限流大小,設定按照一定的速率往桶裡新增令牌。
-
桶設定最大的放置令牌限制,當桶滿時、新新增的令牌就被丟棄或者拒絕。
-
請求到達後首先要獲取令牌桶中的令牌,拿著令牌才可以進行其他的業務邏輯,處理完業務邏輯之後,將令牌直接刪除。
單機虛擬碼如下,分散式環境可以使用Redisson。
class TokenBucketDemo {
public long timeStamp = getNowTime();
public int capacity; // 桶的容量
public int rate; // 令牌放入速度
public int tokens; // 當前令牌數量
public boolean grant() {
long now = getNowTime();
// 先新增令牌
tokens = min(capacity, tokens + (now - timeStamp) * rate);
timeStamp = now;
if (tokens < 1) {
// 若桶中沒有令牌,則拒絕
return false;
} else {
// 還有令牌,領取令牌
tokens -= 1;
return true;
}
}
}
演算法特點
-
可以抵禦突發流量,因為桶內的令牌數不會超過給定的最大值
-
可以做到更加平滑的限流,因為令牌是勻速放入的。
-
令牌桶演算法允許流量一定程度的突發。(相比漏桶演算法)
在時間點重新整理的臨界點上,只要剩餘token足夠,令牌桶演算法會允許對應數量的請求通過,而後重新整理時間因為token不足,流量也會被限制在外,這樣就比較好的控制了瞬時流量。因此,令牌桶演算法也被廣泛使用。
更多內容,歡迎關注微信公眾號:全菜工程師小輝~
相關文章
- 高併發系統限流中的演算法演算法
- 分散式系統限流演算法分析與實現分散式演算法
- 高併發架構下的系統限流保護策略架構
- 慌了,居然被問到怎麼做高併發系統的限流
- 搭建高併發、高可用的系統
- [分散式][高併發]限流的四種策略分散式
- 短影片直播系統,實現高併發秒殺的多種方式
- 高併發後端設計-限流篇後端
- SpringBoot實現Java高併發秒殺系統之Web層開發(三)Spring BootJavaWeb
- Nginx 實現高併發的原理分析Nginx
- 非同步程式設計CompletableFuture實現高併發系統優化之請求合併非同步程式設計優化
- 高併發IM系統架構優化實踐架構優化
- 【高併發】如何實現億級流量下的分散式限流?這些理論你必須掌握!!分散式
- 限流 SDK 的設計與實現
- Springboot:高併發下耗時操作的實現Spring Boot
- django框架怎麼實現高併發Django框架
- 高併發系統設計的15個錦囊
- 【高併發】面試官問我如何使用Nginx實現限流,我如此回答輕鬆拿到了Offer!面試Nginx
- 【高併發寫】庫存系統設計
- 高併發系統之大忌-慢查詢
- Java高併發與多執行緒(二)-----執行緒的實現方式Java執行緒
- 用PHP實現高併發伺服器PHP伺服器
- 如何快速實現高併發短文檢索
- 利用Redis實現高併發計數器Redis
- 大型網站限流演算法的實現和改造網站演算法
- 談談限流演算法,以及Redisson實現演算法Redis
- php令牌桶演算法實現介面限流PHP演算法
- 【高併發】億級流量場景下如何實現分散式限流?看完我徹底懂了!!(文末有福利)分散式
- 【高併發】Redis如何助力高併發秒殺系統,看完這篇我徹底懂了!!Redis
- Activemq構建高併發、高可用的大規模訊息系統MQ
- [java併發程式設計]基於訊號量semaphore實現限流器Java程式設計
- Java併發指南9:AQS共享模式與併發工具類的實現JavaAQS模式
- Redis 實現高併發下的搶購 / 秒殺功能Redis
- 如何設計一個高可用、高併發秒殺系統
- Java ConcurrentHashMap 高併發安全實現原理解析JavaHashMap
- php利用pcntl擴充套件實現高併發PHP套件
- 使用Redis構建高併發高可靠的秒殺拍賣系統 - LuisRedisUI
- 談談高併發系統的一些解決方案