@鄭昀匯總
創建日期:20120925
關鍵詞索引:
令牌桶算法,漏桶算法
背景:
防注冊機、秒殺器或掃號等常見電商流量過濾技術,一般具有如下要求:
1) 高性能。算法簡單高效,能對HTTP Requests進行實時在線處理。
2) 分類錯誤率低。尤其是盡量保證不誤殺正常顧客訪問。
3) 魯棒性強。由於雙方攻防的對抗性很強,所以算法必須適應各種類型的攻擊情形(包括DDoS攻擊)。
課題1:
對網站某一個URL/表單提交/Ajax請求的訪問進行實時檢測,找出過於頻繁請求的ip,對這些ip的訪問頻率進行限制。
課題2:
對網站開放平台訪問,對某一個開放接口的調用,有頻次約束,即針對單一App Key不得超過每小時150次調用。
翻譯一下:
鄭昀認為,我們希望限制住的是,在用M度量的任何時間周期內,某一個動作(action)的發生次數N。
英文關鍵詞:
rate limiter
rate limiting
throttle limiter
要控制的是 Average Rate :
解決思路:
推薦采用令牌桶算法的簡易實現。
參考資料:
一)
Leaky Bucket,漏桶算法。
圖1.1 漏桶算法示意圖
如圖1所示,桶本身具有一個恆定的速率往下漏水,而上方時快時慢地會有水進入桶中。當桶還未滿時,上方的水可以加入。一旦水滿,上方的水就無法加入了。桶滿正是算法中的一個的關鍵觸發條件(即流量異常判斷成立的條件)。
在桶滿水之后,常見的兩種處理方式為:
1)暫時攔截住上方水的向下流動,等待桶中的一部分水漏走后,再放行上方水。
2)溢出的上方水直接拋棄。
將水看作網絡通信中數據包的抽象,則
方式1起到的效果稱為Traffic Shaping,
方式2起到的效果稱為Traffic Policing(流量策略)。
由此可見,Traffic Shaping 的核心理念是“等待”,Traffic Policing 的核心理念是“丟棄”。它們是兩種常見的流速控制方法。
再回顧一下上面的圖,可以看出算法只需要兩個參數:
1)桶漏水的速率
2)桶的大小
算法核心:
利用桶模型判斷何時的流量達到異常了
外延:
1)流量異常時的處理方法:traffic policing v.s. traffic shaping
2)處理的數據包是否定長:定長 v.s. 變長
3)桶的大小是否等同於每個tick放行的水量:as a queue v.s. as a meter
二)
Token Bucket,令牌桶算法是網絡流量整形(Traffic Shaping)和速率限制(Rate Limiting)中最常使用的一種算法。
漏桶算法不夠靈活,因此加入令牌機制。
基本思想:
令牌桶在 traffic shaping 中的應用思想如下圖2.1所示。
圖2.1 CAR和CTS進行流量控制示意圖
我們主要關注約定訪問速率(CAR)模式,即:
a. 按特定的速率向令牌桶投放令牌;
b.根據預設的匹配規則先對報文進行分類,不符合匹配規則的報文不需要經過令牌桶的處理,直接發送;
c.符合匹配規則的報文,則需要令牌桶進行處理。當桶中有足夠的令牌則報文可以被繼續發送下去,同時令牌桶中的令牌量按報文的長度做相應的減少;
d.當令牌桶中的令牌不足時,報文將不能被發送(即丟棄),只有等到桶中生成了新的令牌,報文才可以發送。這就可以限制報文的流量只能是小於等於令牌生成的速度,達到限制流量的目的。
實現:
在數據結構上,沒有必要真的實現一個令牌桶。
基於時間的流逝生成受控制數量的令牌即可——以時間的流逝來洗滌舊跡,也就是將兩次發包或者收包的間隔和令牌數量聯系起來。
輔助理解的圖形:
令牌桶和漏桶算法最主要的差別在於:
漏桶算法能夠強行限制數據的傳輸速率,而令牌桶算法能夠在限制數據的平均傳輸速率的同時還允許某種程度的突發傳輸。
在令牌桶算法中,只要令牌桶中存在令牌,那么就允許突發地傳輸數據直到達到用戶配置的門限,因此它適合於具有突發特性的流量。
三)
http://developer.linkedin.com/documents/throttle-limits 這是常見的開放平台限制請求速率的方式。
LinkedIn 比較好的一點就是把
Application throttles和
Developer throttles分開了。后者是方便聯調測試的。
六)Token Bucket 算法的 Python 實現一:kombu.utils.limits.py
代碼:https://github.com/celery/kombu/blob/master/kombu/utils/limits.py
對此實現一個較為早期的解釋:
http://code.activestate.com/recipes/511490/
即,每次外界調用 _get_tokens 方法時,才會查一下需要追加多少token。
class
TokenBucket
(
object
):
def
_get_tokens
(
self
):
if
self
.
_tokens
<
self
.
capacity
:
now
=
time
.
time
()
delta
=
self
.
fill_rate
*
(
now
-
self
.
timestamp
)
self
.
_tokens
=
min
(
self
.
capacity
,
self
.
_tokens
+
delta
)
self
.
timestamp
=
now
return
self
.
_tokens
消耗令牌則是通過 consume 函數,指明本次消耗多少張令牌:
def consume(self, tokens): """Consume tokens from the bucket. Returns True if there were sufficient tokens otherwise False.""" if tokens <= self.tokens: self._tokens -= tokens else: return False return True
代碼實現:https://github.com/simonw/ratelimitcache/blob/master/ratelimitcache.py
它明確提出了防字典攻擊防掃號的目的。
既可限制住ip,也可限制住其他字段如 username 。
八)Token Bucket 算法的node.js實現
jhurliman/node-rate-limiter 給出了一個非常便於理解的 Token 消耗方式:
下面是 150次請求/次 范例,每1次請求消耗1個token:
var RateLimiter = require('limiter').RateLimiter;
// Allow 150 requests per hour (the Twitter search limit). Also understands
// 'second', 'minute', 'day', or a number of milliseconds
var limiter = new RateLimiter(150, 'hour');
// Throttle requests
limiter.removeTokens(1, function(err, remainingRequests) {
// err will only be set if we request more than the maximum number of
// requests we set in the constructor
// remainingRequests tells us how many additional requests could be sent
// right this moment
callMyRequestSendingFunction(...);
});
下面是150KB/sec 范例
,每1個字節的傳輸就消耗1個token
:
var BURST_RATE = 1024 * 1024 * 150; // 150KB/sec burst rate
var FILL_RATE = 1024 * 1024 * 50; // 50KB/sec sustained rate
var TokenBucket = require('limiter').TokenBucket;
// We could also pass a parent token bucket in as the last parameter to
// create a hierarchical token bucket
var bucket = new TokenBucket(BURST_RATE, FILL_RATE, 'second', null);
bucket.removeTokens(myData.byteLength, function() {
sendMyData(myData);
});
九)StackOverflow 上的相關討論:




