速率限制是一种策略,我们在工作中常常使用,它定义了系统在设定的时间框架内可以处理的最大请求数量。
速率限制定义了系统在指定时间段内可以处理的最大请求数量。
Image.png
速率限制是一种策略,我们在工作中常常使用,它定义了系统在设定的时间框架内可以处理的最大请求数量。
在选择算法时,我遇到了几种不同的方法。
选择合适的方法至关重要,它应该与您的系统的特定需求以及您试图解决的挑战相一致。
在速率限制方面,有一种方法我经常使用,这是由于它的简单性,对于本示例,我将在用户级别进行设置。
“那么‘在用户级别’到底是什么意思呢?” 实质上,它表示速率限制是为每个单独的用户定制的,确保为他们定制了独特的限制。想象一下,每个用户都被分配一个容量为5个令牌的桶,每当用户发出请求时,就会消耗1个令牌。在10分钟的时间内,系统被设计为能够在每个桶中重新填充3个令牌。如果用户的桶是空的,他们将无法再发出更多的请求,直到添加了一些令牌。以下是根据我的配置的详细说明:
Image.png
(1) 优点
我观察到这种方法的一个关键优势是它相当节约资源。它在内存上占用较少的空间,不会对CPU造成太大的负担。
“您提到每10分钟进行一次重新填充。如果有1000万用户,这不是很占资源吗?” 与其为每个用户不断重新填充令牌,您可以采用一种“懒惰重新填充策略”。这意味着只有在用户实际提交请求时才会更新令牌计数。
此外,该方法相对较容易实施。
它旨在支持突发行为。在闲置期间,令牌会积累到其最大限制,使用户偶尔能够快速连续发出多个请求。
(2) 缺点
调整参数可能需要一些权衡,因此确定理想的桶大小、重新填充速率和请求使用可能需要一些试验。
尽管允许突发行为可能是有利的,但它也有其缺点。存在用户在短时间内发送大量请求的潜在风险。为了避免突发行为,我们可以应用“漏桶”策略以实现更一致的工作流。
而令牌桶会快速处理每个请求,漏桶采用了不同的方法。对于这种方法,每个请求都有一个固定的处理时间。
想象一下漏桶的运作方式,就像一个队列,如果队列达到其容量,任何进入的请求都会被拒绝。
Image.png
“但为什么要给它起名字‘漏桶’呢?这里哪里有漏洞呢?” 想象一下,桶底部有一个小孔,可以让水稳定滴出。如果您比桶内的水流得更快,会发生什么?确切地说,它会溢出,任何多余的水,就像我们的多余请求一样,都会被丢弃。
(1) 它是如何运作的?
我们从一个最多可以容纳10个单位的桶开始,每个请求需要1秒来处理。
(2) 优点
(3) 缺点
“但如果一个读取请求只需要100毫秒,而我们的排水速度设置为1秒呢?” 请求仍然需要1秒处理。不过,可以调整设置:允许低于1秒的请求以其自然速度进行处理。然而,这样做可能会促使用户淹没系统,从而抵消了漏桶的优势,也许在这种情况下有更适合的方法。
“如果我们有一个较慢的请求,比如1秒,但桶的排水速率只有200毫秒呢?” 桶会继续释放该请求,但这可能是我们的排水速率设置得太低的一个指标,所以要小心。
移向基于时间的算法,让我们讨论一下固定窗口计数器。虽然听起来有所不同,但我们仍然可以使用我们熟悉的“桶”和“令牌”来解释这个概念。
每小时(这个时间很关键),客户端都会得到一个空的请求桶。每次他们发出请求时,一个令牌就会被放入这个桶中。一旦桶满了,就不允许再发出更多的请求。
(您可以想象客户端从带有令牌的桶开始,每次请求都会拿走一个令牌,没有令牌意味着不能再发出更多请求。)
1*EdG-thC8YV5khwiWGo-X-Q.png
“但这与令牌桶方法有什么不同呢?” 我理解您的困惑,这两种方法似乎都在随时间分配令牌。固定窗口计数器在小时结束时不考虑以前的令牌使用情况,计数器在每个窗口结束时重置为0,与以前的活动无关。相反,令牌桶方法根据剩余的令牌重新填充:previous_tokens += refill_rate(但始终在其最大容量内)。
(11) 它是如何运作的?
我们正在使用一个1分钟的窗口,并且我们的计数器的上限设置为10
(2) 优点
(3) 缺点
例如,对于一个1小时的窗口和一个10个请求的限制,客户端可以在7:59:59和8:00:00之间的过渡时刻发送20个请求
将滑动日志视为固定窗口的更好版本。它旨在处理窗口边缘的请求太多的问题。
与其坚持刚性的窗口,这个方法随着时间的推移而滑动。
对于每个传入的请求,我们的系统将迅速记下其时间戳。每个新的请求都会引发快速的回顾,以查看“过去N秒内有多少请求。
1*rGCamyRYi0ovqjvSozC0yQ.png
(1) 它是如何运作的?
想象一下,我们将时间窗口设置为10秒,并将其限制为3个请求,这意味着在任何给定的10
秒内只允许3个请求。
这个过程很清楚:删除旧日志,检查新请求,更新日志。
(2) 优点
(3) 缺点
对于滑动窗口,与滑动日志不同,我们略微简化了事情。我们关注的是最后一个窗口中的请求数量。
所以,如果你发现自己处于当前窗口的75%,你会权衡请求。25%来自上一个窗口,其余来自当前窗口:
权重 = (100 - 75)% * 上一个窗口的请求 + 当前窗口的请求
现在,当一个新请求试图加入这个过程时,你将这个权重加1(权重+1)。如果这个新的总数超过了我们设定的限制,我们必须拒绝这个请求。
1*js-77Ra-5xS-VVcVFlhdpw.png
它是如何运作的?
假设有一个每分钟10个请求的窗口。
让我们将这个过程分为两个阶段,窗口A为第一分钟,窗口B为第二分钟。
current_weight = 90% * A_counter + 0 = 7.2
这允许大约再添加2个请求(因为7.2 + 2 < 10)。但第三个请求呢?
很遗憾,它被拒绝了,B_counter现在为2。
“但如果一个客户在59秒时发送8个突发请求,然后在1分钟6秒时偷偷再发送2个,他们仍然能够通过,对吗?” 正是如此,这就是与滑动日志相比的一个小权衡。我们选择更少的计算工作和存储空间来换取更高的准确性。使用方程式(1 - 百分比通过)* 上一个窗口 + 当前窗口,我们猜测上一个窗口的请求在时间上是均匀分布的,而不是一次性全部到来。这是一个战略性的选择,旨在寻求一种更高效的方法,即使这意味着在准确性上有所减少。考虑到您的资源、如何管理突发流量、准确性以及您愿意处理多少复杂性,选择适合您需求的正确速率限制器。