Back

如何修复 Web 应用中的 '429 Too Many Requests' 错误

如何修复 Web 应用中的 '429 Too Many Requests' 错误

你的 Web 应用突然停止工作。API 调用失败。用户看到错误提示。罪魁祸首是什么?HTTP 429 Too Many Requests——这是服务器告诉你的应用程序”慢下来”的方式。

本指南将解释现代 Web 应用中导致 429 错误的原因、如何解读服务器发送的信号,以及防止这些错误发生的前端请求限流实用策略。

核心要点

  • HTTP 429 表示你已超出速率限制——这是一个让你慢下来的信号,而不是服务器故障。
  • 使用速率限制响应头(X-RateLimit-*RateLimit)和 Retry-After 响应头来指导你的重试逻辑。
  • 通过防抖、请求批处理和积极缓存来预防 429 错误。
  • 当 429 错误发生时,实施指数退避并将重试请求加入队列,而不是持续轰炸服务器。

HTTP 429 Too Many Requests 的真正含义

429 状态码表示你的客户端已超出服务器设置的速率限制。与 500 系列错误(服务器问题)或 503 Service Unavailable(临时过载)不同,429 的含义非常明确:你发出了太多请求,服务器正在保护自己。

API 速率限制可能来自多个来源:

  • 你自己的后端,执行每用户或每会话限制
  • CDN 和 WAF,阻止看起来像滥用的流量模式
  • 第三方 API,保护其基础设施免受过度调用

每个来源可能有不同的限制、不同的检测时间窗口和不同的恢复行为。你的应用中的单个用户操作可能会触发对多个服务的请求,其中任何一个都可能返回 429。

理解速率限制响应头

当服务器实施速率限制时,它们通常通过响应头传达限制信息。存在两种模式:

传统响应头使用 X-RateLimit-* 前缀:

  • X-RateLimit-Limit:允许的最大请求数
  • X-RateLimit-Remaining:当前时间窗口内剩余的请求数
  • X-RateLimit-Reset:限制重置的时间(通常是 Unix 时间戳)

标准化响应头遵循更新的 RateLimitRateLimit-Policy 格式,提供类似的信息,但语义定义更清晰。

并非每个 API 都使用这些响应头。有些只返回 429 状态码,不提供额外的上下文信息。你的代码应该能够处理这两种情况。

Retry-After 响应头

Retry-After 响应头告诉客户端何时可以安全地重试。它以两种格式出现:

Retry-After: 120                            // 等待的秒数
Retry-After: Wed, 21 Oct 2025 07:28:00 GMT  // 具体的时间戳

当该响应头存在时,请遵守它。当不存在时,实施指数退避——在重试之间等待逐渐增长的时间(1 秒,然后 2 秒,然后 4 秒,依此类推)。

有些服务器完全省略 Retry-After。其他服务器则不一致地包含它。构建你的重试逻辑时,要能在没有它的情况下工作,同时在可用时遵守它。

前端请求限流策略

前端密集型应用中的大多数 429 错误源于 UI 交互触发了过多的 API 调用。以下是预防方法:

对用户输入进行防抖

搜索框、自动完成字段和过滤器通常在每次按键时都会触发请求。防抖会等到用户停止输入后再发送请求:

function debounce(func, delay) {
  let timeoutId;
  return function (...args) {
    clearTimeout(timeoutId);
    timeoutId = setTimeout(() => func.apply(this, args), delay);
  };
}

// 在最后一次按键后等待 300 毫秒再搜索
const debouncedSearch = debounce(searchAPI, 300);

批量处理相关请求

如果 API 支持,不要为每个项目单独获取数据,而是合并请求。一次请求 50 个项目胜过 50 个单独的请求。

积极缓存

存储不经常变化的响应。在发出请求之前,检查是否已有有效的缓存数据。这适用于浏览器缓存和应用级缓存。

遵守速率限制信号

当你收到速率限制响应头时,主动使用它们。如果 X-RateLimit-Remaining 显示你的配额快用完了,在触及限制之前就减速。

优雅地处理 429 错误

当尽管采取了预防措施仍然发生 429 错误时:

  1. 解析响应以获取 Retry-After 或速率限制响应头
  2. 将失败的请求加入队列,在指定延迟后重试
  3. 适当地通知用户——显示加载状态,而不是错误
  4. 记录发生情况,以在监控中识别模式

避免立即重试。对刚刚告诉你慢下来的服务器进行轰炸会使情况变得更糟,并可能触发更长时间的封锁。

请注意,某些 CDN 和 WAF 会强制执行固定的冷却时间窗口,这意味着无论客户端退避逻辑如何,重试都可能被阻止数分钟。

区分 429 与其他错误

429 需要等待。503 可能很快解决或表示更大的中断。500 表明存在 bug。你的错误处理应该加以区分:

  • 429:退避、等待、按指定延迟重试
  • 503:使用退避重试,但监控是否有长时间中断
  • 500:记录错误,可能重试一次,然后优雅地失败

结论

HTTP 429 Too Many Requests 是一个速率限制信号,而不是故障。通过前端请求限流——防抖、批处理和缓存——来预防它。当它发生时,遵守 Retry-After 响应头并实施指数退避。

修复 429 错误的最佳方法是永远不触发它们。设计你的应用程序只发出必要的请求,你就很少会在生产环境中看到这个错误。

常见问题

防抖会等到活动停止后再执行函数,非常适合需要最终值的搜索输入。节流将执行限制为每个时间间隔一次,更适合滚动事件或连续操作。两者都能减少请求量,但防抖通常更适合触发 API 调用的用户输入。

从 1 秒这样的基础延迟开始,然后在每次重试时加倍。添加随机抖动以防止多个客户端的同步重试。设置最大延迟上限以避免过长等待。典型序列可能是 1 秒、2 秒、4 秒、8 秒,然后在 4-5 次尝试后停止。

是的。第三方 API 可能有严格的每密钥限制,无论你的总体流量如何。云平台上的共享 IP 地址可能触发 CDN 速率限制。页面加载或组件挂载产生的突发模式也可能超出限制,即使总用户数很少。

通常不应该。由于 429 错误是临时的且可恢复的,在后台重试时显示加载状态即可。只有在重试用尽时才显示错误。用户不需要知道速率限制——他们只想完成他们的操作。

Understand every bug

Uncover frustrations, understand bugs and fix slowdowns like never before with OpenReplay — the open-source session replay tool for developers. Self-host it in minutes, and have complete control over your customer data. Check our GitHub repo and join the thousands of developers in our community.

OpenReplay