更适合拦截短时间内的瞬时打满,避免误操作、脚本异常或前端死循环把网关瞬间压穿。
正在准备内容,请稍候。
正在切换页面...
网络较慢时会保留当前状态,直到新页面准备完成。
这页专门解释当前平台的分钟与小时窗口、429 响应、限流头和客户端该如何处理频率限制。真正稳定的接入不是“永远不报 429”,而是收到 429 也知道怎么优雅退让。
限流判断基于当前令牌近期的调用日志。分钟窗口更偏突发保护,小时窗口更偏持续消耗治理,两个窗口一起工作时,才能同时照顾用户体验和平台稳定性。
更适合拦截短时间内的瞬时打满,避免误操作、脚本异常或前端死循环把网关瞬间压穿。
更适合限制长时间持续高频调用,帮助平台避免单个令牌在较长周期内持续吃满额度。
只有分钟窗口容易放过缓慢刷量,只有小时窗口又来不及拦突发峰值,所以当前实现同时检查两层窗口。
当前实现会返回 `x-ratelimit-limit-minute`、`x-ratelimit-remaining-minute`、`x-ratelimit-limit-hour`、 `x-ratelimit-remaining-hour`。命中限流时还会额外返回 `Retry-After`。
HTTP/1.1 200 OK Content-Type: application/json x-ratelimit-limit-minute: 6000 x-ratelimit-remaining-minute: 41 x-ratelimit-limit-hour: 60000 x-ratelimit-remaining-hour: 582
HTTP/1.1 429 Too Many Requests
Content-Type: application/json
Retry-After: 60
x-ratelimit-limit-minute: 6000
x-ratelimit-remaining-minute: 0
x-ratelimit-limit-hour: 60000
x-ratelimit-remaining-hour: 247
{
"error": {
"message": "请求过于频繁,已触发分钟级限流,请稍后再试",
"type": "rate_limit_error",
"code": "rate_limit_exceeded"
}
}限流本身是正常行为。好的客户端会读取头信息、减少无效重试、在 UI 上给出明确反馈,而不是在后台默默无限循环。
async function callWithRateLimitBackoff(payload, attempt = 0) {
const response = await fetch("https://api.ciyuan.com/v1/messages", {
method: "POST",
headers: {
"Content-Type": "application/json",
Authorization: `Bearer ${process.env.TOKENAI_API_KEY}`,
},
body: JSON.stringify(payload),
});
if (response.ok) {
return response.json();
}
if (response.status !== 429 || attempt >= 3) {
throw new Error(`request failed: ${response.status}`);
}
const retryAfterSeconds = Number(response.headers.get("Retry-After") ?? "60");
await new Promise((resolve) => setTimeout(resolve, retryAfterSeconds * 1000));
return callWithRateLimitBackoff(payload, attempt + 1);
}如果你在做平台运营,最有价值的问题不是“有没有 429”,而是“是谁在持续吃满窗口”“这是产品设计问题还是业务增长问题”。