不喜欢广告? 无广告 今天

API 速率限制 — 头部信息、指数退避以及应对 429 错误

更新于

您遇到了429错误。API提示您需要放慢速度。以下是如何解析X-RateLimit-*头部信息、理解Retry-After字段,以及实现带抖动的指数退避策略,以确保您的集成在遇到速率限制时能够优雅处理,而不是持续冲击服务器。

API速率限制——响应头、指数退避与应对429错误 1
广告 移除?

你遇到了429错误。可能你的Webhook处理器崩溃了,也可能某个批量任务被静默丢弃了。API返回了“请求过多”的提示,以及一堆你可能已经滚动过去的响应头。

这些响应头才是关键。以下是它们的解读方法,以及如何编写不会使问题恶化的重试逻辑。

真正重要的响应头

大多数受速率限制的API在每次响应中都会返回这些头信息——不仅限于429错误:

  • X-RateLimit-Limit ——你在当前时间窗口内允许的最大请求数。GitHub的REST API为认证用户每小时提供5000次请求;未认证请求则为60次。
  • X-RateLimit-Remaining ——当前时间窗口内剩余的请求数。当这个值变为0时,下一次请求将返回429错误。
  • X-RateLimit-Reset ——时间窗口重置的时间,以Unix时间戳表示。这是大多数开发者忽略的头,却是最有用的一个。
  • X-RateLimit-Used (GitHub专用)——到目前为止已使用的请求数。与 Limit - Remaining 类似,但可用于验证。
  • Retry-After ——仅在429响应中出现。可能是以秒为单位的等待时间,也可能是HTTP日期字符串。如果API发送了该头,请使用它——它比你自己计算的更精确。

真实的GitHub响应头看起来如下:

X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 4823
X-RateLimit-Reset: 1716998400
X-RateLimit-Used: 177
X-RateLimit-Resource: core

X-RateLimit-Resource header是GitHub特有的:它们为REST API、搜索和GraphQL分别维护独立的配额池。消耗搜索配额(每分钟最多30次)不会影响核心配额——反之亦然。

Stripe不同

Stripe不使用 X-RateLimit-* 命名。它们的头信息前缀不同:

Stripe-Ratelimit-Limit: 100
Stripe-Ratelimit-Remaining: 97
Stripe-Ratelimit-Reset: 1716998460

在429响应中:

Retry-After: 30

Stripe的默认配额是每100个实时模式请求 每秒,而不是每小时。这一点比听起来更重要:如果你不进行端侧限流,一个导入500个客户的循环可能在不到5秒内耗尽该窗口。

Stripe还区分了请求速率限制和资源特定限制(例如短时间内创建过多客户)。429响应体中会明确指出你触碰了哪个限制——始终记录完整的响应体,而不仅仅是状态码。

解析重置时间戳

X-RateLimit-Reset 值是一个Unix时间戳。 1716998400 乍一看它没有意义,但很容易解码:使用 Unix 时间戳转换器 将其转换为可读的UTC时间,以查看重置还剩多久。

在代码中: reset_time - time.now() 给出从现在到窗口重置的秒数。但先检查 X-RateLimit-Remaining ——如果你仍有配额,就无需等待。

429响应体告诉你什么

仅凭429状态码是不够的。响应体通常会说明是哪个限制被触发了:

他们的 OG 图片包含帖子标题、日期和阅读时间

{
  "message": "API rate limit exceeded for user ID 12345.",
  "documentation_url": "https://docs.github.com/rest/overview/rate-limits"
}

Stripe:

{
  "error": {
    "code": "rate_limit",
    "message": "Too many requests hit the API too quickly.",
    "type": "invalid_request_error"
  }
}

OpenAI更进一步:错误信息会说明你触碰的是每分钟令牌限制还是每分钟请求限制,这会彻底改变你的重试策略。始终记录完整的429响应体。

指数退避加抖动

最简单的修复方法:捕获429错误,等待1秒后重试。这种方法有两个问题:

  • 如果你有多个工作进程同时访问同一个端点,它们都会等待1秒后同时重试——这会引发同步重试风暴,重现问题。
  • 如果已经耗尽了每小时或每日配额,1秒的等待毫无意义。你只会收集到更多的429错误。

正确的做法是指数退避加抖动:每次重试的等待时间都比上一次更长,并加入随机成分以分散并发工作进程的重试时间。

import time
import random
import requests

def fetch_with_backoff(url, headers, max_retries=5):
    base_delay = 1  # seconds

    for attempt in range(max_retries):
        response = requests.get(url, headers=headers)

        if response.status_code != 429:
            return response

        # Prefer Retry-After if the API provides it
        retry_after = response.headers.get("Retry-After")
        if retry_after:
            wait = int(retry_after)
        else:
            # Fall back to X-RateLimit-Reset
            reset = response.headers.get("X-RateLimit-Reset")
            if reset:
                wait = max(0, int(reset) - int(time.time()))
            else:
                # Pure exponential backoff with full jitter
                cap = 60  # max wait: 60s
                wait = random.uniform(0, min(cap, base_delay * (2 ** attempt)))

        print(f"Rate limited. Attempt {attempt + 1}/{max_retries}. Waiting {wait:.1f}s")
        time.sleep(wait)

    raise Exception(f"Max retries exceeded after {max_retries} attempts")

该实现中的优先级顺序是刻意设计的:

  • Retry-After 优先 ——如果API明确告诉你等待多久,请使用它。不要用自己计算的值去猜测。
  • X-RateLimit-Reset 作为备选 ——计算实际到重置的秒数,而不是猜测一个固定延迟。
  • 完全抖动作为最后手段random.uniform(0, cap) 将重试分布在退避窗口的整个范围内。AWS架构博客中将这种做法称为“完全抖动”,并指出它相比等量抖动或无抖动能显著减少服务器端的碰撞。
  • max(0, ...) 在重置时 ——重置时间戳在你计算时可能已经过期。需防止出现负的等待时间导致你的处理器崩溃。

常见错误

将非429错误误认为是速率限制错误。 503是服务器错误,401表示你的凭据错误。在应用速率限制重试逻辑前,必须明确检查 status_code == 429

吞下429错误并返回空数据。 静默失败比抛出异常更难调试。请暴露错误。

使用固定延迟。 如果你在47分钟内耗尽了每小时配额,等待5秒毫无意义。应根据重置时间戳来计算。

无限重试。 设置一个 max_retries 上限,并在耗尽后抛出异常。某些429错误表示配额耗尽,直到下一个计费周期才会恢复——无限重试循环是一个错误。

未主动监控 X-RateLimit-Remaining。 如果 Remaining 降至10%以下 Limit,应在达到零前开始分散请求。大多数SDK不会自动执行此操作。额外的延迟仅为几毫秒,但好处是永远不会再看到429错误。

总结

429错误不是一次性的,你只需修复后就忘掉的问题。它是一个持续存在的约束,忽略伴随的响应头意味着你将不断撞上同样的墙。当API提供时,请使用 Retry-After 。当它没有提供时,请根据 X-RateLimit-Reset 计算。加入抖动以避免重试同步。设置上限,防止无限重试循环演变为生产事故。

当你盯着 X-RateLimit-Reset: 1716998400 并疑惑它何时真正到来时—— Unix 时间戳转换器 会告诉你答案。

想要享受无广告的体验吗? 立即无广告

安装我们的扩展

将 IO 工具添加到您最喜欢的浏览器,以便即时访问和更快地搜索

添加 Chrome 扩展程序 添加 边缘延伸 添加 Firefox 扩展 添加 Opera 扩展

记分板已到达!

记分板 是一种有趣的跟踪您游戏的方式,所有数据都存储在您的浏览器中。更多功能即将推出!

广告 移除?
广告 移除?
广告 移除?

新闻角 包含技术亮点

参与其中

帮助我们继续提供有价值的免费工具

给我买杯咖啡
广告 移除?