Api Integration
指导前端与后端API集成的类型、错误、鉴权及实时通信设计规范。
下载 255
提供API限流算法、实现方案与最佳实践,适用于防止滥用和配置流量控制。
openclaw skills install @wpank/api-rate-limiting命令、参数、文件名以原文为准
| 算法 | 准确性 | 突发流量处理能力 | 适用场景 |
|---|---|---|---|
| 令牌桶 | 高 | 允许可控的突发请求 | API 速率限制、流量整形 |
| 漏桶 | 高 | 完全平滑突发流量 | 稳定速率处理、队列系统 |
| 固定窗口 | 低 | 允许边缘突发(最高2倍) | 简单场景、原型开发 |
| 滑动窗口日志 | 极高 | 精确控制 | 严格合规、计费关键场景 |
| 滑动窗口计数器 | 高 | 良好近似 | 生产环境 API — 最佳平衡选择 |
固定窗口问题: 用户在 11:59 发送完全部限额请求,又在 12:01 再次发送,导致实际速率翻倍。滑动窗口可解决此问题。
桶中最多持有指定数量的令牌。令牌以固定速率补充。每次请求消耗一个令牌。
class TokenBucket:
def __init__(self, capacity: int, refill_rate: float):
self.capacity = capacity
self.tokens = capacity
self.refill_rate = refill_rate # 每秒补充的令牌数
self.last_refill = time.monotonic()
def allow(self) -> bool:
now = time.monotonic()
elapsed = now - self.last_refill
self.tokens = min(self.capacity, self.tokens + elapsed * self.refill_rate)
self.last_refill = now
if self.tokens >= 1:
self.tokens -= 1
return True
return False结合固定窗口与滑动窗口日志的优点——根据前一窗口的计数按重叠比例加权:
def sliding_window_allow(key: str, limit: int, window_sec: int) -> bool:
now = time.time()
current_window = int(now // window_sec)
position_in_window = (now % window_sec) / window_sec
prev_count = get_count(key, current_window - 1)
curr_count = get_count(key, current_window)
estimated = prev_count * (1 - position_in_window) + curr_count
if estimated >= limit:
return False
increment_count(key, current_window)
return True| 方案 | 作用范围 | 适用场景 |
|---|---|---|
| 内存内实现 | 单个服务实例 | 无延迟、无外部依赖 |
Redis (INCR + EXPIRE) | 分布式部署 | 多实例部署场景 |
| API 网关 | 边缘层 | 无需代码、内置监控仪表盘 |
| 中间件 | 服务级别 | 细粒度的用户/接口级控制 |
建议在网关层设置外层防护,并在应用层实现细粒度控制。
所有响应中均应返回速率限制信息,包括成功请求:
RateLimit-Limit: 1000
RateLimit-Remaining: 742
RateLimit-Reset: 1625097600
Retry-After: 30| 响应头 | 应包含时机 |
|---|---|
RateLimit-Limit | 所有响应 |
RateLimit-Remaining | 所有响应 |
RateLimit-Reset | 所有响应 |
Retry-After | 仅限 429 响应 |
{
"error": {
"code": "rate_limit_exceeded",
"message": "速率限制已超出。每小时最多 1000 次请求。",
"retry_after": 30,
"limit": 1000,
"reset_at": "2025-07-01T12:00:00Z"
}
}速率限制不应返回 500 或 503 错误码,应使用 429 表示请求过多。
在多个粒度上应用限制策略:
| 作用范围 | 关键标识 | 示例限制 | 目的 |
|---|---|---|---|
| 按 IP 地址 | 客户端 IP | 100 次/分钟 | 防止滥用 |
| 按用户 | 用户 ID | 1000 次/小时 | 公平使用 |
| 按 API 密钥 | API 密钥 | 5000 次/小时 | 服务间调用 |
| 按接口 | 路由 + 密钥 | /search 接口 60 次/分钟 | 保护高成本操作 |
分层定价方案:
| 层级 | 速率限制 | 突发容量 | 价格 |
|---|---|---|---|
| 免费版 | 100 次/小时 | 10 | $0 |
| 专业版 | 5,000 次/小时 | 100 | $49/月 |
| 企业版 | 100,000 次/小时 | 2,000 | 自定义 |
评估顺序应从最具体到最不具体:按接口 > 按用户 > 按 IP。
基于 Redis 的实现方式,确保跨多个实例的一致性:
def redis_rate_limit(redis, key: str, limit: int, window: int) -> bool:
pipe = redis.pipeline()
now = time.time()
window_key = f"rl:{key}:{int(now // window)}"
pipe.incr(window_key)
pipe.expire(window_key, window * 2)
results = pipe.execute()
return results[0] <= limit原子 Lua 脚本(防止竞态条件):
local key = KEYS[1]
local limit = tonumber(ARGV[1])
local window = tonumber(ARGV[2])
local current = redis.call('INCR', key)
if current == 1 then
redis.call('EXPIRE', key, window)
end
return current <= limit and 1 or 0切勿使用先查询再设置的方式——中间间隙可能导致计数超限。
NGINX:
http {
limit_req_zone $binary_remote_addr zone=api:10m rate=10r/s;
server {
location /api/ {
limit_req zone=api burst=20 nodelay;
limit_req_status 429;
}
}
}Kong:
plugins:
- name: rate-limiting
config:
minute: 60
hour: 1000
policy: redis
redis_host: redis.internal客户端必须妥善处理 429 响应:
async function fetchWithRetry(url: string, maxRetries = 3): Promise<Response> {
for (let attempt = 0; attempt < maxRetries; attempt++) {
const res = await fetch(url);
if (res.status !== 429) return res;
const retryAfter = res.headers.get('Retry-After');
const delay = retryAfter
? parseInt(retryAfter, 10) * 1000
: Math.min(1000 * 2 ** attempt, 30000);
await new Promise(r => setTimeout(r, delay));
}
throw new Error('重试后仍超出速率限制');
}Retry-After 头部,必须遵守其值Retry-After 缺失时,使用带随机抖动的指数退避持续跟踪以下关键指标:
| 反模式 | 修复建议 |
|---|---|
| 仅应用层限流 | 始终结合基础设施层级的限流策略 |
| 无重试指引 | 在返回 429 时始终包含 Retry-After 头部 |
| 限流策略不一致 | 相同接口在各服务间应保持一致的限流规则 |
| 无突发流量允许 | 允许受控的突发流量以应对合法请求高峰 |
| 静默丢弃请求 | 始终返回 429,以便客户端区分于其他错误 |
| 全局单一计数器 | 使用按端点划分的计数器,保护高成本操作 |
| 硬编码限流值 | 使用配置而非代码中的常量定义限流值 |
200 OK** — 客户端必须知道请求已被限流已收录 10 个 Skill