CHAPTER 4: 《DESIGN A RATE LIMITER》第4章 《设计一个限速器》

DESIGN A RATE LIMITER

在网络系统中,速率限制器用于控制客户端或网络发送的流量速率服务。在HTTP世界中,速率限制器限制允许的客户端请求的数量在指定的时间段内发送。方法定义的阈值速率限制器,所有超额呼叫都被阻塞。
下面是一些例子:

  • 一个用户每秒最多可以写2篇文章。
  • 每天最多可以从同一个IP地址创建10个帐户。
  • 每周从同一设备领取奖励不超过5次。

在本章中,要求你设计一个速率限制器。在开始设计之前,我们首先看一下使用API速率限制器的好处:

  • 防止DoS (Denial of Service)攻击导致的资源饥饿[1]。几乎所有的大型科技公司发布的api强制执行某种形式的速率限制。例如,Twitter将tweet的数量限制为每3小时300条。谷歌docs api有以下默认限制:每个用户每60秒300个读请求[3]。速率限制器防止DoS攻击,无论是有意的还是无意的,通过阻止多余的调用。
  • 降低成本。限制多余的请求意味着更少的服务器和分配更多的服务器将资源分配给高优先级api。速率限制对于那些使用付费的第三方api。例如,您是按每次通话收费的以下外部api:检查信用、付款、检索健康记录等。限制通话次数对降低成本至关重要。
  • 防止服务器过载。为了减少服务器负载,使用速率限制器过滤掉由机器人或用户不当行为引起的多余请求。

步骤1 -理解问题并确定设计范围

速率限制可以使用不同的算法来实现,每种算法都有其优点和缺点,面试官和候选人之间的互动有助于我们理清限制速率的类型都在哪些方面进行设置。

候选人:我们要设计什么样的限速器?它是客户端的汇率限制器还是服务器端API速率限制器?
采访者:问得好。我们关注的是服务器端API速率限制器。
候选人:速率限制器是否基于IP、用户ID或其他限制API请求属性呢?
采访者:速率限制器应该足够灵活,以支持不同的节流阀规则。
候选人:系统的规模是多少?它是为创业公司还是大公司设计的庞大的用户基础?
面试官:系统必须能够处理大量的请求。
候选人:系统能在分布式环境中工作吗?
面试官:是的。
候选人:速率限制器是单独的服务还是应该在应用程序中实现代码?
采访者:这是你的设计决定。
候选人:我们需要通知被节流的用户吗?
面试官:是的。

需求

以下是对系统需求的总结:

  • 准确地限制过多的请求。
  • 低延迟。速率限制器不应该减慢HTTP响应时间。
  • 使用尽可能少的内存。
  • 分布式速率限制。速率限制器可以在多个服务器之间共享流程。
  • 异常处理。当用户的请求被限制时,向用户显示清晰的异常。
  • 高容错性。如果速率限制器有任何问题(例如,缓存服务器脱机),它不会影响整个系统。

步骤2 -提出高层次的设计并获得支持

让我们保持简单,使用基本的客户机和服务器模型进行通信。速率限制器放在哪里?直观地说,您可以在客户机或服务器端实现速率限制器。

  • 客户端实现。一般来说,客户端是一个不可靠的执行场所速率限制,因为客户端请求很容易被恶意行为者伪造。此外,我们可能无法控制客户端实现。
  • 服务器端实现。figure4-1所示为服务器端的限速器。

在这里插入图片描述除了客户机和服务器端实现之外,还有一种替代方法。而不是在API服务器上放置速率限制器,我们创建速率限制器中间件,它进行节流如figure4-2所示。
在这里插入图片描述让我们用figure4-3中的一个例子来说明在这种设计中速率限制是如何工作的。假设我们的API允许每秒2个请求,并且客户端向服务器发送3个请求一秒钟之内。前两个请求被路由到API服务器。但是,速率限制器中间件限制第三个请求并返回HTTP状态码429。HTTP 429响应状态码表示用户发送了过多的请求。
在这里插入图片描述
云微服务[4]已经广泛流行,速率限制通常是在一个称为API网关的组件中实现。API网关是完全托管的支持速率限制、SSL终止、身份验证、IP白名单、服务的服务静态内容等。现在,我们只需要知道API网关是一个中间件支持速率限制。在设计速率限制器时,要问自己的一个重要问题是:它应该在哪里?在服务器端还是在网关中实现速率限制器?没有绝对的答案。这取决于你公司目前的技术水平、工程资源、优先级,目标,等等。
以下是一些一般性的指导原则:

  • 评估你目前的技术栈,如编程语言,缓存服务,等。确保您当前的编程语言能够有效地实现速率限制在服务器端。
  • 确定适合您业务需求的速率限制算法。当你实施服务器端的所有东西,你都可以完全控制算法。然而,你的如果使用第三方网关,选择可能有限。
  • 如果你已经使用了微服务架构,并且包含了一个API网关设计执行身份验证,IP白名单等,您可以添加速率限制器API网关。
  • 建立自己的限价服务需要时间。如果你没有足够的工程资源来实现速率限制器,一个商业化的API网关是更好的选择。

速率限制算法

速率限制可以使用不同的算法来实现,每种算法都有不同的特点尽管本章不关注算法,但理解它们也很重要高级有助于选择适合我们使用的正确算法或算法组合用例。
下面是一些流行的算法:

  • 令牌桶算法
  • 漏桶算法
  • 固定窗口计数器
  • 滑动窗测井
  • 滑动窗口计数器

令牌桶算法

令牌桶算法被广泛用于速率限制。它很简单,很容易理解,而且通常被互联网公司使用。Amazon[5]和Stripe[6]都使用该算法限制他们的API请求。
令牌桶算法的工作原理如下:

  • 令牌桶是具有预定义容量的容器。令牌被放入桶中定期以预设的速率。一旦桶填满,就不会再添加令牌。如图所示figure4-4中,令牌桶容量为4。填充者将2个令牌放入桶中第二。一旦桶满了,额外的令牌就会溢出。
    在这里插入图片描述
  • 每个请求消耗一个令牌。当请求到达时,我们检查是否有足够的数量桶中的令牌。原理如figure4-5所示。
  • 如果有足够的令牌,我们为每个请求取出一个令牌,并且请求经过。
  • 如果没有足够的令牌,请求将被丢弃。
    在这里插入图片描述
    figure4-6说明了令牌消耗、填充和速率限制逻辑的工作原理。在这个例如令牌桶大小为4,填充速率为每1分钟4个。
    在这里插入图片描述
    figure4-6说明了令牌消耗、填充和速率限制逻辑的工作原理。令牌桶算法接受两个参数:
  • 桶大小:桶中允许的令牌的最大数量
  • 填充率:每秒放入桶中的令牌数量我们需要多少个桶?这取决于速率限制规则。在这里举几个例子。
  • 通常有必要为不同的API端点设置不同的桶。例如,如果允许用户每秒发布一个帖子,那么每天添加150个好友,每个帖子添加5个赞其次,每个用户需要3个bucket。
  • 如果我们需要基于IP地址节流请求,每个IP地址需要一个桶。
  • 如果系统允许每秒最多10,000个请求,那么有意义所有请求共享的全局桶。

优点:

算法易于实现。

  • 内存效率高。
  • 令牌桶允许短时间的流量爆发。一个请求可以通过这么长时间因为还有代币。

缺点:

  • 算法中的两个参数是桶大小和令牌填充率。然而,能合适的地调整它们是具有挑战性的。在这个例如令牌桶大小为4,填充速率为每1分钟4个。

漏桶算法

泄漏桶算法类似于令牌桶,不同之处在于请求被处理以固定利率。它通常使用先进先出(FIFO)队列来实现。该算法工作内容如下:

  • 当请求到达时,系统检查队列是否已满。如果未满,则请求添加到队列中。
  • 否则,请求将被丢弃。
  • 请求从队列中取出并定期处理。实现原理如figure4-7所示。
    在这里插入图片描述
    漏桶算法接受以下两个参数:
  • 桶大小:等于队列大小。队列保存要处理的请求固定利率。
  • 流出率:它定义了在固定速率下可以处理多少请求,通常为秒。
    电商公司Shopify使用漏桶来限制流量 [7].。

优点:

给定有限队列大小的内存效率。

  • 请求以固定的速率处理,因此它适合于稳定的用例流出率是必需的。

缺点:

  • 突发的流量填满了队列中的旧请求,如果它们没有被处理时间,最近的请求将受到限制。
  • 算法中有两个参数。正确地调整它们可能并不容易。

固定窗口计数器算法

固定窗口计数器算法工作原理如下:

  • 该算法将时间轴划分为固定大小的时间窗口,并为其分配计数器每个窗口。
  • 每个请求将计数器加1。
  • 一旦计数器达到预定义的阈值,新的请求将被丢弃,直到一个新的时间窗口开始了。让我们用一个具体的例子来看看它是如何工作的。在figure4-8中,时间单位为1秒系统每秒最多允许3个请求。在第二个窗口中,如果更多如figure4-8所示,超过3个请求将被丢弃。
    在这里插入图片描述
    该算法的一个主要问题是,在时间窗口的边缘会产生突发的流量可能导致超过允许配额的请求通过。考虑以下情况:
    在这里插入图片描述在figure4-9中,系统每分钟最多允许处理5个请求,以及可用配额重置在人类友好的回合分钟。如所见,在2:00:00之间有五个请求2:01:00和2:01:00到2:02:00之间还有五个请求。一分钟的窗口在2:00:30到2:01:30之间,有10个请求通过。这是允许数量的两倍请求。

优点:

  • 内存效率高。
  • 易于理解。
  • 在单位时间窗口结束时重置可用配额适合某些用例。

缺点:

  • 窗口边缘的流量峰值可能导致比允许的更多的请求要通过的配额。

滑动窗口测井算法

如前所述,固定窗口计数器算法有一个主要问题:它允许更多的请求通过窗口的边缘。滑动窗口对数算法修复此问题。
它的工作原理如下:

  • 算法跟踪请求时间戳。时间戳数据通常保存在缓存,如Redis[8]的排序集。
  • 当一个新请求进来时,删除所有过时的时间戳。过时的时间戳定义为比当前时间窗口的开始时间更早的那些。
  • 在日志中添加新请求的时间戳
  • 如果日志大小等于或低于允许的数量,则接受请求。否则,它将被拒绝。

我们用figure4-10所示的示例来解释该算法。
在这里插入图片描述
在本例中,速率限制器每分钟允许2个请求。通常,Linux时间戳是存储在日志中。但是,我们的示例中使用了人类可读的时间表示形式更好的可读性。

  • 当新请求在1:00:01到达时,日志为空。因此,请求是允许的。
  • 一个新的请求在1:00:30到达,时间戳1:00:30被插入到日志中。后插入时,日志大小为2,不大于允许的计数。因此,请求是允许的。
  • 一个新的请求在1:00:50到达,时间戳插入到日志中。后插入时,日志大小为3,大于允许大小2。因此,此请求被拒绝即使时间戳保留在日志中。
  • 一个新的请求在1:01:40到达。[1:00:40,1:01:40]范围内的请求在最近的时间框架,但在1:00:40之前发送的请求已过时。两个过时的时间戳,1:00:01和1:00:30,从日志中删除。删除操作后,日志大小变成2;因此,请求被接受。

优点:

  • 该算法实现的速率限制非常精确。在任何滚动的窗口中,请求不会超过速率限制。

缺点:

  • 该算法消耗大量内存,因为即使请求被拒绝,它也会占用大量内存时间戳可能仍然存储在内存中

滑动窗口计数算法

滑动窗口计数器算法是一种结合固定窗口的混合算法计数器和滑动窗原木。该算法可以通过两种不同的方式实现方法。我们将在本节中解释一个实现,并为本节末尾的其他实现。实现原理如figure4-11所示的工作原理。
在这里插入图片描述
假设速率限制器每分钟最多允许7个请求,并且有5个请求前一分钟,3分钟。对于一个30%的新请求在当前分钟的位置,计算滚动窗口中的请求数
使用以下公式:

  • 当前窗口中的请求+前一个窗口中的请求*的重叠百分比滚动窗口和前一个窗口
  • 使用这个公式,我们得到3 + 5 * 0.7% = 6.5请求。根据用例的不同数字可以四舍五入,也可以四舍五入。在我们的示例中,它被四舍五入到6。

由于速率限制器每分钟最多允许7个请求,所以当前请求可以离开
通过。但是,在再收到一个请求后,将达到该限制。由于篇幅限制,我们将不在这里讨论另一个实现。感兴趣读者应参阅参考资料[9]。

这个算法并不完美。它有优点和缺点。

优点
  • 它平滑了高峰流量,因为速率是基于平均速率以前的窗口。
  • 内存效率高。

缺点

  • 它只适用于不那么严格的后视窗。这是实际比率的近似值因为它假设前一个窗口中的请求是均匀分布的。然而,这问题可能不像看上去那么糟。根据Cloudflare b[10]的实验,在4亿个请求中,只有0.003%的请求被错误地允许或限速。

高级体系结构

速率限制算法的基本思想很简单。在高层,我们需要对抗跟踪从同一用户、IP地址等发送的请求数量。如果计数器是大于限制,请求被拒绝。我们把柜台放在哪里?由于磁盘速度慢,使用数据库不是一个好主意访问。之所以选择内存缓存,是因为它速度快,并且支持基于时间的过期策略。
例如,Redis[11]是实现速率限制的一个流行选项。它是一个位于中的内存存储,提供两个命令:INCR和EXPIRE。

  • INCR:使存储的计数器增加1。
  • EXPIRE:设置计数器的超时时间。如果超时,则计数器为自动删除。figure4-12显示了速率限制的高级架构,其工作原理如下:
    在这里插入图片描述
  • 客户端向速率限制中间件发送请求。
  • 速率限制中间件从Redis和对应的bucket中获取计数器检查是否达到限制。
  • 如果达到限制,请求将被拒绝。
  • 如果没有达到限制,请求被发送到API服务器。同时,系统增加计数器并将其保存回Redis。

步骤3 -设计深度潜水

figure4-12中的高层设计没有回答以下问题:

  • 速率限制规则是如何创建的?规则存储在哪里?
  • 如何处理速率受限的请求?

在本节中,我们将首先回答有关速率限制规则的问题,然后继续讨论处理速率限制请求的策略。最后,我们将讨论速率限制分布式环境,详细设计,性能优化和监控

速率限制规则

Lyft开源了他们的限速组件[12]。我们将窥探组件的内部,让我们来看看一些限速规则的例子:

domain: messaging
descriptors:
 - key: message_type
 Value: marketing
 rate_limit:
 unit: day
 requests_per_unit: 5

在上面的示例中,系统配置为最多允许5条营销消息每一天。下面是另一个例子:

domain: auth
descriptors:
 - key: auth_type
 Value: login
 rate_limit:
 unit: minute
 requests_per_unit: 5

该规则表示客户端在1分钟内登录次数不能超过5次。规则是一般写在配置文件中,保存在磁盘上。

超过速率限制

如果请求受到速率限制,api将返回HTTP响应代码429(请求太多)。给客户。根据用例的不同,我们可能会对速率受限的请求进行排队稍后处理。例如,如果某些订单由于系统过载而受到速率限制,我们可能会把这些订单留到以后处理。

速率限制头

客户端如何知道它是否正在被节流?客户是怎么知道在节流之前允许的剩余请求数?答案就在HTTP中响应标头。速率限制器向客户端返回以下HTTP头:
x - ratlimit - remaining:窗口内允许的剩余请求数。
x - ratlimit - limit:表示客户端每个时间窗口可以调用多少次。
x - ratlimit - retry - after:等待重试请求的秒数没有节流。
当用户发送了过多的请求时,会出现429 too many requests错误和X-Ratelimit重试后头返回给客户端。

详细设计

系统详细设计如figure4-13所示。
在这里插入图片描述

  • 规则存储在磁盘上。工作人员经常从磁盘中提取规则并存储它们在缓存中。
  • 当客户端向服务器发送请求时,请求被发送到速率限制器中间件。
  • 速率限制器中间件从缓存加载规则。它获取计数器和最后一个请求从Redis缓存中获取时间戳。限速器根据响应决定:
  • 如果请求没有速率限制,它将被转发到API服务器。
  • 如果请求是速率限制的,速率限制器返回429请求过多错误客户端。同时,请求要么被丢弃,要么被转发到队列。

分布式环境中的速率限制器

构建一个在单个服务器环境中工作的速率限制器并不困难。然而,扩展系统以支持多个服务器和并发线程是另一回事。
有两个挑战:

  • 竞态条件
  • 同步问题

竞态条件

如前所述,速率限制器在高层的工作方式如下:

  • 从Redis读取计数器值。
  • 检查(counter + 1)是否超过阈值。
  • 如果不是,在Redis中将计数器值增加1。
    如figure4-14所示,在高度并发的环境中可能会出现竞态条件。
    在这里插入图片描述

假设Redis中的计数器值为3。如果两个请求并发地读取计数器值在它们中的任何一个写回值之前,它们都将计数器增加1并写入它没有检查其他线程就返回了。两个请求(线程)都认为它们拥有正确的计数器值4。但是,正确的计数器值应该是5。

锁是解决竞争条件最明显的解决方案。但是,锁将显著降低系统运行速度。解决这个问题通常采用两种策略:Lua脚本[13]和排序集的数据结构在Redis[8]。
对这些感兴趣的读者策略,参考相应的参考资料[8][13]。

同步的问题

同步是分布式环境中需要考虑的另一个重要因素。来支持数百万用户,一个速率限制服务器可能不足以处理流量。当使用多个速率限制器服务器时,需要同步。例如,在figure4-15左侧,客户端1向限速器1发送请求,客户端2向限速器1发送请求限速器2。由于web层是无状态的,客户端可以将请求发送到不同的速率限制器如figure4-15右侧所示。如果没有发生同步,则速率限制器1进行同步不包含客户端2的任何数据。因此,速率限制器不能正常工作。
在这里插入图片描述
一个可能的解决方案是使用粘性会话,允许客户端向同一服务器发送流量
速率限制器。这种解决方案是不可取的,因为它既不具有可伸缩性也不灵活。一个更好的方法是使用集中式数据存储,如Redis。外观设计如figure4-16所示。
在这里插入图片描述

性能优化

性能优化是系统设计面试中的一个常见话题。我们会讲到两个需要改进的地方。
首先,多数据中心设置对于速率限制器至关重要,因为对用户来说延迟很高
距离数据中心较远。大多数云服务提供商都构建了许多边缘服务器世界各地的地点。例如,截至2020年5月,Cloudflare在地理上有194个分布式边缘服务器[14]。流量被自动路由到最近的边缘服务器减少延迟
在这里插入图片描述其次,用最终的一致性模型同步数据。如果你不清楚最终的一致性模型,请参阅“第6章:设计密钥值存储”中的“一致性”部分。

监控

在速率限制器放置到位后,收集分析数据以检查是否速率限制是有效的。首先,我们要确保:

  • 限速算法是有效的。
  • 限速规则是有效的。
    例如,如果速率限制规则过于严格,许多有效请求将被丢弃。在这种情况下,我们想放宽一些规定。在另一个例子中,我们注意到速率限制器变成了
    当流量突然增加的时候,比如限时抢购,这是无效的。在这种情况下,我们可以替换算法以支持突发流量。代币桶在这里很合适。

步骤4 -打包

在本章中,我们讨论了不同的速率限制算法及其优缺点。讨论的算法包括:

  • 令牌桶
  • 水桶漏水
  • 固定窗口
  • 滑动窗测井
  • 滑动窗口计数器
    然后,我们讨论了分布式环境下的系统架构、速率限制器、性能优化和监控。与任何系统设计面试问题类似,如果时间允许,你还可以提到其他的谈话要点:
  • 硬/软速率限制。
    • 硬:请求数不能超过阈值。
    • 软:短时间内可以超过阈值。
  • 不同级别的速率限制。在本章中,我们只讨论了速率限制应用级别(HTTP:第7层)。可以在其他层应用速率限制。为例如,可以使用Iptables [15] (IP:三层)应用IP地址限速。注:开放系统互连模型(OSI模型)有7层[16]:物理层,第二层:数据链路层,第三层:网络层,第四层:传输层,第5层:会话层,第6层:表示层,第7层:应用层。
  • 避免受到利率限制。用最佳实践设计您的客户端:
    • 使用客户端缓存以避免频繁调用API。
    • 了解限制,不要在短时间内发送太多请求。
    • 包含捕获异常或错误的代码,以便您的客户端可以优雅地从中恢复例外。
    • 为重试逻辑添加足够的后退时间。
      恭喜你走了这么远!现在给自己点鼓励吧。好工作!

参考资料 Reference materials

[1] Rate-limiting strategies and techniques: https://cloud.google.com/solutions/rate-limitingstrategies-techniques
[2] Twitter rate limits: https://developer.twitter.com/en/docs/basics/rate-limits
[3] Google docs usage limits: https://developers.google.com/docs/api/limits
[4] IBM microservices: https://www.ibm.com/cloud/learn/microservices
[5] Throttle API requests for better throughput:
https://docs.aws.amazon.com/apigateway/latest/developerguide/api-gateway-requestthrottling.html
[6] Stripe rate limiters: https://stripe.com/blog/rate-limiters
[7] Shopify REST Admin API rate limits: https://help.shopify.com/en/api/reference/restadmin-api-rate-limits
[8] Better Rate Limiting With Redis Sorted Sets:
https://engineering.classdojo.com/blog/2015/02/06/rolling-rate-limiter/
[9] System Design — Rate limiter and Data modelling:
https://medium.com/@saisandeepmopuri/system-design-rate-limiter-and-data-modelling-
9304b0d18250
[10] How we built rate limiting capable of scaling to millions of domains:
https://blog.cloudflare.com/counting-things-a-lot-of-different-things/
[11] Redis website: https://redis.io/
[12] Lyft rate limiting: https://github.com/lyft/ratelimit
[13] Scaling your API with rate limiters:
https://gist.github.com/ptarjan/e38f45f2dfe601419ca3af937fff574d#request-rate-limiter
[14] What is edge computing: https://www.cloudflare.com/learning/serverless/glossary/whatis-edge-computing/
[15] Rate Limit Requests with Iptables: https://blog.programster.org/rate-limit-requests-withiptables
[16] OSI model: https://en.wikipedia.org/wiki/OSI_model#Layer_architecture

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

后端工匠之道

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值