使用Guava-RateLimiter做介面限流
阿新 • • 發佈:2019-01-02
RateLimiter
速率限制器,是 Google Guava提供的基於令牌桶演算法的實現類。
對請求流量進行限制。不僅可以控制併發,而且可以準確的控制TPS(每秒鐘請求數)。
應用場景:
一年一度的天貓雙11限時搶購,使用者同時點選搶購, 洶湧澎湃的流量可能導致服務宕機。如果系統扛不住怎麼辦?加機器?加機器不夠怎麼辦?業務降級,系統限流。對搶購介面進行流量控制,降低單位時間內對系統的訪問量,減少系統壓力,保持系統的可用性。以系統不被壓掛為第一要務。
令牌桶演算法
令牌桶演算法的原理是系統會以一個恆定的速度往桶裡放入令牌,而如果請求需要被處理,則需要先從桶裡獲取一個令牌,當桶裡沒有令牌可取時,則拒絕服務。
演算法描述:
/**
* 速率是每秒5個許可
*/
private final static RateLimiter rateLimiter = RateLimiter.create(5.0);
public static void main(String[] args) {
long a = System.currentTimeMillis();
System.out.println("啟動");
ExecutorService service = Executors.newCachedThreadPool();
List<Runnable> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(new Request("第" + (i + 1) + "次請求"));
}
//提交任務
submitTask(service, list);
long end = System.currentTimeMillis();
System.out.println("請求結束,耗時" + (end - a) + "ms");
//停止執行緒
service.shutdownNow();
}
public static void submitTask(ExecutorService exe, List<Runnable> tasks) {
for (Runnable task : tasks) {
// 從RateLimiter 獲取許可如果該許可可以在不超過timeout的時間內獲取得到的話,或者如果無法在timeout 過期之前獲取得到許可的話,那麼立即返回false(無需等待)
boolean time = rateLimiter.tryAcquire(200, TimeUnit.MILLISECONDS);
exe.execute(task);
System.out.println("獲取是否成功: " + time );
}
}
}
class Request implements Runnable {
private String name;
public Request(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(name);
}
} 執行之後的結果:
啟動
獲取是否成功: true
第1次請求
獲取是否成功: true
第2次請求
獲取是否成功: true
第3次請求
獲取是否成功: true
第4次請求
獲取是否成功: true
第5次請求
獲取是否成功: true
第6次請求
獲取是否成功: true
第7次請求
獲取是否成功: false
第9次請求
獲取是否成功: false
第8次請求
第10次請求
獲取是否成功: true
請求結束,耗時1401ms
參考文章連結:
令牌桶演算法的原理是系統會以一個恆定的速度往桶裡放入令牌,而如果請求需要被處理,則需要先從桶裡獲取一個令牌,當桶裡沒有令牌可取時,則拒絕服務。
-
假如使用者配置的平均傳送速率為r,則每隔1/r秒一個令牌被加入到桶中(每秒會有r個令牌放入桶中);
-
假設桶中最多可以存放b個令牌。如果令牌到達時令牌桶已經滿了,那麼這個令牌會被丟棄;
-
當一個n個位元組的資料包到達時,就從令牌桶中刪除n個令牌(不同大小的資料包,消耗的令牌數量不一樣),並且資料包被髮送到網路;
-
如果令牌桶中少於n個令牌,那麼不會刪除令牌,並且認為這個資料包在流量限制之外(n個位元組,需要n個令牌。該資料包將被快取或丟棄);
- 演算法允許最長b個位元組的突發,但從長期執行結果看,資料包的速率被限制成常量r。對於在流量限制外的資料包可以以不同的方式處理:(1)它們可以被丟棄;(2)它們可以排放在佇列中以便當令牌桶中累積了足夠多的令牌時再傳輸;(3)它們可以繼續傳送,但需要做特殊標記,網路過載的時候將這些特殊標記的包丟棄。
/**
* 速率是每秒5個許可
*/
private final static RateLimiter rateLimiter = RateLimiter.create(5.0);
public static void main(String[] args) {
long a = System.currentTimeMillis();
System.out.println("啟動");
ExecutorService service = Executors.newCachedThreadPool();
List<Runnable> list = new ArrayList<>();
for (int i = 0; i < 10; i++) {
list.add(new Request("第" + (i + 1) + "次請求"));
}
//提交任務
submitTask(service, list);
long end = System.currentTimeMillis();
System.out.println("請求結束,耗時" + (end - a) + "ms");
//停止執行緒
service.shutdownNow();
}
public static void submitTask(ExecutorService exe, List<Runnable> tasks) {
for (Runnable task : tasks) {
// 從RateLimiter 獲取許可如果該許可可以在不超過timeout的時間內獲取得到的話,或者如果無法在timeout 過期之前獲取得到許可的話,那麼立即返回false(無需等待)
boolean time = rateLimiter.tryAcquire(200, TimeUnit.MILLISECONDS);
exe.execute(task);
System.out.println("獲取是否成功: " + time );
}
}
}
class Request implements Runnable {
private String name;
public Request(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println(name);
}
} 執行之後的結果:
獲取是否成功: true
第1次請求
獲取是否成功: true
第2次請求
獲取是否成功: true
第3次請求
獲取是否成功: true
第4次請求
獲取是否成功: true
第5次請求
獲取是否成功: true
第6次請求
獲取是否成功: true
第7次請求
獲取是否成功: false
第9次請求
獲取是否成功: false
第8次請求
第10次請求
獲取是否成功: true
請求結束,耗時1401ms
//再用沒有限流的方法測試一遍 public static void submitTaskNoLimiter(ExecutorService exe, List<Runnable> tasks) { for (Runnable task : tasks) { exe.execute(task); } } } 啟動 第1次請求 第2次請求 第5次請求 第4次請求 第3次請求 第7次請求 第8次請求 第9次請求 第10次請求 請求結束,耗時6ms 第6次請求大家對以上結果進行比較一下,可以明顯的看出限流的作用了吧~
參考文章連結: