【限流】——RateLimiter限流方案

背景

在說限流方案之前我們需要知道在什麼樣的場景下需要限流,以前我們都是應用部署在單臺機器上,但是隨着流量的增大單臺服務已經扛不住壓力了,所以就變成了新的的集羣部署,將流量壓力分擔到多臺機器上,來提高吞吐量,但是互聯網企業嘛,在一些節日或者特殊的日子裏會搞一些搶購的活動,這個併發壓力可能是平時的很多倍吧,按照正常的思路來說我們增加機器橫向擴展吧,可是平時的話流量沒有這麼大,購買這些機器其實是非常浪費的,而且我們的搶購只是流量大,但是實際的有效流量是很少的,所以限流方案出來了,guava的工具類中RateLimiter就提供了這樣的功能,下面來看下幾種限流方案。

一、限流方案

1、計數器方式

採用AtomicInteger:

     使用AomicInteger來進行統計當前正在併發執行的次數,如果超過域值就簡單粗暴的直接響應給用戶,說明系統繁忙,請稍後再試或其它跟業務相關的信息。

    弊端:使用 AomicInteger 簡單粗暴超過域值就拒絕請求,可能只是瞬時的請求量高,也會拒絕請求。

 採用令牌Semaphore:

     使用Semaphore信號量來控制併發執行的次數,如果超過域值信號量,則進入阻塞隊列中排隊等待獲取信號量進行執行。如果阻塞隊列中排隊的請求過多超出系統處理能力,則可以在拒絕請求。

    相對Atomic優點:如果是瞬時的高併發,可以使請求在阻塞隊列中排隊,而不是馬上拒絕請求,從而達到一個流量削峯的目的。

 採用ThreadPoolExecutor java線程池:

    固定線程池大小,超出固定先線程池和最大的線程數,拒絕線程請求;

2、令牌桶方式

令牌桶算法是網絡流量整形(Traffic Shaping)和速率限制(Rate Limiting)中最常使用的一種算法。先有一個木桶,系統按照固定速度,往桶裏加入Token,如果桶已經滿了就不再添加。當有請求到來時,會各自拿走一個Token,取到Token 才能繼續進行請求處理,沒有Token 就拒絕服務。

  這裏如果一段時間沒有請求時,桶內就會積累一些Token,下次一旦有突發流量,只要Token 足夠,也能一次處理,所以令牌桶算法的特點是允許突發流量。

    我們看一個例子,看看令牌桶如何允許突發流量,假如令牌則按照每秒5 個的速度放入令牌桶,桶中最多存放20 個令牌,那系統可以支持兩種類型的請求流量,一種是允許持續的每秒處理5 個請求,第二種是每隔4 秒,等桶中20 個令牌攢滿後,就可以處理一次有20 個請求的突發情況。

令牌桶的方案設計:

  使用guava提供工具庫裏的RateLimiter類(內部採用令牌捅算法實現)進行限流

3、漏桶算法

    一個固定容量的漏桶,按照常量固定速率流出水滴;

      先想象有一個木桶,新請求就像水滴一樣,不斷地滴進來,水滴進來的速度是不確定的,有時會快一點,有時會慢一點,同時桶底下有個洞,可以按照固定的速度把水漏走,如果水進來的速度比漏走的快,桶就會滿了,桶滿了水就會漫出來,對應的就是拒絕請求。

  漏桶算法的主要特點是可以平滑網絡上的突發流量,請求可以被整形成穩定的流量。

二、實現(令牌通方式)

1、代碼

 public static void main(String[] args) throws InterruptedException {
        RateLimiter limiter = RateLimiter.create(5);
        long start = System.currentTimeMillis();
        System.out.println("start:"+start);
        for (int i = 0; i <20 ; i++) {
            Thread.sleep(10);
            boolean getToken  =limiter.tryAcquire();
            if (getToken){
                System.out.println("獲取了令牌====="+(i+1));
            }
        }
        long end = System.currentTimeMillis();
        System.out.println("end:"+end);
        System.out.println("花費時間:"+(end-start)+"毫秒");

        RateLimiter limiter1 = RateLimiter.create(1);
        System.out.println(limiter1.acquire(10));
        System.out.println(limiter1.acquire());
        System.out.println(limiter1.acquire());
        System.out.println(limiter1.acquire());
        System.out.println(limiter1.acquire());
        System.out.println(limiter1.acquire());
        System.out.println(limiter1.acquire());
        System.out.println(limiter1.acquire());
        System.out.println(limiter1.acquire());
    }

2、結果

start:1575013943433
獲取了令牌=====1
獲取了令牌=====19
end:1575013943652
花費時間:219毫秒
0.0
9.998836
0.99797
1.000141
0.998961
0.99968
0.999161
0.999291
Disconnected from the target VM, address: '127.0.0.1:62636', transport: 'socket'
0.999234

Process finished with exit code 0

參考:http://www.dczou.com/viemall/852.html

發佈了163 篇原創文章 · 獲贊 49 · 訪問量 17萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章