Java高并发系统的限流策略
限流算法
令牌桶(Token Bucket)、漏桶(leaky bucket)和計數器算法是最常用的三種限流的算法。
計數器限流算法也是比較常用的,主要用來限制總并發數,比如數據庫連接池大小、線程池大小、程序訪問并發數等都是使用計數器算法。也是最簡單粗暴的算法。
使用計數器限流示例1:
public class CountRateLimiterDemo { private static AtomicInteger count = new AtomicInteger(0); public static void exec() { if (count.get() >= 5) { System.out.println("請求用戶過多,請稍后在試!"+System.currentTimeMillis()/1000); } else { count.incrementAndGet(); try { //處理核心邏輯 TimeUnit.SECONDS.sleep(1); System.out.println("--"+System.currentTimeMillis()/1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { count.decrementAndGet(); } } } }使用AomicInteger來進行統計當前正在并發執行的次數,如果超過域值就簡單粗暴的直接響應給用戶,說明系統繁忙,請稍后再試或其它跟業務相關的信息。
弊端:使用 AomicInteger 簡單粗暴超過域值就拒絕請求,可能只是瞬時的請求量高,也會拒絕請求。
使用計數器限流示例2
public class CountRateLimiterDemo { private static Semaphore semphore = new Semaphore(50); public static void exec() { if(semphore.getQueueLength()>100){ System.out.println("當前等待排隊的任務數大于100,請稍候再試..."); } try { semphore.acquire(); // 處理核心邏輯 TimeUnit.SECONDS.sleep(1); System.out.println("--" + System.currentTimeMillis() / 1000); } catch (InterruptedException e) { e.printStackTrace(); } finally { semphore.release(); } } }使用Semaphore信號量來控制并發執行的次數,如果超過域值信號量,則進入阻塞隊列中排隊等待獲取信號量進行執行。如果阻塞隊列中排隊的請求過多超出系統處理能力,則可以在拒絕請求。
相對Atomic優點:如果是瞬時的高并發,可以使請求在阻塞隊列中排隊,而不是馬上拒絕請求,從而達到一個流量削峰的目的。
使用guava的RateLimiter限流示例3
Guava中開源工具類RateLimiter是基于令牌桶算法的實現類,可以簡單的實現限流的工作,并根據系統實際情況調整生成token速率。RateLimiter 對簡單的令牌桶算法做了一些工程上的優化,具體的實現是 SmoothBursty。需要注意的是,RateLimiter 的另一個實現 SmoothWarmingUp,就不是令牌桶了,而是漏桶算法。也許是出于簡單起見,RateLimiter 中的時間窗口能且僅能為 1s,如果想搞其他時間單位的限流,只能另外造輪子。
RateLimiter 有一個有趣的特性是「前人挖坑后人跳」,也就是說 RateLimiter 允許某次請求拿走超出剩余令牌數的令牌,但是下一次請求將為此付出代價,一直等到令牌虧空補上,并且桶中有足夠本次請求使用的令牌為止。這里面就涉及到一個權衡,是讓前一次請求干等到令牌夠用才走掉呢,還是讓它先走掉后面的請求等一等呢?Guava 的設計者選擇的是后者,先把眼前的活干了,后面的事后面再說。
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。
?
總結
以上是生活随笔為你收集整理的Java高并发系统的限流策略的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: java分布式应用限流实现
- 下一篇: JAVA中限制接口流量、并发的方法