令牌桶限流思路分享(PHP+Redis實現機制)
阿新 • • 發佈:2020-09-03
一 、場景描述
在開發介面伺服器的過程中,為了防止客戶端對於介面的濫用,保護伺服器的資源, 通常來說我們會對於伺服器上的各種介面進行呼叫次數的限制。比如對於某個 使用者,他在一個時間段(interval)內,比如 1 分鐘,呼叫伺服器介面的次數不能夠 大於一個上限(limit),比如說 100 次。如果使用者呼叫介面的次數超過上限的話,就直接拒絕使用者的請求,返回錯誤資訊。
服務介面的流量控制策略:分流、降級、限流等。本文討論下限流策略,雖然降低了服務介面的訪問頻率和併發量,卻換取服務介面和業務應用系統的高可用。
二、常用的限流演算法
1、漏桶演算法
漏桶(Leaky Bucket)演算法思路很簡單,水(請求)先進入到漏桶裡,漏桶以一定的速度出水(介面有響應速率),當水流入速度過大會直接溢位(訪問頻率超過介面響應速率),然後就拒絕請求,可以看出漏桶演算法能強行限制資料的傳輸速率.示意圖如下:
可見這裡有兩個變數,一個是桶的大小,支援流量突發增多時可以存多少的水(burst),另一個是水桶漏洞的大小(rate)。
因為漏桶的漏出速率是固定的引數,所以,即使網路中不存在資源衝突(沒有發生擁塞),漏桶演算法也不能使流突發(burst)到埠速率.因此,漏桶演算法對於存在突發特性的流量來說缺乏效率.
2、令牌桶演算法
令牌桶演算法(Token Bucket)和 Leaky Bucket 效果一樣但方向相反的演算法,更加容易理解.隨著時間流逝,系統會按恆定1/QPS時間間隔(如果QPS=100,則間隔是10ms)往桶裡加入Token(想象和漏洞漏水相反,有個水龍頭在不斷的加水),如果桶已經滿了就不再加了.新請求來臨時,會各自拿走一個Token,如果沒有Token可拿了就阻塞或者拒絕服務.
令牌桶的另外一個好處是可以方便的改變速度. 一旦需要提高速率,則按需提高放入桶中的令牌的速率. 一般會定時(比如100毫秒)往桶中增加一定數量的令牌, 有些變種演算法則實時的計算應該增加的令牌的數量.
三、基於PHP+Redis實現的令牌桶演算法
<?php
namespace Api\Lib;
/**
* 限流控制
*/
class RateLimit
{
private $minNum = 60; //單個使用者每分訪問數
private $dayNum = 10000; //單個使用者每天總的訪問量
public function minLimit($uid)
{
$minNumKey = $uid . '_minNum';
$dayNumKey = $uid . '_dayNum';
$resMin = $this->getRedis($minNumKey, $this->minNum, 60);
$resDay = $this->getRedis($minNumKey, $this->minNum, 86400);
if (!$resMin['status'] || !$resDay['status']) {
exit($resMin['msg'] . $resDay['msg']);
}
}
public function getRedis($key, $initNum, $expire)
{
$nowtime = time();
$result = ['status' => true, 'msg' => ''];
$redisObj = $this->di->get('redis');
$redis->watch($key);
$limitVal = $redis->get($key);
if ($limitVal) {
$limitVal = json_decode($limitVal, true);
$newNum = min($initNum, ($limitVal['num'] - 1) + (($initNum / $expire) * ($nowtime - $limitVal['time'])));
if ($newNum > 0) {
$redisVal = json_encode(['num' => $newNum, 'time' => time()]);
} else {
return [