限流响应头
限流响应头(X-RateLimit-* / RateLimit-* / Retry-After)告诉客户端当前的限流状态、剩余配额和重试时机。
#type / concept
#status / evergreen
#tech / dev / backend
#resource / http
#protocol / http
[!info] related notes
- 所属 MOC: http-and-frontend-networking-moc
- 总览: http-response-headers
- 关联概念: API 认证与安全
- 配合状态码: HTTP 429 Too Many Requests
限流响应头
一句话定义
限流响应头告诉客户端:你的请求配额是多少、还剩多少、什么时候重试。前端或 SDK 根据这些头做退避、提示和重试。
它要解决什么问题
API 限流是保护后端服务的基本手段。但光返回 429 状态码不够——客户端需要知道:
- 我还能发多少请求?(限流上限)
- 我还剩多少配额?(剩余次数)
- 我什么时候可以重试?(重试时机)
两套风格
X-RateLimit-*(非标准但广泛使用)
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 42
X-RateLimit-Reset: 1716540000
| 头 | 含义 |
|---|---|
X-RateLimit-Limit | 时间窗口内的总配额 |
X-RateLimit-Remaining | 当前窗口内剩余配额 |
X-RateLimit-Reset | 配额重置的 Unix 时间戳 |
RateLimit-*(较新的 IETF 草案)
RateLimit-Limit: 1000
RateLimit-Remaining: 42
RateLimit-Reset: 60
| 头 | 含义 |
|---|---|
RateLimit-Limit | 时间窗口内的总配额 |
RateLimit-Remaining | 当前窗口内剩余配额 |
RateLimit-Reset | 配额重置前的秒数(不是时间戳) |
注意 RateLimit-Reset 和 X-RateLimit-Reset 的区别:前者是相对秒数,后者是绝对时间戳。
Retry-After
配合 429 或 503 状态码使用,告诉客户端多久后重试。
HTTP/1.1 429 Too Many Requests
Retry-After: 60
或用 HTTP 日期格式:
HTTP/1.1 503 Service Unavailable
Retry-After: Sun, 24 May 2026 10:30:00 GMT
常见搭配
# 限流
HTTP/1.1 429 Too Many Requests
Retry-After: 60
X-RateLimit-Limit: 1000
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1716540000
# 服务不可用
HTTP/1.1 503 Service Unavailable
Retry-After: 120
前端如何使用
退避重试
async function fetchWithRetry(url, options, maxRetries = 3) {
for (let i = 0; i < maxRetries; i++) {
const res = await fetch(url, options);
if (res.status === 429) {
const retryAfter = res.headers.get("Retry-After");
const waitMs = retryAfter
? parseInt(retryAfter) * 1000
: Math.pow(2, i) * 1000; // 指数退避
await new Promise(r => setTimeout(r, waitMs));
continue;
}
return res;
}
throw new Error("Max retries exceeded");
}
显示剩余配额
const res = await fetch("/api/data");
const remaining = res.headers.get("X-RateLimit-Remaining");
if (remaining !== null && parseInt(remaining) < 10) {
showToast("API 调用次数即将用完,请稍后再试");
}
跨域场景
限流头是自定义响应头,跨域时前端 JS 默认读不到。需要后端配合:
Access-Control-Expose-Headers: X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, Retry-After
否则:
res.headers.get("X-RateLimit-Remaining") // null
边界与易混淆点
- X-RateLimit- 不是 RFC 标准*:是约定俗成的实践。不同 API 提供商的实现可能有差异。
- RateLimit- 是 IETF 草案*:draft-ietf-httpapi-ratelimit-headers,尚未正式成为 RFC,但已被部分 API 采用。
- Retry-After 的值是秒数或 HTTP 日期:不是毫秒,不是 Unix 时间戳(除非是 X-RateLimit-Reset)。
- 429 不一定带限流头:有些 API 只返回 429 状态码,不提供详细限流信息。这时前端只能用指数退避。
- 限流窗口设计影响用户体验:滑动窗口比固定窗口更平滑,但实现更复杂。