欢迎访问shiker.tech

请允许在我们的网站上展示广告

您似乎使用了广告拦截器,请关闭广告拦截器。我们的网站依靠广告获取资金。

搞定系统设计:如何设计一个限流器?
(last modified Jan 12, 2025, 4:18 PM )
by
侧边栏壁纸
  • 累计撰写 194 篇文章
  • 累计创建 66 个标签
  • 累计收到 4 条评论

目 录CONTENT

文章目录

搞定系统设计:如何设计一个限流器?

橙序员
2025-01-12 / 0 评论 / 0 点赞 / 128 阅读 / 2,774 字 / 正在检测百度是否收录... 正在检测必应是否收录...
文章摘要(AI生成)

限流器在当今数字化蓬勃发展的时代扮演着关键角色,能够有效地管理系统资源,防止系统瘫痪。设计一个高效可靠的限流器需要考虑诸多因素,如选择适配的流量限制算法、处理用户请求计数、客户端信息反馈等。常用的限流算法包括代币桶算法、漏桶算法、固定窗口计数器算法、滑动窗口日志算法和滑动窗口计数器算法。除此之外,分布式部署、多数据中心布局等进阶需求也需要被考虑。通过合理应用工具如Redis、精妙的脚本编写与数据结构运用,限流器能够在复杂多变的分布式环境中稳定高效运行,为系统稳定、优化资源分配、提升用户体验发挥重要作用。最终,精心设计的限流器将成为数字化业务发展的坚实盾牌。

搞定系统设计:如何设计一个限流器?

在当今数字化蓬勃发展的时代,线上系统承载着海量业务交互。当大量请求同时涌来,如果系统不加以管控,资源被过度占用,就极易陷入瘫痪状态,无法正常运行,这时候限流器就起到了关键作用。

为什么需要限流器

使用限流器有以下好处:

  1. 预防拒绝式服务攻击导致的资源耗尽
  2. 降低成本,把更多的资源分配给优先级更高的API
  3. 预防服务器过载,过滤过量请求

设计一个限流器需要考虑以下方面:

  • 准确限制过量的请求。
  • 低延时。限流器不能拖慢HTTP响应时间。
  • 尽量占用较少的内存。
  • 在分布式系统中需要为分布式限流器,可以在多个服务器或者进程之间共享。
  • 需要处理异常。当用户的请求被拦截时,给用户展示明确的异常信息。
  • 高容错性。如果限流器出现任何问题(比如某个缓存服务器宕机),不能影响整个系统。

在哪里实现:

  1. 无法在客户端实现,因为客户端可以被伪造
  2. 在网关还是服务端实现取决于公司技术栈等因素

流量限制算法

代币桶算法

代币桶是一个有预定义容量的容器。代币按照预定的速率被放入桶中。一旦桶被装满,就不再往里面添加代币。

  1. 每个请求消耗一个代币。当一个请求到达时,我们检查桶里有没有足够的代币。
  2. 如果有足够的代币,每次请求到达时,我们就取出一个代币,然后这个请求就可以通过。
  3. 如果没有足够的代币,这个请求将被丢弃。
代币桶原理

其中有两个参数:

  • 桶大小:桶内最多允许有多少个代币。
  • 重新注入代币的速度:每秒放进桶里的代币数量。

优点:

  • 算法容易实现。
  • 内存的使用效率高。
  • 允许在很短时间内出现突发流量
  • 只要还有代币,请求就可以通过。

缺点:

尽管该算法只需要两个参数,但是要把这两个参数调校好,可能很具有挑战性。

漏桶算法

漏桶算法通常采用先进先出(First-In-First-Out,FIFO)队列来实现

  • 当一个请求到达时,系统先检查桶是否已满。
  • 如果没有,就将请求添加到队列中。否则,丢弃请求。
  • 定期从队列中取出请求并进行处理。

image-20250112150714584

由如下参数控制:

  • 桶大小:它等于队列大小。队列中保存了要按固定速率处理的请求。
  • 出栈速度:它定义了每秒可以处理多少个请求,通常以秒为时间单位。

优点:

  • 因为队列大小是有限的,所以内存的使用更高效。
  • 因为对请求是按固定速率来处理的,所以漏桶算法很适合要求出栈速度稳定的场景

缺点:

  • 突发流量会使队列中积压大量旧的请求,如果这些请求不能被及时处理,最新的请求会被限流。
  • 该算法有两个参数,要调校好它们可能不那么容易。

固定窗口计数器算法

  1. 将时间轴分成固定大小的时间窗口,并给每个时间窗口分配一个计数器。
  2. 每到达一个请求,计数器的值都会加1。
  3. 一旦计数器到达预先设定的阈值,新请求就会被丢弃,直到开始一个新的时间窗口。

例如每秒只允许3个请求被通过:

image-20250112150943050

优点:

  • 内存的使用效率高。
  • 容易理解。
  • 在每个单位时间窗口结束时重置请求数阈值,这种设计适用于某些特定场景。

缺点:

如果在时间窗口的边界上流量激增,会导致通过的请求数超过设定的阈值。

滑动窗口日志算法

  1. 算法记录每个请求的时间戳
  2. 当新请求到达时,移除所有过时的时间戳。
  3. 将新请求的时间戳添加到日志中。
  4. 如果日志的条数等于或者少于允许的请求数,则请求通过,否则请求被拒绝。

下图演示了每分钟限制2个请求时,每次请求到来时都会统计过去1分钟内是否达到相应数量的请求

image-20250112151724310

优点

实现的流量限制非常准确,在任何滑动的时间窗口中,请求的数量都不会超过阈值。

缺点:

消耗的内存过多,因为即使一个请求已被拒绝,它的时间戳依然被保存在内存中。

滑动窗口计数器算法

当一个新请求出现在当前分钟的30%的位置时,滑动窗口所允许的请求数量通过下面的公式来计算:

当前窗口的请求数+之前窗口的请求数×滑动窗口和之前窗口的重合率当前窗口的请求数+之前窗口的请求数×滑动窗口和之前窗口的重合率

用这个公式,我们可以算出滑动窗口所允许的请求数量为6.5个(3+5×0.7=6.5)。基于不同的用户场景,对这个数字可以向上或者向下取整。

image-20250112152017502

优点:

平滑了流量中的波动,因为当前时间窗口内请求的速率是基于前一个时间窗口内请求的平均速率计算出来的。

对内存的使用很高效

缺点:

只对不那么严格的回溯窗口起作用。

其他问题

用户请求计数

我们需要一个计数器来记录有多少请求是由同一个用户、同一个IP地址等发来的。如果计数器的值超出设定的阈值,请求就不允许通过。这里就可以使用redisincrexpire命令,既能保证低延时,过期策略又能保证其不会占用过高的内存

如何知道被限流

客户端如何知道请求有没有被限流呢?客户端如何在请求被拦截之前知道允许通过的请求数还剩多少?答案藏在HTTP头里。限流器会返回下面的HTTP头给客户端。

  • X-Ratelimit-Remaining:在当前时间窗口内剩余的允许通过的请求数量。
  • X-Ratelimit-Limit:客户端在每个时间窗口内可以发送多少个请求。
  • X-Ratelimit-Retry-After:在被限流之后,需要等待多少秒才能继续发送请求而不被拦截。

当用户发送的请求过多时,限流器将向客户端返回HTTP响应码429(表示请求太多)和X-Ratelimit-Retry-After响应头。

详细设计

  1. 流量限制规则存储在硬盘上。
  2. 工作进程(Worker)经常从硬盘中获取规则并将其存储到缓存中。
  3. 当客户端向服务器发送请求时,请求会首先被发给限流器中间件。
  4. 限流器中间件从缓存中加载规则。它从Redis缓存中获取计数器和上一次请求的时间戳。
  5. 基于响应,限流器中间件做出以下决定:
    • 如果请求没有被限流,就将其转发给API服务器。
    • 如果请求被限流,限流器会向客户端返回429响应码(请求太多)来报错。同时,请求要么被丢弃,要么被转发到队列中。

详细设计

在后续拓展中,限流器如果进行分布式部署,就需要考虑用户请求计数时的并发和数据同步。由于redis支持分布式集群部署,所以数据同步不用再过多考虑,而并发问题我们可以使用redis中的lua脚本和有序集合来解决

限流规则、请求计数和失败请求重试等功能可以通过最终一致模型来保证数据一致性。还需要考虑的一点是,和CDN一样,限流器设置多数据中心是至关重要的,因为离数据中心越远,响应延时越高。

总结

综上所述,设计一个高效且可靠的限流器绝非易事,需要全方位综合考量诸多关键要素。从明确限流器的核心价值,到依据不同场景谨慎挑选适配的流量限制算法,再到妥善处理诸如用户请求计数、客户端信息反馈等衍生问题,乃至精心规划详细的系统架构与稳健的后续拓展方案,每一步都紧密相扣。

在实际落地过程中,工程师们必须依据自身系统特性、业务流量模式以及技术资源储备,灵活抉择。无论是追求突发流量应对能力的代币桶算法,还是侧重稳定输出的漏桶算法;无论是简单易上手的固定窗口计数器算法,还是精准严苛的滑动窗口日志算法,亦或是平衡各方的滑动窗口计数器算法,都有其用武之地。

随着系统的持续演进与拓展,分布式部署、多数据中心布局等进阶需求将接踵而至。借助如 Redis 这般强大的工具,配合精妙的脚本编写与数据结构运用,能够助力我们攻克并发、数据同步等棘手难题,保障限流器在复杂多变的分布式环境中依然稳定高效运行。总之,精心雕琢的限流器将成为守护系统稳定、优化资源分配、提升用户体验的坚实盾牌,为数字化业务的蓬勃发展保驾护航。

0

评论区