redis實際應用-限流

程式設計師養成日記發表於2021-04-22

為什麼要做限流

首先讓我們先看一看系統架構設計中,為什麼要做“限流”。

旅遊景點通常都會有最大的接待量,不可能無限制的放遊客進入,比如故宮每天只賣八萬張票,超過八萬的遊客,無法買票進入,因為如果超過八萬人,景點的工作人員可能就忙不過來,過於擁擠的景點也會影響遊客的體驗和心情,並且還會有安全隱患;只賣N張票,這就是一種限流的手段

軟體架構中的服務限流也是類似,也是當系統資源不夠的時候,已經不足以應對大量的請求,為了保證服務還能夠正常執行,那麼按照規則,系統會把多餘的請求直接拒絕掉,以達到限流的效果

不知道大家注意過沒有,比如雙11,剛過12點有些顧客的網頁或APP會顯示下單失敗的提示,有些就是被限流掉了。

常見的限流演算法

計數法

顧名思義就是來一個,記錄一個,比如我1分鐘只能處理1000個請求,那麼我們就可以設定一個計數器,來一個請求就incr+1,當1分鐘之內的數量大於等於1000之後不處理了即可,虛擬碼如下

$redis = new Redis();
$redis->connect('127.0.0.1', 6379);
$rate_limit = 1000;  //限制個數
$rate_seconds = 60;  //限制時間
$redis_key = "redis_limit";
$count = $redis->get($redis_key);
if ($count >= $rate_limit){  //判斷60秒內請求個數是否已經達到上限
    //直接返回,不處理請求
    return
}
$redis->incr($redis_key, 1);//請求計數
$redis->expire($redis$rate_seconds); //設定過期時間 60s
//to do  業務邏輯處理.......

這種計數方式比較簡單快捷,但是有很大的缺點,因為請求的訪問不一定是很平穩的,如果0:59過來了1000個請求,1:01已經是下一個視窗,又過來了1000個請求,但實際上三秒內來了2000個請求,已經超過我們的限流上限了。所以這種方法是不推薦的。

滑動視窗演算法

還拿上面的例子,一分鐘分6份,每份10秒;每過10秒鐘,我們的時間視窗就會往右滑動一格,每個格子都有獨立的計數器,我們每次都計算時間視窗內的數量,可以解決計數器法中的問題,而且當滑動視窗的格子越多,那麼限流的統計就會越精確。具體可以參考下圖,看圖比較清晰 虛擬碼實現如下

function api_limit($scene,  $period$maxCount){
    $redis = new Redis();
    $redis->connect('127.0.0.1', 6379);
    $key = sprintf('hist:%s'$scene); //限流場景唯一標識
    $now = msectime();   // 毫秒時間戳,這樣更精確
    $pipe=$redis->multi(Redis::PIPELINE); //使用管道提升效能
    $pipe->zadd($key$now$now); //value 和 score 都使用毫秒時間戳
    $pipe->zremrangebyscore($key, 0, $now - $period); //移除時間視窗之前的行為記錄,剩下的都是時間視窗內的
    $pipe->zcard($key);  //獲取視窗內的行為數量
    $pipe->expire($key$period/1000 + 1);  //多加一秒過期時間
    $replies = $pipe->exec();
    return $replies[2] <= $maxCount;  //$replies[2]為zcard返回的個數  如果zcard結果大於maxCount,則不處理結果
}

for ($i=0; $i<20; $i++){  //測試限流是否實現程式碼
    var_dump(isActionAllowed("uniq_scene", 60*1000, 5)); //執行可以發現只有前5次是通過的
}

//返回當前的毫秒時間戳
function msectime() {
    list($msec$sec) = explode(' ', microtime());
    $msectime = (float)sprintf('%.0f', (floatval($msec) + floatval($sec)) * 1000);
    return $msectime;
 }

這段程式碼還是略顯複雜,需要讀者花一定的時間好好啃。它的整體思路就是:每一個行為到來時,都維護一次時間視窗。將時間視窗外的記錄全部清理掉,只保留視窗內的記錄。

因為這幾個連續的 Redis 操作都是針對同一個 key 的,使用 pipeline 可以顯著提升Redis 存取效率。但這種方案也有缺點,因為它要記錄時間視窗內所有的行為記錄,如果這個量很大,比如限定 60s 內操作不得超過 100w 次這樣的引數,它是不適合做這樣的限流的,因為會消耗大量的儲存空間

後面還有漏桶演算法和令牌桶演算法,由於各自的實現比較複雜,所以準備各自新開一篇文章單獨描述

相關文章