華文網

基於Java分散式環境下限流系統的設計

前提

業務背景

就拿前些天的雙十一的 “搶券活動” 來說,一般是設置整點開始搶的,你想想,淘寶的用戶群體非常大,可以達到億級別,而服務介面每秒能處理的量是有限的,那麼這個時候問題就會出現,

我們如何通過程式來控制使用者搶券呢,於是就必須加上這個限流功能了。

生產環境

1、服務介面所能提供的服務上限(limit)假如是 500次/s

2、使用者請求介面的次數未知,QPS可能達到 800次/s,1000次/s,或者更高

3、當服務介面的訪問頻率超過 500次/s,超過的量將拒絕服務,多出的資訊將會丟失

4、線上環境是多節點部署的,但是調用的是同一個服務介面

於是,為了保證服務的可用性,

就要對服務介面調用的速率進行限制(介面限流)。

什麼是限流?

限流是對系統的出入流量進行控制,防止大流量出入,導致資源不足,系統不穩定。

限流系統是對資源訪問的控制元件,控制主要的兩個功能:限流策略和熔斷策略,對於熔斷策略,不同的系統有不同的熔斷策略訴求,有的系統希望直接拒絕、有的系統希望排隊等待、有的系統希望服務降級、有的系統會定制自己的熔斷策略,

這裡只針對限流策略這個功能做詳細的設計。

限流演算法

1、限制暫態併發數

Guava RateLimiter 提供了權杖桶演算法實現:平滑突發限流(SmoothBursty)和平滑預熱限流(SmoothWarmingUp)實現。

2、限制某個介面的時間窗最大請求數

即一個時間視窗內的請求數,如想限制某個介面/服務每秒/每分鐘/每天的請求數/調用量。如一些基礎服務會被很多其他系統調用,比如商品詳情頁服務會調用基礎商品服務調用,

但是怕因為更新量比較大將基礎服務打掛,這時我們要對每秒/每分鐘的調用量進行限速;一種實現方式如下所示:

LoadingCache counter =

CacheBuilder.newBuilder()

.expireAfterWrite(2, TimeUnit.SECONDS)

.build(new CacheLoader() {

@Override

public AtomicLong load(Long seconds) throws Exception {

return new AtomicLong(0);

}

});

long limit = 1000;

while(true) {

//得到當前秒

long currentSeconds = System.currentTimeMillis() / 1000;

if(counter.get(currentSeconds).incrementAndGet() > limit) {

System.out.println("限流了:" + currentSeconds);

continue;

}

//業務處理}

}

使用Guava的Cache來存儲計數器,過期時間設置為2秒(保證1秒內的計數器是有的),然後我們獲取當前時間戳記然後取秒數來作為KEY進行計數統計和限流,這種方式也是簡單粗暴,剛才說的場景夠用了。

3、權杖桶

演算法描述:

假如使用者配置的平均發送速率為r,則每隔1/r秒一個權杖被加入到桶中

假設桶中最多可以存放b個權杖。如果權杖到達時權杖桶已經滿了,那麼這個權杖會被丟棄

當流量以速率v進入,從桶中以速率v取權杖,拿到權杖的流量通過,拿不到權杖流量不通過,執行熔斷邏輯

屬性

長期來看,符合流量的速率是受到權杖添加速率的影響,被穩定為:r

因為權杖桶有一定的存儲量,可以抵擋一定的流量突發情況

M是以位元組/秒為單位的最大可能傳輸速率。 M>r

T max = b/(M-r) 承受最大傳輸速率的時間

B max = T max * M 承受最大傳輸速率的時間內傳輸的流量

優點:流量比較平滑,並且可以抵擋一定的流量突發情況

4、Google guava 提供的工具庫中 RateLimiter 類(內部也是採用權杖桶演算法實現)

最快的方式是使用 RateLimit 類,但是這僅限制在單節點,如果是分散式系統,每個節點的 QPS 是一樣的,請求量到服務介面那的話就是 QPS * 節點數 了。所以這種方案在分散式的情況下不適用!

5、基於 Redis 實現,存儲兩個 key,一個用於計時,一個用於計數。請求每調用一次,計數器增加 1,若在計時器時間內計數器未超過閾值,則可以處理任務。

這種能夠很好地解決了分散式環境下多實例所導致的併發問題。因為使用redis設置的計時器和計數器均是全域唯一的,不管多少個節點,它們使用的都是同樣的計時器和計數器,因此可以做到非常精准的流控。

代碼就不公佈了,畢竟涉及公司隱私了。

如果你想學習Java工程化、高性能及分散式、深入淺出。性能調優、Spring,MyBatis,Netty源碼分析和大資料等知識點可以來找我。

而現在我就有一個平臺可以提供給你們學習,讓你在實踐中積累經驗掌握原理。主要方向是JAVA架構師。如果你想拿高薪,想突破瓶頸,想跟別人競爭能取得優勢的,想進BAT但是有擔心面試不過的,可以加我的Java架構進階群:675047716

注:加群要求

1、具有2-5工作經驗的,面對目前流行的技術不知從何下手,需要突破技術瓶頸的可以加。

2、在公司待久了,過得很安逸,但跳槽時面試碰壁。需要在短時間內進修、跳槽拿高薪的可以加。

3、如果沒有工作經驗,但基礎非常扎實,對java工作機制,常用設計思想,常用java開發框架掌握熟練的,可以加。

4、覺得自己很牛B,一般需求都能搞定。但是所學的知識點沒有系統化,很難在技術領域繼續突破的可以加。

5.阿裡Java高級大牛直播講解知識點,分享知識,多年工作經驗的梳理和總結,帶著大家全面、科學地建立自己的技術體系和技術認知!

6.小號加群一律不給過,謝謝。

轉發此文章請帶上原文連結,否則將追究法律責任!

但是這僅限制在單節點,如果是分散式系統,每個節點的 QPS 是一樣的,請求量到服務介面那的話就是 QPS * 節點數 了。所以這種方案在分散式的情況下不適用!

5、基於 Redis 實現,存儲兩個 key,一個用於計時,一個用於計數。請求每調用一次,計數器增加 1,若在計時器時間內計數器未超過閾值,則可以處理任務。

這種能夠很好地解決了分散式環境下多實例所導致的併發問題。因為使用redis設置的計時器和計數器均是全域唯一的,不管多少個節點,它們使用的都是同樣的計時器和計數器,因此可以做到非常精准的流控。

代碼就不公佈了,畢竟涉及公司隱私了。

如果你想學習Java工程化、高性能及分散式、深入淺出。性能調優、Spring,MyBatis,Netty源碼分析和大資料等知識點可以來找我。

而現在我就有一個平臺可以提供給你們學習,讓你在實踐中積累經驗掌握原理。主要方向是JAVA架構師。如果你想拿高薪,想突破瓶頸,想跟別人競爭能取得優勢的,想進BAT但是有擔心面試不過的,可以加我的Java架構進階群:675047716

注:加群要求

1、具有2-5工作經驗的,面對目前流行的技術不知從何下手,需要突破技術瓶頸的可以加。

2、在公司待久了,過得很安逸,但跳槽時面試碰壁。需要在短時間內進修、跳槽拿高薪的可以加。

3、如果沒有工作經驗,但基礎非常扎實,對java工作機制,常用設計思想,常用java開發框架掌握熟練的,可以加。

4、覺得自己很牛B,一般需求都能搞定。但是所學的知識點沒有系統化,很難在技術領域繼續突破的可以加。

5.阿裡Java高級大牛直播講解知識點,分享知識,多年工作經驗的梳理和總結,帶著大家全面、科學地建立自己的技術體系和技術認知!

6.小號加群一律不給過,謝謝。

轉發此文章請帶上原文連結,否則將追究法律責任!