先舉個例子,說明為什么要做“限流”专肪。
旅游景點通常都會有最大的接待量刹勃,不可能無限制的放游客進入,比如故宮每天只賣八萬張票嚎尤,超過八萬的游客荔仁,無法買票進入,因為如果超過八萬人芽死,景點的工作人員可能就忙不過來乏梁,過于擁擠的景點也會影響游客的體驗和心情,并且還會有安全隱患关贵;
只賣 N 張票遇骑,這就是一種限流的手段。
軟件架構中的限流
軟件架構中的限流也是類似揖曾,也是當系統(tǒng)資源不夠的時候落萎,已經不足以應對大量的請求,為了保證服務還能夠正常運行炭剪,那么按照規(guī)則练链,系統(tǒng)會把多余的請求直接拒絕掉,以達到限流的效果奴拦;
對外限流:用戶過多媒鼓,或因為某個活動或熱點問題引發(fā)的訪問量的增加;惡意攻擊错妖,或被爬蟲抓取數據等等绿鸣。不知道大家注意過沒有,比如雙11暂氯,剛過12點有些顧客的網頁或APP會顯示下單失敗的提示潮模,有些就是被限流掉了。
對內限流:大部分公司株旷,系統(tǒng)和系統(tǒng)之間都會相互調用再登,假如 A 系統(tǒng) 被 X、Y晾剖、Z 三個系統(tǒng)調用锉矢,當 X 系統(tǒng)的訪問量徒增,導致 A 系統(tǒng)被調的掛掉了齿尽,那么會導致 Y沽损、Z 系統(tǒng)也無法正常使用(依賴于 A 系統(tǒng))。
計數器法
計數器法的原理就是限制單位時間內的處理請求數不超過閾值循头。
比如一個接口一分鐘可以處理 1000 次請求绵估,那么可以設置一個計數器,當有一次請求過來卡骂,計數器就加 1国裳,如果一分鐘以內計數器超過了 1000,那么后面再過來的請求就不再處理全跨;
但是這個方法的缺點也很明顯缝左,因為請求的訪問不一定是很平穩(wěn)的,如果 0:59 過來了 1000 個請求浓若,1:01 已經是下一個窗口渺杉,又過來了 1000 個請求,但實際上三秒內來了 2000 個請求挪钓,已經超過我們的限流上限了是越;
public class CounterTest {
public static void main(String[] args) {
long timeStamp = System.currentTimeMillis();
int timeCount = 1;
int reqCount = 0; //單位時間內的請求數量
int reqCountTotal = 0; //請求總數
final int limit = 10; // 時間窗口內最大請求數
final long interval = 1000; // 時間窗口ms
for(;;){
// 當前時間
long now = System.currentTimeMillis();
if (now < timeStamp + interval) {
//在之間窗口內
reqCount ++ ;
//沒有超過單位時間的請求數量
if(reqCount < limit){
reqCountTotal ++ ;
System.out.println(timeCount + " : " + reqCountTotal);
}
}else {
//下一個時間窗口
timeStamp = now;
reqCount = 0 ;
timeCount ++ ;
}
}
}
}
滑動窗口算法
還拿上面的例子,一分鐘分 6 份碌上,每份 10 秒倚评;每過 10 秒鐘,我們的時間窗口就會往右滑動一格馏予,每個格子都有獨立的計數器蔓纠,我們每次都計算時間窗口內的數量,可以解決計數器法中的問題吗蚌,而且當滑動窗口的格子越多腿倚,那么限流的統(tǒng)計就會越精確。
具體可以參考下圖蚯妇,看圖比較清晰:
漏桶算法
這個算法也很簡單敷燎,就是我們有一個固定容量的桶,有水流進來箩言,也有水流出去硬贯,我們不需要控制流進來的速度,只需要控制流出去的速度陨收,如果水流進來的太快饭豹,桶滿了鸵赖,多余的水會溢出去,并不會影響水流出去的速度拄衰。
令牌桶算法
還是有一個桶它褪,桶里面有 N 個令牌,所有的請求在處理之前都需要拿到一個可用的令牌才會被處理翘悉,如果桶里面沒有令牌的話茫打,則拒絕服務;令牌桶算法的原理是系統(tǒng)會以一個恒定的速度往桶里放入令牌妖混。
有人會把漏桶算法和令牌桶算法混淆老赤,而從某種意義上講,令牌桶算法確實是對漏桶算法的一種改進制市,兩者的不同之處在于:漏桶算法能夠強行限制數據的傳輸速率抬旺,而令牌桶算法能夠在限制數據的平均傳輸速率的同時還允許某種程度的突發(fā)傳輸;
也就是說祥楣,如果令牌桶中存在令牌嚷狞,則允許發(fā)送流量;而如果令牌桶中不存在令牌荣堰,則不允許發(fā)送流量床未;放入令牌的動作是持續(xù)執(zhí)行的,比如桶的大小為 100 振坚,當一直沒有請求的時候薇搁,桶內的令牌數量始終保持在 100 ,當下一秒有大量請求過來的時候渡八,可以瞬間將“攢下”的 100 個令牌處理掉啃洋,然后再按照恒定速度進行處理。
再安利一下 Google 開源的 guava 包屎鳍,使用 RateLimiter 我們可以很輕松的創(chuàng)建一個令牌桶算法的限流器宏娄。
import java.util.concurrent.Executors;
import com.google.common.util.concurrent.ListeningExecutorService;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.RateLimiter;
public class RateLimiterTest {
public static void main(String[] args) {
testRateLimiter();
}
public static void testRateLimiter() {
ListeningExecutorService executorService = MoreExecutors
.listeningDecorator(Executors.newFixedThreadPool(5));
RateLimiter limiter = RateLimiter.create(4); // 每秒不超過4個任務被提交
int num = 0 ;
for (;;) {
limiter.acquire(); // 請求RateLimiter, 超過permits會被阻塞
num ++ ;
executorService.submit(new Task("is "+ num));
}
}
}
class Task implements Runnable{
String str;
public Task(String str){
this.str = str;
}
@Override
public void run() {
System.out.println("Task call execute.." + str);
}
}
會點代碼的大叔 | 文【原創(chuàng)】