什麼是介面限流
那麼什麼是限流呢?顧名思義,限流就是限制流量,包括並發的流量和一定時間內的總流量,就像你寬頻包了1個G的流量,用完了就沒了,所以控制你的使用頻率和單次使用的總消耗。透過限流,我們可以很好地控制系統的qps,從而達到保護系統或介面伺服器穩定的目的。
介面限流的常用演算法
計數器法
計數器法是限流演算法裡最簡單也是最容易實現的一種演算法。例如我們規定,對於A介面來說,我們1分鐘的訪問次數不能超過100個。那我們可以這麼做:在一開始的時候,我們可以設定一個計數器counter,每當一個請求過來的時候,counter就加1,如果counter的值大於100並且該請求與第一個請求的間隔時間還在1分鐘之內,那麼表示請求數過多;如果該請求與第一個請求的間隔時間大於1分鐘,且counter的值還在限流範圍內,那麼就重置counter,具體演算法的示意圖如下:
偽代碼如下:
class CounterDemo{ private $timeStamp; public $reqCount=0; public $limit=100;//时间窗口内最大请求数 public $interval=1000; //时间窗口 ms public function __construct() { $this->timeStamp = time(); } public function grant(){ $now=time(); if($now<$this->timeStamp+$this->interval){ //时间窗口内 $this->reqCount++; return $this->reqCount<=$this->limit; }else{ // 超时后重置 $this->timeStamp=time(); $this->reqCount=1; return true; } } }
這個演算法看起來簡單但是有個很嚴重的問題,那就是臨界問題,看下圖:
從上圖中我們可以看到,假設有一個惡意用戶,他在0:59時,瞬間發送了100個請求,並且1:00又瞬間發送了100個請求,那麼其實這個用戶在1秒裡面,瞬間發送了200個請求。我們剛才規定的是1分鐘最多100個請求,也就是每秒鐘最多1.7個請求,用戶透過在時間窗口的重置節點處突發請求, 可以瞬間超過我們的速率限制。用戶有可能透過演算法的這個漏洞,瞬間壓垮我們的應用。
相關推薦:《php教學》
聰明的朋友可能已經看出來了,剛才的問題其實是因為我們統計的精準度太低。那麼如何好好處理這個問題呢?或者說,如何將臨界問題的影響降低呢?我們可以看下面的滑動視窗演算法。
滑動視窗演算法
在上圖中,整個紅色的矩形框表示一個時間窗口,在我們的例子中,一個時間窗口就是一分鐘。然後我們將時間窗口劃分,比如圖中,我們就將滑動視窗 劃成了6格,所以每格代表的是10秒鐘。每過10秒鐘,我們的時間窗就會往右滑動一格。每個格子都有自己獨立的計數器counter,例如當一個請求 在0:35秒的時候到達,那麼0:30~0:39對應的counter就會加1。
那麼滑動視窗怎麼解決剛才的臨界問題的呢?我們可以看上圖,0:59到達的100個請求會落在灰色的格子中,而1:00到達的請求會落在橘黃色的格 子中。當時間到達1:00時,我們的視窗會往右移動一格,那麼此時時間視窗內的總請求數量一共是200個,超過了限定的100個,所以此時能夠偵測出來觸發了限流。
我再來回顧剛才的計數器演算法,我們可以發現,計數器演算法其實就是滑動視窗演算法。只是它沒有對時間窗口做進一步地劃分,所以只有1格。
由此可見,當滑動視窗的格子劃分的越多,那麼滑動視窗的滾動就越平滑,限流的統計就會越精確。
漏桶演算法
漏桶演算法,又稱為leaky bucket。為了理解漏桶演算法,我們來看看維基百科上的對於該演算法的示意圖:
從圖中我們可以看到,整個演算法其實十分簡單。首先,我們有一個固定容量的桶,有水流進來,也有水流出 去。對於流進來的水來說,我們無法預期總共有多少水會流進來,也無法預期水流的速度。但是對於流出去的水來說,這個桶子可以固定水流出的速率。而且,當桶滿了之後,多餘的水將會溢出。
我們將演算法中的水換成實際應用中的請求,我們可以看到漏桶演算法天生就限制了請求的速度。當使用了漏桶演算法,我們可以保證介面會以一個常速速率來處理請求。所以漏桶演算法天生不會有臨界問題。具體的偽代碼實作如下:
class LeakyDemo{ private $timeStamp; public $capacity;// 桶的容量 public $rate; // 水漏出的速度 public $water;// 当前水量(当前累积请求数) public function __construct() { $this->timeStamp = time(); } public function grant(){ $now=time(); $this->water=max(0,$this->water-($now-$this->timeStamp)*$this->rate);// 先执行漏水,计算剩余水量 $this->timeStamp=$now; if(($this->water+1)<$this->capacity){ // 尝试加水,并且水还未满 $this->water+=1; return true; }else{ // 水满,拒绝加水 return false; } } }
令牌桶演算法
令牌桶算法,又称token bucket。为了理解该算法,我们再来看一下维基百科上对该算法的示意图:
从图中我们可以看到,令牌桶算法比漏桶算法稍显复杂。首先,我们有一个固定容量的桶,桶里存放着令牌(token)。桶一开始是空的,token以一个固定的速率r往桶里填充,直到达到桶的容量,多余的令牌将会被丢弃。每当一个请求过来时,就会尝试从桶里移除一个令牌,如果没有令牌的话,请求无法通过。
具体的伪代码实现如下:
class TokenBucketDemo{ private $timeStamp; public $capacity;// 桶的容量 public $rate; // 令牌放入的速度 public $tokens;// 当前令牌的数量 public function __construct() { $this->timeStamp = time(); } public function grant(){ $now=time(); $this->tokens=min(0,$this->tokens+($now-$this->timeStamp)*$this->rate);// 先执行漏水,计算剩余水量 $this->timeStamp=$now; if($this->tokens<1){ // 若不到1个令牌,则拒绝 return false; }else{ // 还有令牌,领取令牌 $this->tokens -= 1; return true; } } }
临界问题
我们再来考虑一下临界问题的场景。在0:59秒的时候,由于桶内积满了100个token,所以这100个请求可以瞬间通过。但是由于token是以较低的 速率填充的,所以在1:00的时候,桶内的token数量不可能达到100个,那么此时不可能再有100个请求通过。所以令牌桶算法可以很好地解决临界问题。下图比较了计数器(左)和令牌桶算法(右)在临界点的速率变化。我们可以看到虽然令牌桶算法允许突发速率,但是下一个突发速率必须要等桶内有足够的token后才能发生。
总结
计数器算法是最简单的算法,可以看成是滑动窗口的低精度实现。滑动窗口由于需要存储多份的计数器(每一个格子存一份),所以滑动窗口在实现上需要更多的存储空间。也就是说,如果滑动窗口的精度越高,需要的存储空间就越大。
漏桶算法 VS 令牌桶算法
漏桶算法和令牌桶算法最明显的区别是令牌桶算法允许流量一定程度的突发。因为默认的令牌桶算法,取走token是不需要耗费时间的,也就是说,假设桶内有100个token时,那么可以瞬间允许100个请求通过。
令牌桶算法由于实现简单,且允许某些流量的突发,对用户友好,所以被业界采用地较多。当然我们需要具体情况具体分析,只有最合适的算法,没有最优的算法。
以上是php如何對api介面限流的詳細內容。更多資訊請關注PHP中文網其他相關文章!