保障服務(wù)穩(wěn)定之服務(wù)限流

一钱贯、前言

對于一個系統(tǒng)而言,最重要的要求之一肯定就是服務(wù)的穩(wěn)定性了侦另,一個不穩(wěn)定的系統(tǒng)可能給企業(yè)帶來巨大的損失秩命,包括經(jīng)濟(jì)和品牌等等方面的損失。

我們都希望系統(tǒng)能穩(wěn)定可靠地對外提供服務(wù)褒傅,響應(yīng)快弃锐,不宕機(jī),不故障殿托,但是在實(shí)際情況中霹菊,常常會遇到一些異常的情況,這就考驗(yàn)我們系統(tǒng)的穩(wěn)定性了碌尔。

今天就來講講保障服務(wù)穩(wěn)定性的手段之一的服務(wù)限流浇辜。

二、解決的問題

我們系統(tǒng)運(yùn)行過程中有時候可能會遇到突發(fā)異常大流量唾戚,如果系統(tǒng)無法正確處理這些突然涌入大量請求柳洋,就會導(dǎo)致系統(tǒng)輕則響應(yīng)慢,經(jīng)常超時叹坦,重則導(dǎo)致整個系統(tǒng)宕機(jī)熊镣,因此這就要求我們系統(tǒng)能以一定的策略處理大流量的涌入,這樣才不對被突發(fā)大流量壓垮,導(dǎo)致完全無法對外提供服務(wù)绪囱。

注意测蹲,這里大流量說的是突發(fā)異常大流量,是非正常情況鬼吵,所以我們的處理策略是對部分請求進(jìn)行丟棄或者排隊(duì)處理扣甲,保證我們系統(tǒng)對外還是可用的,而非全部請求都需要處理完齿椅。而對于系統(tǒng)正常的普通流量來說琉挖,如果其請求量逐漸達(dá)到了我們系統(tǒng)的負(fù)載能力的上限的話,這時候需要進(jìn)行的就是服務(wù)的擴(kuò)容涣脚,而不是限流并丟棄請求了示辈。

我們系統(tǒng)可能遇到的突發(fā)大流量的場景有很多,但統(tǒng)一的表現(xiàn)都是遣蚀,某些接口請求量激增矾麻,導(dǎo)致超過了系統(tǒng)的處理能力了,例如:

  • 突發(fā)熱點(diǎn)事件(例如微博熱搜)
  • 爬蟲
  • 惡意攻擊
  • 惡意刷單(如12306)
    ···

面對突發(fā)大流量芭梯,我們系統(tǒng)能使用的手段之一就是服務(wù)限流了险耀。限流是通過對一個時間段處理內(nèi)的請求量進(jìn)行限制來保護(hù)系統(tǒng),一旦達(dá)到限制速率則可以丟棄請求粥帚,從而控制了系統(tǒng)處理的請求量不會超過其處理能力胰耗。

限流可能在整個網(wǎng)絡(luò)請求過程的各個層面發(fā)生,例如nginx芒涡,業(yè)務(wù)代碼層等柴灯,這里主要介紹的是限流的思想,也就是限流算法费尽,并給出業(yè)務(wù)層的代碼實(shí)現(xiàn)例子赠群。

三、限流算法

1旱幼、計(jì)數(shù)器算法

計(jì)數(shù)器限流算法是比較簡單粗暴的算法查描,主要通過一個或者多個計(jì)數(shù)器來統(tǒng)計(jì)一段時間內(nèi)的請求總量,然后判斷是否超過限制柏卤,超過則進(jìn)行限流冬三,不超過則對應(yīng)的計(jì)數(shù)器數(shù)量加1。

計(jì)數(shù)器限流算法又可以分為固定窗口和滑動窗口兩種缘缚。

固定窗口

固定窗口計(jì)數(shù)器限流算法是統(tǒng)計(jì)固定一個時間窗口內(nèi)的請求總數(shù)來判斷是否進(jìn)行限流勾笆,例如限制每分鐘請求總數(shù)為100,則可以通過一個計(jì)數(shù)器來統(tǒng)計(jì)當(dāng)前分鐘的請求總數(shù)桥滨,每來一個請求判斷當(dāng)前分鐘對應(yīng)的計(jì)數(shù)器的數(shù)量窝爪,沒有超過限制則在當(dāng)前分鐘對應(yīng)的計(jì)數(shù)器加1弛车,超過則拒絕請求。

PHP實(shí)現(xiàn)邏輯如下:

/**
 * 固定窗口計(jì)數(shù)器限流算法
 * @param $key string 限流依據(jù)蒲每,例如uid纷跛,url等
 * @param $time int 限流時間段,單位秒
 * @param $limit int 限流總數(shù)
 */
function limit($key, $time, $limit) {

    //當(dāng)前時間所在分片
    $current_segment=floor(time() / $time);
    //按當(dāng)前時間和限流參數(shù)生成key
    $current_key = $key . '_' . $time . '_' . $limit . '_' . $current_segment;

    $redis = new Redis();
    //key不存在才設(shè)置邀杏,且設(shè)置過期時間
    $redis->set($current_key, 0, ['nx', 'ex' => $time]);
    $current = $redis->incr($current_key);

    //為了解決請求并發(fā)的問題贫奠,代碼實(shí)現(xiàn)上要先加再判斷
    if ($current > $limit) {
        return false;
    }
    return true;
}

缺點(diǎn)

固定窗口計(jì)數(shù)器限流算法實(shí)現(xiàn)起來雖然很簡單,但是有一個十分致命的問題淮阐,那就是臨界突刺問題:最后一秒和最開始1秒的流量集中一起叮阅,會出現(xiàn)大量流量刁品。

計(jì)數(shù)器的限制數(shù)量判斷是按時間段的泣特,在兩個時間段的交界時間點(diǎn),限制數(shù)量的當(dāng)前值會發(fā)生一個抖動的變化挑随,從而使瞬間流量突破了我們期望的限制状您。例如以下的情況:


可以看到在0:59的時候,如果突然來了100個請求兜挨,這時候當(dāng)前值是100膏孟,而到了1:00的時候,因?yàn)槭窍乱粋€時間段了拌汇,當(dāng)前值陡降到0柒桑,這時候又進(jìn)來100個請求,都能通過限流判斷噪舀,雖然兩個時間段平均下來還是沒超過限制魁淳,但是在臨界時間點(diǎn)的請求量卻達(dá)到了兩倍之多,這種情況下就可能壓垮我們的系統(tǒng)与倡。

滑動窗口

上面會出現(xiàn)突刺的問題其實(shí)就在于固定窗口算法的窗口時間跨度太大界逛,且是固定不變的,為了解決突刺的問題纺座,我們就有了滑動窗口計(jì)數(shù)器限流算法息拜。

滑動窗口算法是固定窗口算法的優(yōu)化版,主要有兩個特點(diǎn):

  • 劃分多個小的時間段净响,各時間段各自進(jìn)行計(jì)數(shù)少欺。
  • 根據(jù)當(dāng)前時間,動態(tài)往前滑動來計(jì)算時間窗口范圍馋贤,合并計(jì)算總數(shù)赞别。


可以看到,每次時間往后掸掸,窗口也會動態(tài)往后滑動氯庆,從而丟棄一些更早期的計(jì)數(shù)數(shù)據(jù)蹭秋,從而實(shí)現(xiàn)總體計(jì)數(shù)的平穩(wěn)過度。當(dāng)滑動窗口劃分的格子越多堤撵,那么滑動窗口的滑動就越平滑仁讨,限流的統(tǒng)計(jì)就會越精確。事實(shí)上实昨,固定窗口算法就是只劃分成一個格子的滑動窗口算法洞豁。

PHP實(shí)現(xiàn)邏輯如下:

/**
 * 滑動窗口計(jì)數(shù)器限流算法
 * @param $key string 限流依據(jù),例如uid荒给,url等
 * @param $time int 限流時間段丈挟,單位秒
 * @param $limit int 限流總數(shù)
 * @param $segments_num int 分段個數(shù)
 */
function limit($key, $time, $limit, $segments_num) {

    //小分片時間長度
    $segments_time=floor($time/$segments_num);
    //當(dāng)前時間所在小分片
    $current_segment=floor(time() / $segments_time);
    //按限流時間段生成key
    $current_key = $key . '_' . $time . '_' . $limit . '_' . $current_segment;
    
    $redis = new Redis();
    //先更新當(dāng)前時間所在小分片計(jì)數(shù),key不存在才設(shè)置志电,且設(shè)置過期時間
    $redis->set($current_key, 0, ['nx', 'ex' => $time]);
    $current = $redis->incr($current_key);

    for($window=$segments_time;$window<$time;$window+=$segments_time){
        $current_segment=$current_segment-1;
        $tmp_key = $key . '_' . $time . '_' . $limit . '_' . $current_segment;
        //計(jì)算時間窗口內(nèi)的總數(shù)
        $current+=intval($redis->get($tmp_key));
        if ($current > $limit) {
            //超過限制的話要回滾本次加的次數(shù)
            $redis->decr($current_key);
            return false;
        }
    }
    
    return true;
}

缺點(diǎn)

滑動窗口限流算法雖然可以保證任意時間窗口內(nèi)接口請求次數(shù)都不會超過最大限流值曙咽,但是相對來說對系統(tǒng)的瞬時處理能力還是沒有考慮到,無法防止在更細(xì)的時間粒度上訪問過于集中的問題挑辆,例如在同一時刻(同一秒)大量請求涌入例朱,還是可能會超過系統(tǒng)負(fù)荷能力。

2鱼蝉、漏桶算法

漏桶算法就是一種從系統(tǒng)的處理能力出發(fā)進(jìn)行限流的算法洒嗤。類似生活用到的漏斗,上面進(jìn)水魁亦,下面有個小口出水渔隶,當(dāng)請求進(jìn)來時,相當(dāng)于水倒入漏斗洁奈,然后從下端小口慢慢勻速的流出间唉。不管上面流量多大,下面流出的速度始終保持不變睬魂,超過漏斗容量的則丟棄终吼。漏桶算法以固定的速率釋放訪問請求(即請求通過),直到漏桶為空氯哮。


漏桶算法有兩個關(guān)鍵數(shù)據(jù):桶的容量V和流出的速率R际跪。假設(shè)每個請求的平均處理時間是S,最大超時時間是SS喉钢,則V/R+S<=SS姆打。

可以使用隊(duì)列來實(shí)現(xiàn),隊(duì)列設(shè)置最大容量肠虽,訪問請求先進(jìn)入隊(duì)列幔戏,隊(duì)列滿了的話就丟棄丟棄后續(xù)請求,然后通過另外一個woker以固定速率從隊(duì)列出口拿請求去處理税课。具體實(shí)現(xiàn)邏輯不展示了闲延。

缺點(diǎn)
漏桶算法的缺陷很明顯痊剖,由于出口的處理速率是固定的,當(dāng)短時間內(nèi)有大量的突發(fā)請求時垒玲,即便此時服務(wù)器沒有任何負(fù)載陆馁,每個請求也都得在隊(duì)列中等待一段時間才能被響應(yīng),因此漏桶算法無法應(yīng)對突發(fā)流量合愈。

3叮贩、令牌桶算法

令牌桶算法也是有一個桶,但是它不是通過限制漏出的請求來控制流量佛析,而是通過控制桶的令牌的生成數(shù)量來達(dá)到限流的目的的益老。令牌桶定時往桶里面丟一定的令牌,令牌桶滿了就不再往里面加令牌寸莫。每來一個請求就要先在桶里拿一個令牌捺萌,拿到令牌則通過,拿不到則拒絕储狭。

當(dāng)訪問量小于令牌桶的生成速率時互婿,令牌桶可以慢慢積累令牌直到桶滿,這樣當(dāng)短時間的突發(fā)訪問量來時辽狈,其積累的令牌數(shù)保證了大量請求都能立刻拿到令牌進(jìn)行后續(xù)處理。當(dāng)訪問量持續(xù)大量流入時呛牲,積累的令牌被消耗完了之后刮萌,后續(xù)請求又依賴于一定速率產(chǎn)生的新令牌,這時候就變成類似漏桶算法那樣的固定流量限制娘扩。

由此可見着茸,相比于漏桶算法,令牌桶算法能夠在限制調(diào)用的平均速率的同時還允許一定程度的突發(fā)調(diào)用琐旁。

PHP實(shí)現(xiàn)邏輯如下:

/**
 * 令牌桶限流算法
 * @param $key string 限流依據(jù)涮阔,例如uid,url等
 * @param $rate float 令牌生成速率灰殴,每秒$rate個
 * @param $volume int 容量
 * @return bool
 */
function limit($key, $rate, $volume) {

    //按限流參數(shù)生成key
    $current_key = $key . '_' . $rate . '_' . $volume;

    $redis = new Redis();
    $time=time();
    
    //沒有則初始化
    $redis->hSetNx($current_key, 'num', $volume);
    $redis->hSetNx($current_key, 'time', $time);

    //以下邏輯在高并發(fā)情況下要用lua腳本或者加分布式鎖敬特,這里僅用于說明算法的邏輯,就不考慮并發(fā)情況了
    //計(jì)算從上次到現(xiàn)在牺陶,需要添加的令牌數(shù)量
    $last=$redis->hMGet($current_key,['num','time']);
    $last_time=$last['time'];
    $last_num=$last['num'];
    $incr=($time-$last_time)*$rate;
    $current=min($volume,($last_num+$incr));//計(jì)算當(dāng)前令牌數(shù)
    
    if($current>0){
        $redis->hMSet($current_key,[
            'num'=>$current-1,
            'time'=>time()
        ]);
        return true;
    }
    
    return false;
}

上面的實(shí)現(xiàn)方案是令牌按時間回復(fù)數(shù)量伟阔,事實(shí)上令牌的生成也可以通過另外的服務(wù)去生成,這樣可以按一定策略去調(diào)控令牌的生成速率掰伸。

版權(quán)聲明

轉(zhuǎn)載請注明作者和文章出處
作者: X先生
http://www.reibang.com/p/b91dc5fd5d05

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末皱炉,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子狮鸭,更是在濱河造成了極大的恐慌合搅,老刑警劉巖多搀,帶你破解...
    沈念sama閱讀 218,755評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異灾部,居然都是意外死亡酗昼,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,305評論 3 395
  • 文/潘曉璐 我一進(jìn)店門梳猪,熙熙樓的掌柜王于貴愁眉苦臉地迎上來麻削,“玉大人,你說我怎么就攤上這事春弥∏河矗” “怎么了?”我有些...
    開封第一講書人閱讀 165,138評論 0 355
  • 文/不壞的土叔 我叫張陵匿沛,是天一觀的道長扫责。 經(jīng)常有香客問我,道長逃呼,這世上最難降的妖魔是什么鳖孤? 我笑而不...
    開封第一講書人閱讀 58,791評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮抡笼,結(jié)果婚禮上苏揣,老公的妹妹穿的比我還像新娘。我一直安慰自己推姻,他們只是感情好平匈,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,794評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著藏古,像睡著了一般增炭。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上拧晕,一...
    開封第一講書人閱讀 51,631評論 1 305
  • 那天隙姿,我揣著相機(jī)與錄音,去河邊找鬼厂捞。 笑死输玷,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的蔫敲。 我是一名探鬼主播饲嗽,決...
    沈念sama閱讀 40,362評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼奈嘿!你這毒婦竟也來了貌虾?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,264評論 0 276
  • 序言:老撾萬榮一對情侶失蹤裙犹,失蹤者是張志新(化名)和其女友劉穎尽狠,沒想到半個月后衔憨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,724評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡袄膏,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年践图,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片沉馆。...
    茶點(diǎn)故事閱讀 40,040評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡码党,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出斥黑,到底是詐尸還是另有隱情揖盘,我是刑警寧澤,帶...
    沈念sama閱讀 35,742評論 5 346
  • 正文 年R本政府宣布锌奴,位于F島的核電站兽狭,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏鹿蜀。R本人自食惡果不足惜箕慧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,364評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望茴恰。 院中可真熱鬧颠焦,春花似錦、人聲如沸琐簇。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,944評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽婉商。三九已至,卻和暖如春渣叛,著一層夾襖步出監(jiān)牢的瞬間丈秩,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,060評論 1 270
  • 我被黑心中介騙來泰國打工淳衙, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留蘑秽,地道東北人。 一個月前我還...
    沈念sama閱讀 48,247評論 3 371
  • 正文 我出身青樓箫攀,卻偏偏與公主長得像肠牲,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子靴跛,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,979評論 2 355