其實業(yè)務(wù)被攻擊過一次之后兴想,我就概覽過限流算法一次淹冰,當(dāng)時發(fā)現(xiàn)所用的庫主要是利用了Golang現(xiàn)成的標準庫來做的,沒很深入繼續(xù)研究下去被芳。
前幾周回頭看這個問題缰贝,發(fā)現(xiàn)這個庫的Readme赫然寫著“以前的版本弄錯啦,根本提供不了之前說的那些功能畔濒, 大家趕緊改改吧”剩晴。頓時心里一驚,決定這次抽空好好研究一下侵状。
簡單點說赞弥,常用的限流算法有兩種:令牌桶(token bucket)和漏桶(leaky bucket)。
漏桶:比較像街邊小攤的叫號趣兄,甭管排了多少人绽左,只能隔一小段時間買一次商品。
令牌桶:比較像餐館的叫號艇潭,本來餐館有一定的容量拼窥, 填滿之前,顧客都可以進蹋凝,填滿之后鲁纠,就跟漏桶差不多了。
一般情況下鳍寂,令牌桶就夠用了改含。你可以定義最初有多少個令牌(burst),并且定義令牌增加的頻率(每秒多少個)迄汛,每次請求來的時候看有沒有令牌可用捍壤。Golang庫里一個好的實現(xiàn)是tollbooth,除了實現(xiàn)限流算法外鞍爱,還附帶了很多方便的方法取ip鹃觉,header域等。另外硬霍,要說明一點帜慢,雖然令牌桶算法是一定時間放一個令牌,但是實現(xiàn)的時候,并不需要新開一個goroutine去隔一段時間增加計數(shù)(事實上粱玲,用戶量很小的情況下躬柬,完全可以這么做,見官方例子)抽减,而是邏輯上計算時間差對應(yīng)的令牌差額即可允青,詳細可見golang.org/x/time/rate
的實現(xiàn),對CPU和Memory非常友好卵沉,所以不用擔(dān)心并發(fā)量大了要怎么辦颠锉。
不過,上述包里的令牌桶算法有幾個限制:
- 從寫法上看史汗,頻率規(guī)則必須是以秒為單位琼掠。其實他們都會轉(zhuǎn)化成令牌增加速度。例如停撞,1秒10次瓷蛙,那么邏輯上令牌桶里面每0.1秒會增加一個令牌(除非已經(jīng)滿了)。你當(dāng)然可以設(shè)置成1分鐘60次戈毒,但最終都會轉(zhuǎn)化成1秒1次艰猬。
- 從定義上可以看出,它并不能精確限制每個時間單位的個數(shù)埋市。例如冠桃,定義桶里3個令牌, 每秒新增2個令牌道宅, 那么一秒內(nèi)(閉區(qū)間的話食听,意味著首尾跨越了一個間隔),可能有2(0 + 2污茵,對應(yīng)開始時是空桶)到 5(3 + 2碳蛋, 對應(yīng)開始時是滿桶)個令牌,設(shè)定值時省咨,需要注意這點。
有點迷糊玷室?不要慌零蓉,下面舉個栗子。比如穷缤,需要實現(xiàn)“每分鐘60個請求”敌蜂,可能令牌桶并不能特別好的勝任。 假設(shè)桶滿是20個(相當(dāng)于一個buffer)津肛, 每分鐘新增40章喉, 那么就是60個了。但是,爆發(fā)的最大值(幾乎同一時刻請求)其實達不到60秸脱, 因為桶最多就20落包。同時,如果桶空了摊唇,其后的一分鐘最多只能接受40個請求咐蝇。
所以, 爆發(fā)值太大巷查,會造成每分鐘的請求限制抖動很大有序。爆發(fā)值設(shè)置太小,又可能扛不住突然的大量訪問岛请。
這該如何是好旭寿?下面要講的東西還有一些,那就下回分解吧崇败。
更新:
重讀文章的時候發(fā)現(xiàn)之前的理解有誤盅称。
對于每分鐘60次這樣的限制,其實就是設(shè)置每秒1次的速率即可僚匆。桶大形⑶(Burst)只是用來處理突發(fā)的大請求的,即最多一次處理滿桶那么多請求咧擂。