一、常見的限流算法
目前常用的限流算法有兩個:漏桶算法和令牌桶算法。
1.漏桶算法
漏桶算法的原理比較簡單,請求進入到漏桶中,漏桶以一定的速率漏水。當請求過多時,水直接溢出。可以看出,漏桶算法可以強制限制數據的傳輸速度。
2.令牌桶算法
令牌桶算法的原理是系統以一定速率向桶中放入令牌,如果有請求時,請求會從桶中取出令牌,如果能取到令牌,則可以繼續完成請求,否則等待或者拒絕服務。這種算法可以應對突發程度的請求,因此比漏桶算法好。
在 Wikipedia 上,令牌桶算法是這么描述的:
- 每秒會有 r 個令牌放入桶中,或者說,每過 1/r 秒桶中增加一個令牌
- 桶中最多存放 b 個令牌,如果桶滿了,新放入的令牌會被丟棄
- 當一個 n 字節的數據包到達時,消耗 n 個令牌,然后發送該數據包
- 如果桶中可用令牌小於 n,則該數據包將被緩存或丟棄
二、RateLimiter
Guava中開源出來一個令牌桶算法的工具類RateLimiter,可以輕松實現限流的工作。RateLimiter 對簡單的令牌桶算法做了一些工程上的優化,具體的實現是 SmoothBursty。需要注意的是,RateLimiter 的另一個實現 SmoothWarmingUp,就不是令牌桶了,而是漏桶算法。也許是出於簡單起見,RateLimiter 中的時間窗口能且僅能為 1s,如果想搞其他時間單位的限流,只能另外造輪子。
RateLimiter 有一個有趣的特性是「前人挖坑后人跳」,也就是說 RateLimiter 允許某次請求拿走超出剩余令牌數的令牌,但是下一次請求將為此付出代價,一直等到令牌虧空補上,並且桶中有足夠本次請求使用的令牌為止。這里面就涉及到一個權衡,是讓前一次請求干等到令牌夠用才走掉呢,還是讓它先走掉后面的請求等一等呢?Guava 的設計者選擇的是后者,先把眼前的活干了,后面的事后面再說。
測試代碼:
public class RateLimiterMain {
public static void main(String[] args) {
RateLimiter rateLimiter = RateLimiter.create(2);
System.out.println(rateLimiter.acquire(5));
System.out.println(rateLimiter.acquire(2));
System.out.println(rateLimiter.acquire(1));
}
}
輸出內容:
0.0
2.496889
0.992149
可以看出,令牌桶每秒只能產生2個令牌,我們可以第一次取出5個,但是第二個再去取令牌的時候,需要等2.5s,也就是第一次令牌取完后,需要等2.5s才能取到令牌。同樣的,第三次取1個令牌的時候,也需要等待第二次的1s的時間。也就是,取的速率可以超過令牌產生的速率,但是下一次再次去取的時候,需要阻塞等待。
當然也可以使用tryAcquire來非阻塞的獲取,可以實時返回結果。另外tryAcquire也可以傳入參數,也就是等待的時間,超時直接返回false。這點等同於常見的lock,tryLock。
三、並發控制Semaphore
一般來說,在網關系統中,還有一個參數叫並發控制,就是某一個資源可以被同時訪問的個數。這種情況下,我們可以使用Semaphore來控制。
Semaphore不同於互斥鎖。互斥鎖是某個資源只能支持同時一個訪問,而Semaphore可以支持多個訪問,但是加上了總數的控制。
感興趣的同學可以繼續深入了解Semaphore的使用。