API Rate Limiting

提供API限流算法、实现方案与最佳实践,适用于防止滥用和配置流量控制。

已扫描
适合谁
后端开发工程师、API架构师
不适合谁
无编程基础的普通用户、仅需简单工具的非技术人员
国内可用性
需网络配置。可能需要网络配置或第三方服务可访问。
安装难度
新手友好(★☆☆)。基于终端操作、依赖、API Key 和本地环境要求的初步判断。

安装与下载

openclaw skills install @wpank/api-rate-limiting

Skill 说明

命令、参数、文件名以原文为准

速率限制模式

算法对比

算法准确性突发流量处理能力适用场景
令牌桶允许可控的突发请求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 网关边缘层无需代码、内置监控仪表盘
中间件服务级别细粒度的用户/接口级控制

建议在网关层设置外层防护,并在应用层实现细粒度控制。


HTTP 响应头

所有响应中均应返回速率限制信息,包括成功请求:

RateLimit-Limit: 1000
RateLimit-Remaining: 742
RateLimit-Reset: 1625097600
Retry-After: 30
响应头应包含时机
RateLimit-Limit所有响应
RateLimit-Remaining所有响应
RateLimit-Reset所有响应
Retry-After仅限 429 响应

429 错误响应体示例

{
  "error": {
    "code": "rate_limit_exceeded",
    "message": "速率限制已超出。每小时最多 1000 次请求。",
    "retry_after": 30,
    "limit": 1000,
    "reset_at": "2025-07-01T12:00:00Z"
  }
}

速率限制不应返回 500503 错误码,应使用 429 表示请求过多。


速率限制层级

在多个粒度上应用限制策略:

作用范围关键标识示例限制目的
按 IP 地址客户端 IP100 次/分钟防止滥用
按用户用户 ID1000 次/小时公平使用
按 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

切勿使用先查询再设置的方式——中间间隙可能导致计数超限。


API 网关配置

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 状态码的请求占比(持续超过 5% 时触发告警)
  • 接近限流警告 — 剩余配额低于限额 10% 的请求
  • 主要违规者 — 频繁触发限流的 API 密钥或 IP 地址
  • 限流余量 — 正常流量距离上限的距离
  • 误触发 — 合法用户被错误地限流

反模式

反模式修复建议
仅应用层限流始终结合基础设施层级的限流策略
无重试指引在返回 429 时始终包含 Retry-After 头部
限流策略不一致相同接口在各服务间应保持一致的限流规则
无突发流量允许允许受控的突发流量以应对合法请求高峰
静默丢弃请求始终返回 429,以便客户端区分于其他错误
全局单一计数器使用按端点划分的计数器,保护高成本操作
硬编码限流值使用配置而非代码中的常量定义限流值

绝对不要做

  1. 绝不要对健康检查端点进行限流 — 监控系统会误报
  2. 绝不要仅使用客户端提供的标识符作为限流键 — 极易被伪造
  3. **绝不要在限流时返回 200 OK** — 客户端必须知道请求已被限流
  4. 绝不要在未测量实际流量的情况下设置限流 — 会导致合法用户被阻断或限流值过高无效
  5. 绝不要在无关租户之间共享计数器 — 避免“噪音邻居”问题
  6. 绝不要跳过内部 API 的限流 — 行为异常的内部服务可能拖垮共享基础设施
  7. 绝不要在未记录日志的情况下实现限流 — 缺乏可见性将影响限流调优和滥用检测
W
@wpank

已收录 10 个 Skill

相关推荐