”工欲善其事,必先利其器。“—孔子《论语.录灵公》
首页 > 编程 > 节流解释:管理 API 请求限制的指南

节流解释:管理 API 请求限制的指南

发布于2024-12-22
浏览:866

什么时候应该在代码中实施限制?

对于大型项目,通常最好使用 Cloudflare Rate Limiting 或 HAProxy 等工具。这些功能强大、可靠,可以为您处理繁重的工作。

但是对于较小的项目——或者如果您想了解事情是如何工作的——您可以在代码中创建自己的速率限制器。为什么?

  • 很简单:您将构建一些简单、易于理解的东西。
  • 预算友好:除了托管服务器之外,无需额外费用。
  • 它适用于小型项目:只要流量较低,它就能保持快速高效。
  • 它是可重用的:您可以将其复制到其他项目中,而无需设置新的工具或服务。

你将学到什么

在本指南结束时,您将了解如何在 TypeScript 中构建基本的节流器,以保护您的 API 不被淹没。我们将介绍以下内容:

  • 可配置的时间限制:每次被阻止的尝试都会增加锁定持续时间以防止滥用。
  • Request Caps:设置允许的最大请求数。这对于涉及付费服务的 API 尤其有用,例如 OpenAI。
  • 内存存储:无需 Redis 等外部工具即可工作的简单解决方案,非常适合小型项目或原型。
  • 每用户限制:使用 IPv4 地址跟踪每个用户的请求。我们将利用 SvelteKit 的内置方法轻松检索客户端 IP。

本指南旨在作为一个实用的起点,非常适合想要学习基础知识而又不想太复杂的开发人员。 但它还没有准备好生产

在开始之前,我想对 Lucia 的速率限制部分给予正确的评价。


节流器实施

让我们定义 Throttler 类:

export class Throttler {
    private storage = new Map();

    constructor(private timeoutSeconds: number[]) {}
}

Throttler 构造函数接受超时持续时间 (timeoutSeconds) 列表。每次用户被阻止时,持续时间都会根据此列表逐渐增加。最终,当达到最终超时时,您甚至可以触发回调以永久禁止用户的 IP - 尽管这超出了本指南的范围。

以下是创建阻止用户增加间隔的节流器实例的示例:

const throttler = new Throttler([1, 2, 4, 8, 16]);

此实例第一次会阻止用户一秒钟。两人第二次,以此类推。

我们使用 Map 来存储 IP 地址及其相应的数据。映射是理想的选择,因为它可以有效地处理频繁的添加和删除。

专业提示:使用地图来处理经常变化的动态数据。对于静态、不变的数据,对象更好。 (兔子洞1)


当您的端点收到请求时,它会提取用户的 IP 地址并咨询 Throttler 以确定是否应允许该请求。

它是如何运作的

  • 案例 A:新用户或不活跃用户

    如果在 Throttler 中未找到该 IP,则这可能是用户的第一个请求,或者他们已经处于不活动状态足够长的时间。在这种情况下:

    • 允许该操作。
    • 通过存储用户的 IP 并设置初始超时来跟踪用户。
  • 案例 B:活跃用户

    如果找到该IP,则说明该用户之前曾发出过请求。这里:

    • 检查自最后一个块以来是否已经过了所需的等待时间(基于 timeoutSeconds 数组)。
    • 如果已经过去了足够的时间:
    • 更新时间戳。
    • 增加超时索引(上限为最后一个索引以防止溢出)。
    • 如果不是,则拒绝该请求。

在后一种情况下,我们需要检查自上一个块以来是否已经过去了足够的时间。通过索引,我们知道应该引用哪个 timeoutSeconds。如果没有,只需反弹即可。否则更新时间戳。

export class Throttler {
    // ...

    public consume(key: string): boolean {
        const counter = this.storage.get(key) ?? null;
        const now = Date.now();

        // Case A
        if (counter === null) {
            // At next request, will be found.
            // The index 0 of [1, 2, 4, 8, 16] returns 1.
            // That's the amount of seconds it will have to wait.
            this.storage.set(key, {
                index: 0,
                updatedAt: now
            });
            return true; // allowed
        }

        // Case B
        const timeoutMs = this.timeoutSeconds[counter.index] * 1000;
        const allowed = now - counter.updatedAt >= timeoutMs;
        if (!allowed) {
            return false; // denied
        }

        // Allow the call, but increment timeout for following requests.
        counter.updatedAt = now;
        counter.index = Math.min(counter.index   1, this.timeoutSeconds.length - 1);
        this.storage.set(key, counter);

        return true; // allowed
    }
}

更新索引时,上限为timeoutSeconds的最后一个索引。如果没有它,counter.index 1 就会溢出,接下来访问 this.timeoutSeconds[counter.index] 将导致运行时错误。

端点示例

此示例演示如何使用 Throttler 来限制用户调用 API 的频率。如果用户发出太多请求,他们会收到错误,而不是运行主逻辑。

const throttler = new Throttler([1, 2, 4, 8, 16, 30, 60, 300]);

export async function GET({ getClientAddress }) {
    const IP = getClientAddress();

    if (!throttler.consume(IP)) {
        throw error(429, { message: 'Too Many Requests' });
    }

    // Read from DB, call OpenAI - do the thing.

    return new Response(null, { status: 200 });
}

Throttling Explained: A Guide to Managing API Request Limits

认证注意事项

当对登录系统使用速率限制时,您可能会遇到这个问题:

  1. 用户登录,触发 Throttler 将超时与其 IP 关联起来。
  2. 用户注销或会话结束(例如,立即注销、cookie 随会话过期和浏览器崩溃等)。
  3. 当他们不久后尝试再次登录时,Throttler 可能仍会阻止他们,返回 429 Too Many Requests 错误。

为了防止这种情况,请使用用户的唯一用户ID而不是他们的IP进行速率限制。此外,您必须在成功登录后重置节流器状态,以避免不必要的阻塞。

在 Throttler 类中添加一个重置方法:

export class Throttler {
    // ...

    public reset(key: string): void {
        this.storage.delete(key);
    }
}

登录成功后使用:

const user = db.get(email);

if (!throttler.consume(user.ID)) {
    throw error(429);
}

const validPassword = verifyPassword(user.password, providedPassword);
if (!validPassword) {
    throw error(401);
}

throttler.reset(user.id); // Clear throttling for the user

通过定期清理来管理过时的 IP 记录

当您的节流器跟踪 IP 和速率限制时,重要的是要考虑如何以及何时删除不再需要的 IP 记录。如果没有清理机制,您的节流器将继续在内存中存储记录,随着数据的增长,可能会导致性能问题。

为了防止这种情况,您可以实现清理功能,在一定时间不活动后定期删除旧记录。以下是如何添加简单的清理方法以从节流器中删除陈旧条目的示例。

export class Throttler {
    // ...

    public cleanup(): void {
        const now = Date.now()

        // Capture the keys first to avoid issues during iteration (we use .delete)
        const keys = Array.from(this.storage.keys())

        for (const key of keys) {
            const counter = this.storage.get(key)
            if (!counter) {
                // Skip if the counter is already deleted (handles concurrency issues)
                return
            }

            // If the IP is at the first timeout, remove it from storage
            if (counter.index == 0) {
                this.storage.delete(key)
                continue
            }

            // Otherwise, reduce the timeout index and update the timestamp
            counter.index -= 1
            counter.updatedAt = now
            this.storage.set(key, counter)
        }
    }
}

安排清理的一个非常简单的方法(但可能不是最好的)是使用 setInterval:

const throttler = new Throttler([1, 2, 4, 8, 16, 30, 60, 300])
const oneMinute = 60_000
setInterval(() => throttler.cleanup(), oneMinute)

这种清理机制有助于确保您的节流器不会无限期地保留旧记录,从而保持应用程序的高效。虽然这种方法简单且易于实现,但可能需要针对更复杂的用例进一步细化(例如,使用更高级的调度或处理高并发)。

通过定期清理,您可以防止内存膨胀,并确保一段时间内未尝试发出请求的用户不再被跟踪 - 这是使您的速率限制系统可扩展且资源高效的第一步。


  1. 如果您喜欢冒险,您可能有兴趣阅读属性的分配方式及其变化方式。另外,为什么不考虑诸如内联缓存之类的虚拟机优化,单态性特别青睐这种优化。享受。 ↩

版本声明 本文转载于:https://dev.to/didof/throttling-explained-a-guide-to-managing-api-request-limits-102a?1如有侵犯,请联系[email protected]删除
最新教程 更多>
  • 在 Go 中使用 WebSocket 进行实时通信
    在 Go 中使用 WebSocket 进行实时通信
    构建需要实时更新的应用程序(例如聊天应用程序、实时通知或协作工具)需要一种比传统 HTTP 更快、更具交互性的通信方法。这就是 WebSockets 发挥作用的地方!今天,我们将探讨如何在 Go 中使用 WebSocket,以便您可以向应用程序添加实时功能。 在这篇文章中,我们将介绍: WebSoc...
    编程 发布于2024-12-23
  • 如何在 PHP 中组合两个关联数组,同时保留唯一 ID 并处理重复名称?
    如何在 PHP 中组合两个关联数组,同时保留唯一 ID 并处理重复名称?
    在 PHP 中组合关联数组在 PHP 中,将两个关联数组组合成一个数组是一项常见任务。考虑以下请求:问题描述:提供的代码定义了两个关联数组,$array1 和 $array2。目标是创建一个新数组 $array3,它合并两个数组中的所有键值对。 此外,提供的数组具有唯一的 ID,而名称可能重合。要求...
    编程 发布于2024-12-23
  • 尽管代码有效,为什么 POST 请求无法捕获 PHP 中的输入?
    尽管代码有效,为什么 POST 请求无法捕获 PHP 中的输入?
    解决 PHP 中的 POST 请求故障在提供的代码片段中:action=''而不是:action="<?php echo $_SERVER['PHP_SELF'];?>";?>"检查 $_POST数组:表单提交后使用 var_dump 检查 $_POST 数...
    编程 发布于2024-12-23
  • CSS 可以在内联块元素中本机插入换行符吗?
    CSS 可以在内联块元素中本机插入换行符吗?
    CSS 在行内块元素中插入换行符:理论探索在不断发展的 Web 开发领域,这种能力操纵内容流仍然是最重要的。经常出现的一个特殊挑战涉及在内联块元素中插入换行符。考虑以下 HTML 结构:<h3 id="features">Features</h3> <...
    编程 发布于2024-12-23
  • 如何在 PHP 中轻松转换时区之间的时间和日期?
    如何在 PHP 中轻松转换时区之间的时间和日期?
    在 PHP 中转换时区之间的时间和日期使用 PHP,您可以轻松地在不同时区之间转换时间和日期。此功能在处理全球数据的应用程序或与来自不同位置的用户一起工作时特别有用。获取时区偏移量要获取与 GMT 的时间偏移量,您可以使用 DateTimeZone 类。它提供了时区及其各自偏移量的完整列表。$tim...
    编程 发布于2024-12-23
  • HTML 格式标签
    HTML 格式标签
    HTML 格式化元素 **HTML Formatting is a process of formatting text for better look and feel. HTML provides us ability to format text without us...
    编程 发布于2024-12-23
  • 如何在Windows上安装和使用Pip进行Python包管理?
    如何在Windows上安装和使用Pip进行Python包管理?
    Pip:在 Windows 上安装 Python 包的轻松方式在 Windows 上安装 Python 包可能是一项艰巨的任务,特别是如果您在使用 EasyInstall 时遇到困难。幸运的是,EasyInstall 的后继者 Pip 提供了更加简化和简化的解决方案。在 Windows 上逐步安装 ...
    编程 发布于2024-12-23
  • 大批
    大批
    方法是可以在对象上调用的 fns 数组是对象,因此它们在 JS 中也有方法。 slice(begin):将数组的一部分提取到新数组中,而不改变原始数组。 let arr = ['a','b','c','d','e']; // Usecase: Extract till index p...
    编程 发布于2024-12-23
  • 如何在Python的`type()`和`isinstance()`之间进行选择以进行对象类型检查?
    如何在Python的`type()`和`isinstance()`之间进行选择以进行对象类型检查?
    如何确定对象的类型确定对象的类型对于保证数据一致性并相应地执行操作至关重要。 Python 为此目的提供了两个内置函数:type() 和 isinstance()。使用 type()type() 函数返回确切的类型一个物体的。例如:>>> type([]) is list True...
    编程 发布于2024-12-23
  • JavaScript 的“querySelector()”和“querySelectorAll()”中是否有用于元素名称匹配的通配符方法?
    JavaScript 的“querySelector()”和“querySelectorAll()”中是否有用于元素名称匹配的通配符方法?
    通配符元素名称与 JavaScript 中的“querySelector()”和“querySelectorAll()”匹配问题:查询名称中嵌入特定字符串的元素的 XML 文档可能具有挑战性。虽然 CSS 支持属性查询的通配符,但元素名称似乎缺少相同的功能。解决方案:不幸的是,没有直接的方法来匹配通...
    编程 发布于2024-12-23
  • 为什么我的 Go 应用程序在 Docker 容器中运行时出现“接收失败:连接被对等方重置”?
    为什么我的 Go 应用程序在 Docker 容器中运行时出现“接收失败:连接被对等方重置”?
    Docker 端口暴露问题:解决“接收失败:连接由对等方重置”尝试在 Docker 中运行 Go 应用程序二进制文件时容器中,用户可能会遇到应用无法接收外部连接的问题。该错误表现为调用curl命令时出现“Recv failure: Connection Reset by Peer”。此问题的根源在于...
    编程 发布于2024-12-23
  • 如何使用 Visual Studio 查看预处理的 C/C++ 代码?
    如何使用 Visual Studio 查看预处理的 C/C++ 代码?
    在 Visual Studio 中查看预处理的 C/C 源文件使用包含大量预处理器指令的 C/C 源文件时,它会变为必须了解预处理后它们的外观。 Visual Studio 提供了一个命令行实用程序 cl.exe,它为开发人员提供了多种输出预处理文件的选项:1。预处理到标准输出:要将预处理后的文件输...
    编程 发布于2024-12-23
  • 如何使用 MySQL 查找今天生日的用户?
    如何使用 MySQL 查找今天生日的用户?
    如何使用 MySQL 识别今天生日的用户使用 MySQL 确定今天是否是用户的生日涉及查找生日匹配的所有行今天的日期。这可以通过一个简单的 MySQL 查询来实现,该查询将存储为 UNIX 时间戳的生日与今天的日期进行比较。以下 SQL 查询将获取今天有生日的所有用户: FROM USERS ...
    编程 发布于2024-12-23
  • 删除行后如何重置 Pandas DataFrame 的索引?
    删除行后如何重置 Pandas DataFrame 的索引?
    在 Pandas Dataframe 中重置索引的方法当您删除行并想要保留行时,可能需要重置 DataFrame 的索引连续索引。在这种情况下,您可能会遇到索引不规则的问题,例如 [1, 5, 6, 10, 11]。为了解决这个问题,pandas 提供了一个方便的解决方案,使用 DataFrame....
    编程 发布于2024-12-23
  • 如何在 Bootstrap 中创建悬停激活的下拉菜单并删除箭头图标?
    如何在 Bootstrap 中创建悬停激活的下拉菜单并删除箭头图标?
    Twitter Bootstrap 中的悬停激活下拉菜单许多用户更喜欢将 Bootstrap 菜单悬停时下拉,从而无需显式点击。本文讨论了悬停功能和删除菜单标题旁边的箭头图标。悬停激活下拉菜单要启用悬停时自动下拉菜单,请使用 CSS定位隐藏菜单选项。将以下代码添加到您的 CSS 中:ul.nav l...
    编程 发布于2024-12-23

免责声明: 提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发到邮箱:[email protected] 我们会第一时间内为您处理。

Copyright© 2022 湘ICP备2022001581号-3