目前經常使用的限流算法有兩個:漏桶算法和令牌桶算法。java
漏桶算法的原理比較簡單,請求進入到漏桶中,漏桶以必定的速率漏水。當請求過多時,水直接溢出。能夠看出,漏桶算法能夠強制限制數據的傳輸速度。算法
令牌桶算法的原理是系統以必定速率向桶中放入令牌,若是有請求時,請求會從桶中取出令牌,若是能取到令牌,則能夠繼續完成請求,不然等待或者拒絕服務。這種算法能夠應對突發程序的請求,所以比漏桶算法好。緩存
在Wikipedia上,令牌桶算法是這麼描述的:併發
Guava中開源出來一個令牌桶算法的工具類RateLimiter,能夠輕鬆實現限流的工做。RateLimiter對簡單的令牌桶算法作了一些工程上的優化,具體的實現是SmoothBursty。須要注意的是,RateLimiter的另外一個實現SmoothWarmingUp,就不是令牌桶了,而是漏桶算法。也許是出於簡單起見,RateLimiter中的時間窗口能且僅能爲1S,若是想搞其餘時間單位的限流,只能另外造輪子。app
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的時間。也就是,取的速率能夠超過令牌產生的速率,可是下一次再次去取的時候,須要阻塞等待。ui
固然也可使用tryAcquire來非阻塞的獲取,能夠實時返回結果。另外tryAcquire也能夠傳入參數,也就是等待的時間,超時直接返回false。這點等同於常見的lock,tryLock。google
通常來講,在網關係統中,還有一個參數叫併發控制,就是某一個資源能夠被同時訪問的個數。這種狀況下,咱們可使用Semaphore來控制。
Semaphore不一樣於互斥鎖。互斥鎖是某個資源只能支持同時一個訪問,而Semaphore能夠支持多個訪問,可是加上了總數的控制。
<dependency> <groupId>com.google.guava</groupId> <artifactId>guava</artifactId> <version>18.0</version> </dependency>
把限流服務封裝到一個類中AccessLimitService,提供tryAcquire()方法,用來嘗試獲取令牌,返回true表示獲取到,以下所示:
@Service public class AccessLimitService { //每秒只發出5個令牌 RateLimiter rateLimiter = RateLimiter.create(5.0); /** * 嘗試獲取令牌 * @return */ public boolean tryAcquire(){ return rateLimiter.tryAcquire(); } }
調用方是個普通的controller,每次收到請求的時候都嘗試去獲取令牌,獲取成功和失敗打印不一樣的信息,以下:
@Controller public class HelloController { private static SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Autowired private AccessLimitService accessLimitService; @RequestMapping("/access") @ResponseBody public String access(){ //嘗試獲取令牌 if(accessLimitService.tryAcquire()){ //模擬業務執行500毫秒 try { Thread.sleep(500); }catch (InterruptedException e){ e.printStackTrace(); } return "aceess success [" + sdf.format(new Date()) + "]"; }else{ return "aceess limit [" + sdf.format(new Date()) + "]"; } } }