跳至主要内容

API 性能:速率限制、分页和异步处理

使用速率限制算法、基于游标的分页、异步作业队列和响应压缩最佳实践构建高性能 API。

E
ECOSIRE Research and Development Team
|2026年3月15日3 分钟阅读554 字数|

您的 API 的速度仅与其在峰值负载下最慢的端点一样快。 保持数据库连接 5 秒的单个未优化端点可能会耗尽您的连接池,并导致整个平台出现级联故障。 API 性能工程侧重于三个支柱:保护 API 免受过载(速率限制)、有效处理大型数据集(分页)以及将昂贵的操作移出请求周期(异步处理)。

属于我们的性能与可扩展性系列

阅读完整指南

您的 API 的速度仅与其在峰值负载下最慢的端点一样快。 保持数据库连接 5 秒的单个未优化端点可能会耗尽您的连接池,并导致整个平台出现级联故障。 API 性能工程侧重于三个支柱:保护 API 免受过载(速率限制)、有效处理大型数据集(分页)以及将昂贵的操作移出请求周期(异步处理)。

要点

  • 令牌桶和滑动窗口是覆盖 95% 用例的两种速率限制算法 - 根据您是否想要突发容忍或严格执行进行选择
  • 对于大型数据集,基于游标的分页优于偏移分页,因为它避免了对跳过的行进行计数
  • 使用作业队列进行异步处理,通过将电子邮件发送、PDF 生成和 Webhook 交付移出请求路径,减少 P95 响应时间
  • 使用 Brotli 进行响应压缩可将有效负载大小减少 70-85%,直接转化为更快的客户端渲染

速率限制算法

速率限制可以保护您的 API 免遭滥用,确保公平的资源分配,并防止流量高峰期间发生级联故障。您选择的算法决定了如何处理突发以及消费者的限制行为的可预测性。

算法突发处理内存使用情况精密最适合
固定窗允许在窗口边界处进行 2x 突发非常低低简单的用例,内部 API
滑动窗口日志无突发,精准高(存储时间戳)非常高金融API,严格合规
推拉窗柜台最小边界爆发低高通用公共 API
令牌桶允许受控爆发低中等具有自然突发模式的 API
漏水桶畅通所有交通低高需要稳定吞吐量的 API

令牌桶

令牌桶算法对于大多数 API 来说是最实用的选择。桶可以容纳最大容量的令牌。代币以固定速率(补充率)添加。每个请求消耗一个令牌。如果存储桶为空,则请求将被拒绝或排队。

令牌桶的主要优点是突发容忍度。如果客户端有一段时间没有发出请求,则他们的存​​储桶已满,他们可以发出突发请求,直至达到存储桶的容量。这符合自然的使用模式——加载仪表板的客户端可能会快速连续发出 20 个请求,然后在 30 秒内什么也不发出。

电子商务 API 的配置示例:

  • 桶大小:100 个代币
  • 填充率:每秒 10 个代币
  • 这允许突发最多 100 个请求,同时长期维持每秒 10 个请求

滑动窗口计数器

滑动窗口计数器结合了滑动窗口日志的精度和固定窗口的内存效率。它维护当前和上一个窗口的计数器,然后根据请求落入当前窗口的程度计算加权计数。

对于在 45 秒内评估的 60 秒窗口,有效计数为:(前一个窗口计数 * 0.25)+(当前窗口计数)。这消除了固定窗口的边界突发问题,而无需存储单独的请求时间戳。

使用 Redis 实现

Redis 是分布式速率限制的标准后备存储,因为它提供了带有 TTL 的原子增量操作。将 INCR 与 EXPIRE 结合使用用于固定窗口,或将排序集与 ZADD 和 ZRANGEBYSCORE 结合使用用于滑动窗口。对于令牌桶,Redis Lua 脚本提供原子检查和递减操作。

速率限制标头向 API 使用者传达限制:

  • X-RateLimit-Limit -- 窗口中允许的最大请求数
  • X-RateLimit-Remaining -- 请求保留在当前窗口中
  • X-RateLimit-Reset -- 窗口重置时的 Unix 时间戳
  • Retry-After -- 客户端重试之前的秒数(针对 429 响应)

分页策略

每个列表端点都必须分页。返回无限的结果集会浪费带宽,使数据库紧张,并且随着数据的增长而出现超时错误。

偏移分页

偏移分页使用 LIMIT 和 OFFSET SQL 子句。客户端请求 ?page=3&limit=20,服务器转换为 OFFSET 40 LIMIT 20。

优点:

  • 易于实施和理解
  • 客户可以直接跳转到任何页面
  • 总计数启用“第 X 页,共 Y 页”UI

缺点:

  • 高偏移量会降低性能 - OFFSET 1000000 在返回结果之前仍会扫描 1,000,000 行
  • 页面之间的数据更改时结果不一致(插入或删除新数据时行会移动)
  • 总计数查询 (COUNT(*)) 在大型表上可能会很昂贵

基于光标的分页

基于游标的分页使用不透明游标(通常是编码的主键或时间戳)来标记结果集中的位置。客户端请求?cursor=abc123&limit=20,服务器使用游标作为WHERE子句:WHERE id > decoded(abc123) LIMIT 20。

优点:

  • 无论数据集中的位置如何,性能始终如一 - 无偏移扫描
  • 即使页面之间的数据发生变化,结果也稳定
  • 自然适合无限滚动和实时提要

缺点:

  • 无法跳转到任意页面(无“转到第 50 页”)
  • 实现起来更复杂,尤其是多列排序顺序
  • 如果需要,必须单独提供总数

使用哪个分页

场景推荐原因
带有页码的管理数据表偏移用户期望页面导航
手机无限滚动光标任何深度的性能
集成使用的 API光标批处理的稳定分页
小型数据集(10,000 行以下)要么性能差异可以忽略不计
大型数据集(超过 100,000 行)光标偏移量变得异常缓慢
实时提要(聊天、通知)光标新数据到达时的一致性

分页响应格式

精心设计的分页响应包括客户端需要导航的元数据:

{
  "data": [],
  "pagination": {
    "total": 15432,
    "limit": 20,
    "hasMore": true,
    "nextCursor": "eyJpZCI6MTAwfQ=="
  }
}

使用作业队列进行异步处理

同步 API 端点应在 200 毫秒内返回响应。任何需要较长时间的操作(发送电子邮件、生成 PDF、处理图像、调用外部 API、运行报告)都应移至后台作业队列。

作业队列模式

  1. API端点验证请求并创建作业记录 2.作业被放入队列(Redis、RabbitMQ、SQS)
  2. API 立即返回 202 Accepted 响应和作业 ID 4.工作进程接收作业并异步执行
  3. 客户端轮询作业状态或在完成时接收 Webhook 回调

常见异步用例

电子邮件发送 - SMTP 操作需要 500 毫秒至 3 秒,具体取决于提供商。对电子邮件进行排队可减少 API 响应时间,并允许针对暂时性故障重试逻辑,而不会阻止用户。

PDF 生成 -- 生成发票、报告或导出文件会占用大量 CPU 和内存。在专用工作线程中运行这些可以防止 API 请求处理的资源争用。

Webhook 交付 -- 传出 Webhook 取决于第三方服务器的可用性。通过指数退避重试(1 秒、2 秒、4 秒、8 秒,最多 5 分钟)对 Webhook 交付进行排队,以处理临时故障,而不会阻塞系统。

数据导入和导出 - 处理 100,000 行的 CSV 上传不应在请求周期内发生。接受上传,返回作业ID,并批量处理行。

队列选择

队列技术最适合注意事项
BullMQ(Redis 支持)Node.js 应用程序、NestJS 集成出色的开发人员体验,内置仪表板
兔子MQ多语言系统,复杂路由成熟,支持消息确认模式
AWS SQS无服务器、托管基础设施无需服务器管理,按消息付费
卡夫卡事件流,高吞吐量对于简单的作业队列来说太过分了,对于事件溯源来说非常好

响应优化

除了应用程序逻辑之外,响应本身还可以针对大小和交付速度进行优化。

压缩

启用响应压缩以减少网络上的有效负载大小。现代压缩算法显着减少基于文本的有效负载(JSON、HTML、CSS、JavaScript)。

算法压缩比CPU 成本浏览器支持
压缩包减少 60-75%低通用
布罗特利减少 70-85%中等所有现代浏览器
zstd减少 70-85%低新兴(尚未普及)

将 Brotli 用于静态资产(在构建时预压缩),并使用 gzip 作为动态响应的后备。在 NestJS 中,压缩中间件会自动处理此问题,但在生产中,让 Nginx 处理压缩以从应用程序服务器上卸载 CPU。

字段选择

允许 API 使用者仅请求他们需要的字段。 GraphQL 本身就是这样做的,但 REST API 可以支持使用 ?fields=id,name,price 查询参数进行字段选择。这可以减少有效负载大小,并可以通过仅选择所需的列来优化数据库查询。

响应缓存标头

在 API 响应上设置适当的 Cache-Control 标头。公共列表端点(产品、类别)可以使用 Cache-Control: public, max-age=300 缓存 5 分钟。经过身份验证的端点应使用 Cache-Control: private, no-cache 来防止 CDN 缓存,同时允许浏览器缓存并重新验证。

有关缓存策略的更多信息,请参阅我们有关 Redis、CDN 和 HTTP 缓存 的详细指南。


连接管理

数据库和 HTTP 连接是有限的资源,必须在负载下小心管理。

数据库连接池

连接池维护一组可重用的数据库连接。如果没有池化,每个 API 请求都会打开一个新的数据库连接(50-100 毫秒的开销)并在响应后关闭它。通过池化,请求从池中借用连接并在完成后返回它们。

池大小调整公式: 连接数 = (core_count * 2) + effective_spindle_count。对于具有 SSD 存储的 4 核服务器,每个应用程序实例 10-20 个连接是一个很好的起点。监视池利用率 - 如果它定期超过 80%,请增加池大小或优化查询持续时间。

HTTP 保持活动状态

为与上游服务(数据库、Redis、外部 API)的连接启用 HTTP keep-alive。这会重用 TCP 连接,而不是为每个请求建立新连接,从而消除了 TCP 握手和 TLS 协商开销(每个新连接 50-200 毫秒)。


常见问题

我应该为公共 API 设置什么速率限制?

从保守的限制开始,并根据合法的使用模式进行调整。常见的起点是经过身份验证的用户每分钟 100 个请求,匿名用户每分钟 20 个请求。监控 429 响应率 - 如果合法用户经常达到限制,请提高响应率。为高级 API 层提供更高的限制。

当页面之间的数据发生变化时,如何处理分页?

基于游标的分页可以自然地处理这个问题,因为它锚定到排序数据中的特定位置。使用偏移分页时,生成的文档可能会在页面之间移动。对于关键用例(财务报告、数据导出),在分页开始时对数据进行快照并对快照进行分页。

我应该使用 REST 还是 GraphQL 来提高性能?

对于简单、定义明确的端点,具有字段选择和适当缓存的 REST 速度更快。 GraphQL 消除了复杂数据需求的过度获取和不足获取,但增加了查询解析开销并使 HTTP 缓存变得更加困难。将 REST 用于具有缓存需求的公共 API,将 GraphQL 用于满足复杂前端数据需求的内部 API。

如何监控生产中的 API 性能?

跟踪每个端点的 P50、P95 和 P99 响应时间。针对 P95 违反 SLO(通常为 200-500 毫秒)设置警报。使用分布式跟踪来分解数据库、缓存、外部服务和应用程序逻辑所花费的时间。有关详细设置,请参阅我们的监控和可观察性 指南。


下一步是什么

首先审核您的 API 端点是否缺少分页、不受保护且没有速率限制的公共端点以及应该异步的同步操作。这三项更改通常可将 P95 响应时间缩短 50-70%,并防止最常见的生产事故。

有关完整的性能工程视角,请参阅我们关于扩展您的业务平台 的支柱指南。对于为您的 API 提供支持的数据库层,请阅读我们的查询优化指南。

ECOSIRE 在 Odoo ERP 和自定义架构上为业务平台构建高性能 API。 联系我们 进行 API 性能审核。


由 ECOSIRE 发布 — 通过 Odoo ERP、Shopify 电子商务 和 OpenClaw AI 等人工智能驱动的解决方案帮助企业扩展规模。

E

作者

ECOSIRE Team

技术写作

ECOSIRE 技术写作团队的内容涵盖 Odoo ERP、Shopify 电子商务、AI 智能体、Power BI 分析、GoHighLevel 自动化以及企业软件最佳实践。我们的指南帮助企业做出明智的技术决策。

ECOSIRE

与 ECOSIRE 一起发展您的业务

跨 ERP、电子商务、人工智能、分析和自动化的企业解决方案。