1. Leaky Bucket (漏桶)
漏桶是一种常用的限流策略。NGINX 和 QEMU 中利用漏桶来实现限流。在漏桶模型中,桶的容量是固定的,当桶被流进的水填满时,多余的水就会溢出;虽然水可以以不同的速率流进桶中,但却必须以固定的速率从桶底部漏出。
可以将漏桶看成一个队列,队列有固定大小的容量,元素可以以任意速率入队,但是只能以固定速率出队。如果队列满了,要入队的元素将被丢弃。
算法伪代码:
leaky_bucket() now ← current_time() x ← counter - (now - last_confirm_time) * leak_rate if x >= bucket_size return false // reject the request counter ← max(0, x) + 1 last_confirm_time ← now return true // accept the request
漏桶可以保证请求进入其后端系统的速率,同时桶的容量限制了请求到达速率的峰值。
2. Token Bucket (令牌桶)
在令牌桶模型中,令牌以固定的速率进入桶中,如果桶已经被填满,那么后续填入的令牌会被丢弃。每个请求在进入后端系统时需要从桶中拿出一个令牌,如果桶中没有足够的令牌,那么请求就需要在队列中继续等待。
token_bucket(n_tokens) now ← current_time() x ← counter + (now - last_confirm_time) * fill_rate if x > bucket_size x ← bucket_size n_tokens_removed ← min(x, n_tokens) counter ← x - n_tokens_removed last_confirm_time ← now return n_tokens_removed
相对于漏桶算法,令牌桶算法更宽松,它允许在短时间内有大量的请求进入后端系统。也就是说,令牌桶允许 burst traffic
3. Fixed Window Counter
假设限流是限制一分钟内的请求数目,那么可以只对当前1分钟内的请求数目进行统计。我们只统计 [00:00:00, 00:00:59], [00:01:00, 00:01:59], ... [23:59:00, 23:59:59] 这样的时间段内的请求数目,在任意时刻,我们只需要一个计数器。
这种做法虽然简单,但是在一个1分钟长度的时间段内可能出现 2 * rate_limit 个请求。
例如,请求速率限制为 100 requests / min, 那么在 1 分钟的时间窗口内的请求数目就可以达到 200。很多时候,这并不是我们期望的。
4. Sliding Window Log
假设我们的限流算法的时间窗口大小为1分钟,我们可以维护一个unix时间截列表,记录了过去的 1 分钟时间段内的所有请求的到达时间, 这个列表的长度就是过去1分钟内接受的请求的数目。这一算法可能需要消耗很大的存储空间,假设我们允许的最大请求数目为 500 requests / min, 那么一个列表的长度就能达到 500。想一想,如果我们维护大量这种列表的话 ...
5. Sliding Window Counter
假设限流算法的时间窗口为1分钟,我们维护一个列表,列表的每一项包含两个数,一个是 unix 时间截,一个是 unix 时间截所表示的 1s 时间内的请求数目,那么 1 分钟时间窗口内的请求数目,就是这些列表项中的请求数目的和。列表的最大长度为 60。
这个算法的基本思路就是将时间窗口划分成更小的时间片,分别对这些时间片上的请求数目做统计,这就解决了上一种算法列表长度依赖于限流速率的问题,同时能够一定程度上缓解固定窗口计数的缺陷。
6. 一种近似算法
有一种非常简单的近似算法,我们维护两个计数,一个是上一分钟接受的请求数目,一个是本分钟内接受的请求数目,通过简单的计算我们可以估计在过去1分钟时间窗口内的请求数目。具体算法通过举例来说明。
假设我们允许在一分钟内的最大请求数目是 50, 即任意的 1 分钟长度的时间段内的请求数目不能超过50。
假设当前时间为 21:32:15, 那么上一分钟是 [21:31:00, 21:31:59],在这段时间内有 42 个请求。本分钟为 [21:32:00, 21:32:59], 本分钟过去了 15s,在这 15s 时间里我们已经接受了18个请求,由此,我们对在过去的 1 分钟 [21:31:16, 21:32:15] 时间窗口内请求速率可以做出如下估计:
estimated_rate = 42 * (60 - 15) / 60 + 18 = 49.5
如果有新的请求在 [21:31:15, 21:31:16) 到达, 若接收请求,估计值就会超过 50, 违反了请求速率限制。
参考
1. Nikrad Mahdi (April 12, 2017). "An Alternative Approach to Rate Limiting"
2. How we built rate limiting capable of scaling to millions of domains