Google限速神器——RateLimiter分享


前言

對微服務有所涉獵的小伙伴,應該都知道限流組件這個東西,它是微服務領域中有一個特別重要的組件,它的作用是限制同一時間點訪問某一個服務的線程的數量或者請求數,而且這樣的場景在現實應用開發中使用的也別廣泛,比如雙十一秒殺、春運搶票。

如果沒有這個組件的加持,那么我們的服務器很容易因為瞬時並發數量過高而導致宕機,所以換句話說,一個系統運行是否穩定,限流組件起着特別重要的作用的,所以從今天開始,我打算搜集下常用的限流解決方案,逐一研究下其基本原理,當然以我現階段的積累,可能不會涉及太深。

今天我們就來看下googlgguava中提供的一款限流組件——RateLimiter,關於guava這個工具包,我們之前已經分享過其中集合部分的應用,感興趣的小伙伴可以去看下:

https://mp.weixin.qq.com/s?__biz=MjM5NDMwNzA0NQ==&mid=2648417486&idx=1&sn=eb5aeaa688345cd64e3b77e527e95901&chksm=bea6ca4489d14352265b659325965918ad148ef932ad60af0baeaa85525afea0d2d31a2e0539&token=643197278&lang=zh_CN#rd

其實關於限流組件這塊,我們當時在分享多線程相關內容的時候,有一個多線程組件就可以用來做限速使用,不知道給位小伙伴是否還記得——Semaphore,如果你還記得名字,說明你多線程這塊學的還是比較扎實的,忘記了也沒關系,好好復習下就好。

另外,因為Semaphore之前已經分享過了,所以今天就不再贅述了,感興趣的小伙伴自己可以去看下:

https://mp.weixin.qq.com/s?__biz=MjM5NDMwNzA0NQ==&mid=2648419288&idx=1&sn=e893e3ee0877070fe541ce3add98781a&chksm=bea6c55289d14c44b11d27ac99a3c19d0ca1ba12bad8ecaf4671200e7d743f896a477da69195&token=643197278&lang=zh_CN#rd

RateLimiter

我們先看下官方文檔對於RateLimiter的描述:

RateLimiter經常用於限制對一些物理資源或者邏輯資源的訪問速率。與Semaphore 相比,Semaphore 限制了並發訪問的數量而不是使用速率。

通過設置許可證的速率來定義RateLimiter。在默認配置下,許可證會在固定的速率下被分配,速率單位是每秒多少個許可證。為了確保維護配置的速率,許可會被平穩地分配,許可之間的延遲會做調整。

可能存在配置一個擁有預熱期的RateLimiter 的情況,在這段時間內,每秒分配的許可數會穩定地增長直到達到穩定的速率。

從概念上講,速率限制器(RateLimiter)以可配置的速率分發許可證。 如有必要,每個acquire() 都會阻塞,直到獲得許可為止,然后獲取它。 獲得許可后,無需發放許可證。

RateLimiter 對於並發使用是安全的:它將限制來自所有線程的總調用率。 但請注意,它並不能保證公平。

源碼簡單分析

從源碼來看,RateLimiter是一個抽象類,而且它並沒有直接對外提供構造方法,所以我們只能通過靜態方法create來創建RateLimiter的實例:

另外,從源碼中發現,這個組件的使用環境必須大於等於jdk13,所以各位小伙伴在測試的時候一定要注意:

根據這段代碼,以及我們的多線程使用經驗來說,這個方法本身是組賽的,而且阻塞是基於stopwatch來實現的,而且這里阻塞的時長是根據我們的速率計算的,關於速率我們等下會說到。

簡單使用

其實關於RateLimter的使用,在源碼的注釋中,官方已經給出了相應的示例:

第一的示例的作用是,限制線程的執行速率,也就是每秒執行不能超過兩次。下面我們通過簡單的代碼演示下這個示例,然后從實例中體會RateLimiter的應用場景。

代碼實現很簡單,我們通過線程池提交了100個打印操作,然后在線程啟動前,我們加了一行代碼:

rateLimiter.acquire()

這行代碼的作用就是控制速率,這個方法就更過分了,它需要jdk16以上才能運行,我這里剛好是1613的版本返回值是void:

根據這段代碼,以及我們的多線程使用經驗來說,這個方法本身是組賽的,而且阻塞是基於stopwatch來實現的,而且這里阻塞的時長是根據我們的速率計算的,關於速率我們等下會說到。

下面是示例的完整實現:

public class RateLimiterTest {
    public static void main(String[] args) {
        final RateLimiter rateLimiter = RateLimiter.create(2.0);
        ExecutorService executorService = Executors.newFixedThreadPool(100);
        List<Runnable> tasks = Lists.newArrayList();
        for (int i = 0; i < 100; i++) {
            rateLimiter.acquire();
            executorService.execute(() -> {
                String dateTime = new SimpleDateFormat("HH:mm:ss:SSS").format(new Date());
                System.out.printf("limiter-%s%n", dateTime);
            });
        }
        executorService.shutdown();
    }
}

運行結果如下:

從運行結果可以看出來,不論運行多少次,每一秒始終只會運行兩次,而且兩次的間隔時間剛好是500ms,這說明時間間隔的算法是1000ms / 22就是我們上面指定的速率),我們可以測試下:

當我們將速率設置為4時,線程之間間隔的時間接近250ms,說明我們上面的推斷基本正確,但是速率的具體控制算法還需要進一步研究源碼。

結語

鑒於時間的關系,RateLimiter先將這么多,這里算是引入RateLimiter的限流解決方案,希望借此能夠勾起各位小伙伴對於限流組件實現原理的思考。

從實際應用角度來看,RateLimiter似乎還不具備實際業務應用的條件,一個是因為它的運行環境要求比較高,必須jdk 16及以上,現階段應該很少有企業在正式環境使用這個版本;另一個原因是,RateLimiter的類和部分方法上加了@Bate這樣的注解,表明它應該還是一款正處於開發測試階段的產品,尚未經過相關論證,應用到先說環境確實也需要謹慎驗證。

但是如果作為學習借鑒的話,那RateLimiter無疑是比較完美的實驗品,從個人的角度來說,我覺得RateLimiter的源碼值得研究,而且我后期還會進一步探究它的實現原理。好了,今天就先到這里吧,各位小伙伴晚安呀!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM