name: rate-limiting-patterns description: 用于实现限流、节流或API配额的模式。涵盖令牌桶、滑动窗口等算法,以及分布式限流模式。 allowed-tools: Read, Glob, Grep
限流模式
通过限流、节流和配额管理来保护API和服务的模式。
何时使用此技能
- 实现API限流
- 选择限流算法
- 设计分布式限流
- 设置配额管理
- 防止滥用
为什么需要限流
保护免受:
- DDoS攻击
- 暴力尝试
- 资源耗尽
- 成本超支(云API)
- 级联故障
业务好处:
- 公平资源分配
- 可预测的性能
- 成本控制
- SLA强制执行
限流算法
令牌桶
概念: 以固定速率添加令牌,请求消耗令牌
配置:
- 桶大小(最大令牌数):100
- 填充速率:10个令牌/秒
行为:
┌─────────────────────────┐
│ 桶(容量:100) │
│ ████████████░░░░░░░░░░ │ 60个可用令牌
└─────────────────────────┘
↑ ↓
10个令牌/秒 请求消耗1个令牌
允许突发流量达到桶大小,然后限流。
特性:
- 允许受控突发
- 简单实现
- 内存高效
- 最常用算法
实现草图:
token_bucket:
tokens = min(tokens + (now - last_update) * rate, capacity)
if tokens >= cost:
tokens -= cost
return ALLOW
return DENY
漏桶
概念: 请求排队并以固定速率处理
┌─────────────────────────┐
│ 队列(容量:100) │
│ ██████████████████████ │ 等待的请求
└──────────┬──────────────┘
│
▼ 以固定速率处理(10/秒)
[处理中]
平滑流量至恒定速率。
特性:
- 平滑输出速率
- 不允许突发
- 请求可能排队
- 适合下游保护
固定窗口
概念: 在固定时间窗口内计数请求
窗口:1分钟,限制:100个请求
|-------- 窗口 1 --------|-------- 窗口 2 --------|
95个请求 ?个请求
[允许] [重置为0]
问题:边界突发
窗口1结束:100个请求
窗口2开始:100个请求
= 在约1秒内200个请求
特性:
- 简单实现
- 内存高效
- 边界突发问题
- 适合简单用例
滑动窗口日志
概念: 跟踪每个请求的时间戳
窗口:1分钟,限制:100
请求:[t-55秒, t-50秒, t-45秒, ..., t-5秒, t-2秒, 现在]
计数[现在 - 60秒, 现在]内的所有请求
无边界突发问题,但内存密集。
特性:
- 精确限流
- 无边界问题
- 内存密集(存储所有时间戳)
- 适合严格限制
滑动窗口计数器
概念: 当前和先前窗口的加权平均值
先前窗口:80个请求
当前窗口:30个请求(窗口进行40%)
加权计数 = 80 * 0.6 + 30 = 78
限制:100
结果:允许(78 < 100)
特性:
- 近似(通常足够好)
- 内存高效
- 平滑边界问题
- 最适合大多数情况
算法选择指南
| 算法 | 突发处理 | 内存 | 精度 | 用例 |
|---|---|---|---|---|
| 令牌桶 | 允许突发 | 低 | 好 | 通用API限流 |
| 漏桶 | 不允许突发 | 低 | 好 | 平滑速率强制执行 |
| 固定窗口 | 边界突发 | 极低 | 差 | 简单限制 |
| 滑动日志 | 不允许突发 | 高 | 精确 | 严格合规 |
| 滑动计数器 | 最小突发 | 低 | 好 | 最佳通用选择 |
分布式限流
挑战
单节点:简单的内存计数器
多节点:需要协调
无协调:
节点1:50个请求(低于100限制)
节点2:50个请求(低于100限制)
节点3:50个请求(低于100限制)
总计:150个请求(超过100限制!)
模式1:集中式(Redis)
┌─────────┐ ┌─────────┐ ┌─────────┐
│ 节点1 │ │ 节点2 │ │ 节点3 │
└────┬────┘ └────┬────┘ └────┬────┘
│ │ │
└───────────────┼───────────────┘
│
┌──────▼──────┐
│ Redis │
│ (计数器) │
└─────────────┘
优点:准确、一致
缺点:Redis依赖、延迟、单点故障
模式2:本地 + 同步
每个节点获取限制的一部分:
- 3个节点,100限制 → 每个节点33
定期同步以重新平衡未使用的容量。
优点:低延迟、弹性
缺点:精度较低、同步复杂
模式3:粘性会话
将同一客户端路由到同一节点(通过IP、API密钥等)
优点:简单、无需协调
缺点:负载不均、故障转移复杂
Redis实现
令牌桶与Redis:
EVALSHA token_bucket_script 1 {key}
{capacity} {refill_rate} {tokens_requested}
脚本:
1. 获取当前令牌和时间戳
2. 计算自上次请求以来添加的令牌
3. 如果足够令牌,扣除并允许
4. 返回剩余令牌
限流头部
标准头部,用于向客户端通信限制:
HTTP/1.1 200 OK
X-RateLimit-Limit: 100
X-RateLimit-Remaining: 45
X-RateLimit-Reset: 1640000000
Retry-After: 30 (当限流时)
或草案标准:
RateLimit-Limit: 100
RateLimit-Remaining: 45
RateLimit-Reset: 30
限流响应
HTTP/1.1 429 过多请求
Content-Type: application/json
Retry-After: 30
{
"error": {
"code": "RATE_LIMITED",
"message": "超出限流",
"retry_after": 30,
"limit": 100,
"window": "1m"
}
}
多层级限流
在多个层级应用限制:
层级1:全局(保护基础设施)
- 所有客户端10,000请求/秒
层级2:每租户(公平分配)
- 每个组织1,000请求/分钟
层级3:每用户(防止滥用)
- 每个用户100请求/分钟
层级4:每端点(保护昂贵操作)
- /export端点10请求/分钟
配额管理
配额 vs 限流
限流:每时间窗口的请求数(突发保护)
- 100请求/分钟
配额:周期内的总分配(预算)
- 10,000 API调用/月
配额跟踪
跟踪使用情况:
- 每个API密钥
- 每个端点
- 每个操作类型
警报阈值:
- 80%使用率:警告通知
- 100%使用率:硬阻止或超支收费
最佳实践
优雅降级
替代硬阻止:
1. 降低质量(更低分辨率、更少结果)
2. 队列请求(稍后处理)
3. 提供缓存响应
4. 允许突发但带惩罚(恢复更慢)
客户端处理
实现指数退避:
1. 收到429
2. 等待Retry-After(或1秒)
3. 重试
4. 如果再次429,等待2秒
5. 继续加倍直到最大(例如,60秒)
测试限流
测试场景:
- 突发流量
- 持续高流量
- 时钟偏差(分布式系统)
- 限制后恢复
- 多种客户端类型
相关技能
api-design-fundamentals- API设计模式idempotency-patterns- 安全重试quality-attributes-taxonomy- 性能属性