什麼是限流
限流就是對某一段時間窗口內的請求數進行控制,保持系統的可用性和穩定性。
防止因爲流量的暴增而導致系統奔潰。
很多平臺都有限流,例如我們調用第三方的api的時候,對方都會限制每天最多隻能調用幾次之類的。
常用的限流方式
1)信號量
2)令牌桶
信號量
Semaphore是一個計數信號量。常用於限制獲取某資源的線程數量,
可用java的Semaphore 實現。
原理就是先定義總共有多少個信號量,然後每個線程進來拿到幾個信號量纔可以成功訪問,這樣就允許了同時有多少個線程訪問。
例如,定義了6個信號量,每個線程進來拿2個,那麼同時允許3個線程成功訪問。
通過acquire()方法獲取許可,該方法會阻塞,直到獲取許可爲止。
通過release()方法釋放許可。
代碼實現如下
@RestController
@RequestMapping("/semaphore")
public class SemaphoreController {
//初始化信號量=5,true-公平信號量FIFO。
private Semaphore semaphore = new Semaphore(5, true);
//該方法是阻塞的,直到獲取許可爲止
@RequestMapping("/acquire/{par1}")
public String acquire(@PathVariable String par1){
try {
semaphore.acquire(1);//獲取一個許可
//
return par1+"獲取一個許可";
}catch (InterruptedException e){
e.printStackTrace();
return "獲取許可失敗";
}catch (Exception e1){
return "獲取許可失敗";
}finally {
semaphore.release(1);//處理完後,釋放一個許可
}
}
}
令牌桶
首先看下來自網絡的圖片
令牌桶法的過程大概如下:
1)程序按照一定的速度生成令牌,並且放到令牌桶中;
2)令牌桶容量是一定的,如果桶滿了,就丟掉多餘的令牌;
3)如果桶沒滿,就繼續放令牌
4)請求過來的時候,拿到令牌就請求接口成功,拿不到令牌就拒絕訪問。
令牌桶的實現谷歌的guava已經幫我們實現了,就是谷歌的 RateLimiter。
接下來,我們看下代碼實現:
//每秒發放2個令牌
private static final RateLimiter rateLimiter = RateLimiter.create(2);
//以下是測試限流的接口
//一般是採用阻塞和非阻塞的方式來獲取令牌,建議採用非阻塞。
/**
* 非阻塞方式
* 嘗試獲取令牌,默認超時時間是0,拿不到就立即返回false
*/
public String rateLimiterNonBlock1(){
if (rateLimiter.tryAcquire()) {//tryAcquire嘗試獲取令牌,默認超時時間是0,意思是拿不到就立即返回false。而且tryAcquire()只拿一個令牌
try {
//模擬操作。
Thread.sleep(500);
}catch (Exception e){
e.printStackTrace();
}
}else{//拿不到令牌
return "拿不到令牌,暫時無法訪問。";
}
return "正常拿到了令牌,成功訪問";
}
/**
* 非阻塞方式
* 嘗試獲取令牌,超時時間是1秒,如果在1000ms內無法獲取令牌,則直接返回false,無需等待
*/
public String rateLimiterNonBlock2() {
//如果令牌桶內有令牌,則直接發放;判斷如果能在1000ms內能夠獲取一個令牌,則等待到時後發放;如果在1000ms內無法獲取令牌,則直接返回false,無需等待。
if (rateLimiter.tryAcquire(1000, TimeUnit.MILLISECONDS)) {
//do sth
return "獲取令牌成功";
} else {
// do other
return "獲取令牌失敗";
}
}
/**
* 阻塞方式
* acquire拿不到就等待,直到一次拿到5個令牌爲止
*/
public String reateLimiterBlock(){
rateLimiter.acquire(5); // 一次拿5個
return "";
}
注意:!!!!!
tryAcquire 並不是真正等待timeout時間,而是計算判斷在timeout時間內能否拿到令牌,如果不能就返回false。這是一個挺複雜的數學問題,每一個請求都會被計算未來可能獲取令牌的概率。還好谷歌幫我們實現了這個判斷。
令牌桶也有大神封裝好的可以直接用的,而且還封裝有redis實現的分佈式的限流方式。見下面傳送門。
apiBoot項目傳送門
apiBoot文檔傳送門
如果有幫助到你,就關注或者點贊吧。感謝~~