• 系统实现速率限制的方式有哪些?
  • 发布于 1周前
  • 40 热度
    0 评论
可以肯定地说,大多数人了解红绿灯等基础设施的工作原理。然而,我们大多低估了基础设施的重要性。由于普通驾驶员缺乏车道纪律,没有交通系统的道路将完全混乱,而且在许多城市,道路网络甚至无法跟上人口增长。这个类比延伸到 API 设计。正如不守纪律的司机会在道路上造成严重破坏一样,恶意用户也可以威胁您的应用程序。此外,随着用户群的增长,管理流量激增变得至关重要。与道路的红绿灯一样,处理此问题的一种有效方法是速率限制。

在本教程中,我们将探讨速率限制以及其他 API 流量管理技术。我们将介绍它们的工作原理、如何实施它们、何时使用每种策略,并提供一个比较表来帮助您确定哪种方法最适合您的需求。

什么是速率限制?
速率限制是一种控制 API 上传入和传出流量的技术,它通过对 API 用户在给定时间范围内可以发出的请求数设置预定义限制。这样,您可以防止单个用户垄断您的 API 基础设施资源,同时防止拒绝服务 (DoS) 和暴 力攻击等恶意攻击。在这个场景下,速率限制是通过速率限制器实现的,该速率限制器会不断检查每个用户的请求,以查看其请求是否在其请求限制内或超出:

如上图所示,如果用户在其限制范围内,则会处理请求,并更新其新限制。但是,如果请求已超出其限制,则请求将被拒绝。此外,这些限制的上限和补充频率取决于组织的首选项,而这些首选项又受系统容量和业务要求的影响。

实施速率限制
在实践中,可以使用各种算法实现速率限制,每种算法都有自己的方法来管理请求速率。一些流行的包括:
Token Bucket ---- 此算法使用具有固定令牌数量的“桶”。每个请求都会从存储桶中删除一个令牌;如果存储桶为空,则请求被拒绝。
Leaky Bucket ---- 与令牌桶类似,但在此桶中,令牌(或请求)以恒定速率处理。如果传入请求速率超过处理速率,则超出的请求将被丢弃或延迟。
Fixed Window ----  在固定时间窗口(例如,每分钟或每小时)内对请求进行计数和限制。如果请求数超过窗口内的限制,则进一步的请求将被拒绝,直到下一个窗口。

Sliding Log (滑动日志)---- 此函数使用时间戳记录每个请求。为了确定是否允许新请求,该算法会检查日志中允许的时间范围内的请求数,并根据该计数做出决定。

例如,我们可以利用以下代码中显示的模式,通过令牌桶算法实现速率限制:
class TokenBucket {
    constructor(rate, capacity) {
        this.rate = rate;
        this.capacity = capacity;
        this.tokens = capacity;
        this.lastRequestTime = Date.now();
    }

    addTokens() {
        const now = Date.now();
        const elapsed = (now - this.lastRequestTime) / 1000; // Convert to seconds
        const addedTokens = elapsed * this.rate;
        this.tokens = Math.min(this.capacity, this.tokens + addedTokens);
        this.lastRequestTime = now;
    }
    // 堆代码 duidaima.com
    allowRequest(tokensNeeded = 1) {
        this.addTokens();
        if (this.tokens >= tokensNeeded) {
            this.tokens -= tokensNeeded;
            return true;
        } else {
            return false;
        }
    }
}
在这个代码示例中,我们定义了一个 TokenBucket 类,该类设置 Token 生成速率和容量,并记录最后一次请求时间;然后,我们创建一个 addTokens() 方法,该方法根据自上次请求以来的时间计算要添加的令牌数量,并额外更新用户的当前令牌计数。最后,我们定义了一个 allowRequest() 方法,用于检查请求是否有足够的令牌,扣除任何必要的令牌,并返回是否允许请求。

在我们的应用程序中应用此实现将如下所示:
const bucket = new TokenBucket(1, 10); // 1 token per second, max 10 tokens
function handleRequest(userRequest) {
  if (bucket.allowRequest()) {
    // Request allowed
    userRequest();
  } else {
    console.log("Too many requests, please try again later");
  }
}

function getPosts() {
  fetch('/path/to/api')
}

handleRequest(getPosts);
在此使用示例中,我们初始化一个新的 TokenBucket 实例,速率为每秒 1 个令牌,容量为 10 个令牌。然后,我们创建一个 handleRequest() 函数来检查请求是否被允许并打印适当的消息。我们还使用假设的 getPosts() 函数测试我们的请求处理程序。此示例虽然是用 JavaScript 编写的,但应该能够帮助您开始以任何语言实现速率限制或使用令牌存储桶算法。

几乎所有的语言和框架都有库,您可以使用这些库轻松实现速率限制,而无需重新发明轮子;JavaScript 生态系统中的一些常用软件包包括用于 Express.js 的 express-rate-limit 包和用于 NestJS 应用程序的 @nestjs/throttler。

速率限制替代方案
API 流量管理不仅限于限流;还有其他方法可以控制应用程序的使用情况和管理流量激增。让我们在下面快速探索它们。

Throttling 节流
限制是另一种控制用户向 API 发出请求的速率的技术。与速率限制不同,速率限制会在超出限制时阻止请求,而限制会引入延迟来减慢请求速率。凭借这种设计性质,限制可以消除流量峰值,而用户只会遇到更少的拒绝和延迟请求。但是,一个缺点是,这些故意的延迟也会增加延迟并使系统变慢,因为每个请求都在队列中等待处理。此外,与简单的速率限制相比,实现限制逻辑可能更复杂,在极端情况下,仅靠限制可能无法保护系统免受过载。

如何实施限制
可以通过保留请求时间戳队列、计算给定时间段内的请求数以及在请求速率超过允许的限制时引入延迟来实现限制。示例如下所示:
class Throttler {
    constructor(maxRequests, period) {
        this.maxRequests = maxRequests;
        this.period = period;
        this.requestTimes = [];
    }

    addTokens() {
        // Filter out old request timestamps
    }

    allowRequest() {
        // Check if current requests are below maxRequests
        // If yes, log the current timestamp and allow the request
        // If no, deny the request
    }

    delayRequest() {
        // Calculate delay needed until the next request can be allowed
    }
}
在此伪代码示例中,Throttler 类通过保留请求时间戳队列来管理请求速率。然后,addTokens() 方法会删除早于设置时间段的请求时间戳。此外,allowRequest() 方法确定该时间段内的请求量是否小于允许的最大值;如果是这样,它会记录当前时间戳并允许请求。否则,它将拒绝请求。最后,delayRequest 方法估计允许下一个请求之前的时间。您还可以在此处查看此示例的完整 JavaScript 实现。

Spike control  峰值控制
峰值控制也叫削峰,是另一种流行的技术,用于管理可能使 API 或服务不堪重负的突然激增的流量。它的工作原理是监控短时间间隔内的请求速率,并实施临时阻止请求、重定向流量或扩展资源等措施以适应增加的负载。例如,假设 API 通常每分钟可以处理 100 个请求。使用峰值控制,您可以设置一个阈值来检测请求数是否突然跃升至每分钟 150 个。

如上所述,当检测到此类峰值时,您可以将系统配置为通过暂时阻止新请求来防止过载,将流量重定向到其他服务器以平衡负载,或快速扩展资源以管理增加的需求。

Circuit breaking 熔断
熔断也是管理 API 或服务弹性的有效技术,尤其是在面临故障或性能下降时。它的工作原理是监控服务交互的运行状况并暂时停止对失败服务的请求,以防止级联故障。这里使用“service”这个词也应该暗示,与我们之前介绍的技术不同,熔断在微服务架构中比在整体式或简单的 API 系统中更受欢迎和有用。

想象一下您的服务与第三方 API 交互的场景。如果第三方 API 开始失败或响应缓慢,您的系统可以使用断路器来检测此问题,并在设定的时间内停止向失败的服务发出进一步的请求。

当断路器检测到多个连续故障或超时时,它会 “跳闸” 电路,暂时阻止对有问题的服务的新请求。在此期间,系统可以向用户返回回退响应或错误消息。然后,在指定的超时时间后,断路器允许有限的测试请求检查服务是否已恢复。如果服务响应成功,则电路将关闭,并恢复正常操作。如果故障继续,电路将保持打开状态,并再次阻止请求。

决定使用哪种 API 流量管理技术
决定使用哪种技术主要取决于您的应用领域和要求。但是,考虑到我们到目前为止介绍的所有内容,速率限制更适合需要强制实施严格请求配额的应用程序,例如公有 API 或具有分层访问级别的 API。限制更适合于维护性能和用户体验至关重要的应用程序,例如电子商务网站或社交媒体平台,因为它会引入延迟而不是直接阻止请求,从而消除流量峰值。

对于遇到不可预测的流量激增的应用程序,峰值控制将至关重要,例如,在高需求活动期间向网站提供票务,或在突发新闻期间向新闻网站提供票务。熔断对于依赖多个外部服务(如微服务架构或 SaaS 平台)的应用程序特别有用,因为它通过停止对失败服务的请求来防止级联故障,同时允许系统保持响应。

还可以组合多种策略以实现更有效的流量管理。在某些情况下,您可以进一步应用负载均衡以在服务器之间分配流量。

比较速率限制、限制、尖峰控制和熔断
下表突出显示了我们介绍的不同 API 流量管理技术之间的主要差异,以帮助您快速确定哪种技术最适合您。

结论
在本教程中,我们探讨了速率限制和其他 API 流量管理技术,例如限制、峰值控制和熔断。我们介绍了它们的工作原理、基本实现以及理想的应用领域。采取某些流量管理措施来确保您的 API 能够按预期为用户提供服务非常重要,本文提供了一个快速指南,可快速帮助您决定如何以及何时使用哪种技术。
用户评论