限流:通過對並發訪問/請求進行限速,或者對一個時間窗口內的請求進行限速來保護系統,一旦達到限制速率則可以拒絕服務、排隊或等待、降級等處理
1、計數法(固定時間窗口限流算法):
選定一個時間的起點,之后每當有接口請求到來,我們就將計數器加1,如果在當前時間窗口內,根據限流規則(每秒鍾允許100次訪問請求),出現累加訪問次數超過限流值情況,我們請拒絕后續訪問請求。當進入下一個時間窗口后,計數器就清零重新計數。
缺點:限流策略過於粗略,無法應對兩個時間窗口臨界時間內的突發流量
2、滑動時間窗口限流算法:
在任意1s的時間窗口內,接口的請求次數都不能大於K次。
維護一個K+1的循環隊列,用來記錄1s內到來的請求,【當隊列滿時,tail指向的位置實際上是沒有存儲數據的,所以循環隊列會浪費一個數組的存儲空間】
當有新的請求到來時,我們將與這個新請求的時間間隔超過1s的請求,從隊列中刪除。然后我們再來看循環隊列中是否有空閑位置。如果有,則把新請求存儲在隊列尾部,如果沒有,則說明1s內的請求次數已經超過了限流值K,所以這個請求被拒絕服務。
缺點:只能在選定時間粒度上限流,對選定時間粒度內的更加細粒度的訪問頻率不做限制。
循環隊列代碼:
/** * 隊空條件 head == tail * 隊滿條件 (tail + 1)% n == head * 當隊列滿時,tail指向的位置實際上是沒有存儲數據的,所以循環隊列會浪費一個數組的存儲空間。 */ public class CircularQueue { private String[] items; private int n; //隊列大小 private int head = 0; private int tail = 0; public CircularQueue(int capacity) { items = new String[capacity]; this.n = capacity; } public boolean enqueue(String item) { //隊列滿了 if ((tail + 1) % n == head) return false; items[tail] = item; tail = (tail + 1) % n; return true; } public String dequeue() { if (head == tail) return null; //head == tail 隊列是空 String ret = items[head]; head = (head + 1) % n; return ret; } }
常用的更平滑的限流算法:漏桶算法和令牌桶算法。
3、漏桶算法:
水(請求)先進入到漏桶里,漏桶以一定的速度出水(接口有響應速率),當水流入速度過大會直接溢出(訪問頻率超過接口響應速率),然后就拒絕請求可以看出漏桶算法能強行限制數據的傳輸速率。
缺點:對於突發的流量缺乏效率。
4、令牌桶:Google開源項目Guava中的RateLimiter使用的就是令牌桶控制算法。
系統會按恆定1/QPS時間間隔(如果QPS=100,則間隔是10ms)往桶里加入Token,如果桶已經滿了就不再加了。新請求來臨時,會各自拿走一個Token,如果沒有Token可拿了就阻塞或者拒絕服務。
好處:允許流量一定程度的突發。
可以方便的改變速度. 一旦需要提高速率,則按需提高放入桶中的令牌的速率. 一般會定時(比如100毫秒)往桶中增加一定數量的令牌, 有些變種算法則實時的計算應該增加的令牌的數量