速率限制与重试策略
本文说明 AnideaAI 的限流维度、分组差异和 429 处理方式。
限流维度总览
AnideaAI 当前主要有 5 层限制:
- 全局接口频率限制(按 IP)
- 关键接口频率限制(按 IP)
- 模型请求频率限制(按用户/分组)
- 订阅窗口限制(5 小时 / 日 / 周 / 月)
- 并发会话限制(按用户)
1) 全局接口频率限制(IP 维度)
默认值(管理员可改):
GLOBAL_API_RATE_LIMIT = 180GLOBAL_API_RATE_LIMIT_DURATION = 180秒
可以理解为:同一 IP 在 180 秒内最多 180 次 API 请求。
命中后典型返回:
- HTTP
429 error.code = RATE_GLOBAL_LIMIT
2) 关键接口频率限制(IP 维度)
用于登录、支付、OAuth 等敏感接口。
默认值(管理员可改):
CRITICAL_RATE_LIMIT = 20CRITICAL_RATE_LIMIT_DURATION = 1200秒
命中后通常返回 429。
3) 模型请求频率限制(用户/分组维度)
该限制默认可关闭,开启后有两个计数器:
- 总请求数(包含失败)
- 成功请求数(只统计状态码小于 400)
默认配置:
- 时间窗口:
1分钟 - 总请求上限:
0(0 代表不限制) - 成功请求上限:
1000
分组可单独覆盖:
- 配置结构为
{"group_name": [总请求上限, 成功请求上限]} - 同一用户不同分组可以有不同上限
命中后典型返回:
- HTTP
429 error.code = RATE_MODEL_LIMIT
4) 订阅窗口限制(额度维度)
如果账号使用订阅/月卡,会额外检查窗口额度:
- 5 小时窗口
- 日窗口
- 周窗口
- 月窗口
典型错误码:
QUOTA_MONTHLY_5H_EXCEEDEDQUOTA_MONTHLY_DAILY_EXCEEDEDQUOTA_MONTHLY_WEEKLY_EXCEEDEDQUOTA_MONTHLY_EXCEEDED
说明:很多平台会直接写 RPM/TPM。AnideaAI 这里是“请求次数窗口 + 额度窗口”双轨控制。额度窗口本质上也是对 token 消耗速度的限制。
5) 并发限制(会话维度)
当并发会话占满时,返回:
- HTTP
429 error.code = RATE_CONCURRENT_FULL- 可能附带响应头
Retry-After(秒)
处理时应优先使用 Retry-After,不要立即重试。
不同等级/分组限制差异
AnideaAI 的差异主要来自:
- 账号计划差异:订阅额度窗口不同。
- 分组差异:模型频率限制可按分组覆盖。
- Key 差异:不同 Key 可绑定不同分组、模型白名单和额度。
建议做法:
- 生产和测试环境分开 Key。
- 大流量服务单独分组,避免互相挤占频率。
- 给高优先级业务预留独立 Key 和分组。
429 处理建议
最小重试原则
- 先识别
error.code,不要把所有429当成同一种问题。 - 有
Retry-After就严格按它等待。 - 没有
Retry-After时使用指数退避 + 随机抖动。 - 设置最大重试次数,避免无限重试。
JavaScript 示例(指数退避)
function sleep(ms) {
return new Promise((resolve) => setTimeout(resolve, ms));
}
async function requestWithRetry(doRequest, maxRetry = 5) {
for (let i = 0; i <= maxRetry; i++) {
const res = await doRequest();
if (res.status !== 429) return res;
const retryAfter = Number(res.headers.get("retry-after") || 0);
if (i === maxRetry) return res;
const baseMs = retryAfter > 0 ? retryAfter * 1000 : 500 * Math.pow(2, i);
const jitterMs = Math.floor(Math.random() * 300);
await sleep(baseMs + jitterMs);
}
}
Python 示例(指数退避)
import random
import time
def call_with_retry(do_request, max_retry=5):
for i in range(max_retry + 1):
resp = do_request()
if resp.status_code != 429:
return resp
if i == max_retry:
return resp
retry_after = int(resp.headers.get("Retry-After", "0") or "0")
base = retry_after if retry_after > 0 else 0.5 * (2 ** i)
time.sleep(base + random.uniform(0, 0.3))