前言
最近在梳理某大模型平台的限流策略,因此也借此梳理下系统有哪些限流手段,最常见的就是用量限流了,例如我们买codex、各种coding plan都有一个几个小时的窗口限制,防止你一次性把token消耗完。
仔细想想,这个既简单又很复杂,也许是因为我没设计过这种系统,这次正好让AI帮我梳理下,但我发现他写的有点搓,我反反复复还对话了很多次,还手动修改了一部分。
注:此文代码均为AI生成,且未提供任何材料辅助AI生成。
正文
用量限流系统是 API 网关中的核心组件,用于控制用户在特定时间窗口内的请求量或 Token 消耗量。对于大语言模型服务而言,限流不仅是资源保护的手段,更是成本控制、公平分配和服务质量保障的基础设施。
本文将从需求分析、架构设计、技术选型到代码实现,完整讲解如何设计一个支持近 5 小时、近一周、近一月三个时间窗口的网关用量限流系统。
需求分析:为什么需要限流系统?
大模型服务的资源瓶颈
大语言模型的推理成本远高于传统 API:
1 | 传统 REST API: |
核心问题:
flowchart TD
A[大模型服务特点] --> B1["GPU 算力昂贵"]
A --> B2["推理延迟高"]
A --> B3["Token 成本按量计费"]
B1 --> C["资源有限"]
B2 --> C
B3 --> C
C --> D1["需要控制并发"]
C --> D2["需要限制用量"]
C --> D3["需要公平分配"]
style A fill:#ffe0b2
style C fill:#ff8a65,color:#fff
style D1 fill:#66bb6a,color:#fff
style D2 fill:#42a5f5,color:#fff
style D3 fill:#ab47bc,color:#fff
限流的核心价值
| 维度 | 问题 | 限流的作用 |
|---|---|---|
| 资源保护 | GPU 过载导致服务崩溃 | 防止突发流量压垮服务 |
| 成本控制 | 恶意调用或异常循环 | 避免巨额账单 |
| 公平分配 | 单一用户占用全部资源 | 多租户场景下的配额管理 |
| 服务质量 | 无限制导致延迟飙升 | 保障 SLA 和用户体验 |
| 计费基础 | 按用量定价 | 支撑 Tier 定价模型 |
限流 vs 熔断 vs 降级
flowchart TD
A[服务保护机制] --> B1["限流 Rate Limiting"]
A --> B2["熔断 Circuit Breaking"]
A --> B3["降级 Fallback"]
B1 --> C1["事前预防\n控制请求进入"]
B2 --> C2["事中保护\n快速失败"]
B3 --> C3["事后兜底\n返回降级响应"]
style B1 fill:#66bb6a,color:#fff
style B2 fill:#ffa726
style B3 fill:#ef5350,color:#fff
- 限流:控制流量进入的速度(事前)
- 熔断:检测到异常后快速失败(事中)
- 降级:返回简化响应保证可用性(事后)
限流系统核心概念
三个关键时间维度
大模型服务的用量限流通常采用多时间窗口策略:
flowchart TD
A[用量限流] --> B1["近 5 小时\n短期突发控制"]
A --> B2["近一周\n中期用量管理"]
A --> B3["近一月\n长期配额规划"]
B1 --> C1["粒度: 分钟级"]
B1 --> D1["场景: 防刷、控并发"]
B2 --> C2["粒度: 小时级"]
B2 --> D2["场景: 周配额管理"]
B3 --> C3["粒度: 天级"]
B3 --> D3["场景: 月套餐限制"]
style B1 fill:#42a5f5,color:#fff
style B2 fill:#66bb6a,color:#fff
style B3 fill:#ffa726
style D1 fill:#e3f2fd
style D2 fill:#e8f5e9
style D3 fill:#fff3e0
| 时间窗口 | 控制粒度 | 典型场景 | 存储策略 |
|---|---|---|---|
| 近 5 小时 | 分钟级 | 短期突发控制、实时用量限制 | Redis Sorted Set + Hash(滑动窗口) |
| 近一周 | 小时级 | 周配额管理、按周计费 | Redis(配额计数器) |
| 近一月 | 天级 | 月套餐限制、月度预算控制 | Redis + MySQL(配额计数器) |
配额限流的核心概念
配额限流(Quota-based Rate Limiting)的核心是控制特定时间窗口内的总用量,而非单纯限制请求频率。
flowchart TD
A[配额限流] --> B1["用量计量\nToken/请求数"]
A --> B2["时间窗口\n5h/7d/30d"]
A --> B3["配额上限\n动态配置"]
B1 --> C["实时统计 + 超限拒绝"]
B2 --> C
B3 --> C
C --> D1["短期:防突发"]
C --> D2["中期:控预算"]
C --> D3["长期:管套餐"]
style A fill:#ff7043,color:#fff
style C fill:#42a5f5,color:#fff
style D1 fill:#66bb6a,color:#fff
style D2 fill:#ffa726
style D3 fill:#ab47bc,color:#fff
配额限流 vs 频率限流:
| 维度 | 频率限流(Rate Limiting) | 配额限流(Quota Limiting) |
|---|---|---|
| 控制对象 | 请求速率(QPS/RPM) | 时间窗口内的总用量 |
| 典型场景 | 防刷、控并发 | 套餐管理、成本控制 |
| 时间维度 | 单一窗口(如 1 分钟) | 多窗口组合(5h/7d/30d) |
| 计量单位 | 请求次数 | Token 数/请求数/Credits |
| 重置策略 | 固定周期重置 | 按套餐周期/手动充值 |
限流算法选型
1. 固定窗口计数器(Fixed Window Counter)
最简单的限流算法:将时间划分为固定窗口,统计每个窗口内的请求数。
flowchart TD
A[请求到达] --> B[确定当前窗口]
B --> C[窗口内计数 +1]
C --> D{计数 > 阈值?}
D -->|是| E["❌ 拒绝请求"]
D -->|否| F["✅ 放行请求"]
style A fill:#bbdefb
style D fill:#ffe0b2
style E fill:#ef5350,color:#fff
style F fill:#66bb6a,color:#fff
实现示例(Go):
1 | type FixedWindowLimiter struct { |
缺陷:边界突变问题
1 | 时间轴: |---窗口1---|---窗口2---|---窗口3---| |
2. 滑动窗口计数器(Sliding Window Counter)
将窗口细分为多个子窗口,通过加权计算实现平滑过渡。
flowchart TD
A[请求到达] --> B[计算当前时间戳]
B --> C[移除过期子窗口]
C --> D[聚合当前窗口内所有子窗口计数]
D --> E{总数 > 阈值?}
E -->|是| F["❌ 拒绝"]
E -->|否| G["✅ 放行 + 当前子窗口 +1"]
style A fill:#e3f2fd
style D fill:#ffe0b2
style F fill:#ef5350,color:#fff
style G fill:#66bb6a,color:#fff
滑动窗口 vs 固定窗口对比:
flowchart LR
subgraph Fixed["固定窗口"]
A1["|===|===|===|"]
A2["计数突变 ❌"]
end
subgraph Sliding["滑动窗口"]
B1["|==|==|==|==|==|"]
B2["平滑过渡 ✅"]
end
style Fixed fill:#ffebee
style Sliding fill:#e8f5e9
style A2 fill:#ffcdd2
style B2 fill:#c8e6c9
3. 令牌桶算法(Token Bucket)
以固定速率生成令牌,请求需要消耗令牌才能通过,允许一定程度的突发。
flowchart TD
A[令牌生成器] -->|"恒定速率 r"| B["令牌桶\n容量: N"]
C[请求到达] --> D{桶中有令牌?}
B --> D
D -->|是| E["消耗 1 个令牌\n✅ 放行"]
D -->|否| F["❌ 拒绝或排队"]
style A fill:#ab47bc,color:#fff
style B fill:#42a5f5,color:#fff
style D fill:#ffe0b2
style E fill:#66bb6a,color:#fff
style F fill:#ef5350,color:#fff
核心公式:
当前令牌数计算:
$$\text{tokens} = \min(\text{capacity}, \text{old_tokens} + (\text{now} - \text{last_time}) \times \text{rate})$$
Go 实现:
1 | type TokenBucket struct { |
4. 漏桶算法(Leaky Bucket)
与令牌桶相反,漏桶以固定速率流出请求,平滑突发流量。
flowchart TD
A[请求流入] --> B["水桶\n容量: N"]
B -->|"恒定速率 r"| C[请求流出]
B --> D{桶满?}
D -->|是| E["❌ 溢出丢弃"]
D -->|否| F["✅ 入桶等待"]
style A fill:#bbdefb
style B fill:#ffa726,color:#fff
style C fill:#66bb6a,color:#fff
style E fill:#ef5350,color:#fff
算法对比总结
| 算法 | 特点 | 适用场景 | 是否允许突发 |
|---|---|---|---|
| 固定窗口 | 实现简单,边界突变 | 简单场景 | ❌ |
| 滑动窗口 | 精确统计,存储开销大 | 精确用量控制 | ❌ |
| 令牌桶 | 允许突发,实现复杂 | API 限流 | ✅ |
| 漏桶 | 平滑输出,延迟增加 | 流量整形 | ❌ |
多时间窗口架构设计
核心挑战
同时维护 5 小时、7 天、30 天三个窗口,需要解决:
- 存储效率:细粒度数据存储成本高
- 查询性能:多窗口聚合查询延迟
- 原子性:分布式环境下计数一致性
- 过期清理:自动清理过期数据
flowchart TD
A[多窗口限流挑战] --> B1["存储效率\n细粒度 vs 成本"]
A --> B2["查询性能\n聚合延迟"]
A --> B3["原子性\n分布式一致"]
A --> B4["过期清理\n自动化管理"]
B1 --> C["架构设计"]
B2 --> C
B3 --> C
B4 --> C
style A fill:#ff7043,color:#fff
style C fill:#42a5f5,color:#fff
数据结构设计
配额限流系统根据时间窗口特性,采用不同的数据结构:
5 小时窗口:Sorted Set + Hash(滑动窗口)
5 小时窗口需要动态滑动,任何时刻查询都是过去 5 小时的用量,因此使用时间分桶方案。
1 | Key: rate_limit:user:{user_id}:5h:index |
优势:
- ✅ 真正的滑动窗口,任何时刻查询都是过去 5 小时
- ✅ 自动过期机制(ZREMRANGEBYSCORE)
- ✅ 按桶聚合,内存占用可控(5小时/30分钟 = 10个桶)
- ✅ 支持用量趋势分析
劣势:
- ⚠️ 需要两个数据结构配合(Sorted Set + Hash)
- ⚠️ 查询时需要聚合计算(但 10 个桶的聚合 < 1ms)
7 天/30 天窗口:配额计数器
长周期窗口不需要精确滑动,使用简单计数器即可。
1 | Key: rate_limit:user:{user_id}:7d:quota |
优势:
- ✅ 数据结构简单,只需两个 Key
- ✅ 查询快速(两次 GET)
- ✅ 更新快速(INCRBY)
- ✅ 自动过期(EXPIRE)
劣势:
- ⚠️ 固定窗口,过期时突然清零
- ⚠️ 无法追溯历史用量
多窗口检查流程
flowchart TD
A[请求到达] --> B["提取用户ID、Token数"]
B --> C["检查 5 小时窗口"]
C --> D{5h 超限?}
D -->|是| E["❌ 返回 429"]
D -->|否| F["检查 7 天窗口"]
F --> G{7d 超限?}
G -->|是| E
G -->|否| H["检查 30 天窗口"]
H --> I{30d 超限?}
I -->|是| E
I -->|否| J["✅ 放行请求"]
J --> K["异步更新计数器"]
style A fill:#bbdefb
style E fill:#ef5350,color:#fff
style J fill:#66bb6a,color:#fff
style K fill:#ffe0b2
配额存储与同步架构设计
核心问题:配额数据如何存储?
配额限流系统面临一个关键架构挑战:
1 | 用户购买配额 → 数据存储在哪里? |
方案一:Redis 主存储 + MySQL 持久化备份(推荐)
核心思路:配额数据以 Redis 为准,MySQL 仅做持久化和审计。
flowchart TD
A[用户购买配额] --> B[写入 MySQL]
B --> C[同步写入 Redis]
D[请求到达] --> E[查询 Redis 配额]
E --> F{配额充足?}
F -->|是| G[放行 + Redis 扣减]
F -->|否| H[拒绝]
I[定时任务] --> J[Redis 用量同步到 MySQL]
style B fill:#42a5f5,color:#fff
style E fill:#66bb6a,color:#fff
style G fill:#66bb6a,color:#fff
style J fill:#ffa726
实现细节:
1 | // 1. 购买配额时:双写 MySQL + Redis |
优势:
- ✅ 高性能:限流检查只读 Redis(亚毫秒级)
- ✅ 高可用:Redis 故障时可从 MySQL 恢复
- ✅ 一致性:购买时双写,后续以 Redis 为准
劣势:
- ⚠️ Redis 数据丢失风险(需配置持久化)
- ⚠️ 需要定时同步任务
Redis 持久化配置:
1 | # redis.conf |
方案二:MySQL 主存储 + Redis 懒加载
核心思路:MySQL 是主存储,Redis 仅作为缓存,缓存未命中时从 MySQL 加载。
flowchart TD
A[请求到达] --> B{Redis 有缓存?}
B -->|是| C[使用 Redis 配额]
B -->|否| D[查询 MySQL]
D --> E[加载配额到 Redis]
E --> C
C --> F{配额充足?}
F -->|是| G[放行 + Redis 扣减]
F -->|否| H[拒绝]
G --> I[异步批量同步到 MySQL]
style B fill:#ffe0b2
style D fill:#42a5f5,color:#fff
style G fill:#66bb6a,color:#fff
style I fill:#ffa726
实现细节:
1 | func CheckQuotaLazyLoad(userID, period string, cost int) bool { |
优势:
- ✅ MySQL 是单一数据源,数据最可靠
- ✅ 懒加载减少不必要的 Redis 写入
- ✅ 异步批量同步,降低数据库压力
劣势:
- ⚠️ 首次请求延迟较高(需要查询 MySQL)
- ⚠️ Redis 和 MySQL 可能有短暂不一致
方案三:双写 + 定期校对(最强一致性)
核心思路:同时写入 Redis 和 MySQL,定期校对差异并修复。
flowchart TD
A[购买配额] --> B[双写 MySQL + Redis]
C[请求扣减] --> D[更新 Redis]
D --> E[异步写入 MySQL]
F[定时校对任务] --> G[对比 Redis vs MySQL]
G --> H{数据一致?}
H -->|是| I[记录日志]
H -->|否| J[以 Redis 为准修复 MySQL]
style B fill:#66bb6a,color:#fff
style D fill:#42a5f5,color:#fff
style J fill:#ef5350,color:#fff
校对任务实现:
1 | func ReconcileQuotas() { |
方案对比与选型建议
| 维度 | 方案一:Redis 主存储 | 方案二:懒加载 | 方案三:双写+校对 |
|---|---|---|---|
| 性能 | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐ | ⭐⭐⭐⭐ |
| 一致性 | ⭐⭐⭐ | ⭐⭐ | ⭐⭐⭐⭐⭐ |
| 复杂度 | ⭐⭐ | ⭐⭐⭐ | ⭐⭐⭐⭐ |
| 数据安全性 | ⭐⭐⭐ | ⭐⭐⭐⭐⭐ | ⭐⭐⭐⭐⭐ |
| 适用场景 | 高并发、可容忍少量丢失 | 中等并发、强一致性要求 | 金融级、不能出错 |
推荐方案:
对于大模型配额限流系统,推荐 方案一(Redis 主存储)+ 持久化配置:
1 | // Cron 定时任务配置 |
这样既能保证高性能,又能确保数据不丢失!
⚠️ 生产环境注意事项:避免 KEYS 命令陷阱
在定时同步任务中,绝对不能使用 KEYS 命令!
1 | // ❌ 危险操作!生产环境禁止使用 |
为什么 KEYS 命令很危险?
| 问题 | 影响 | 严重程度 |
|---|---|---|
| 阻塞 Redis | KEYS 是 O(N) 复杂度,扫描期间阻塞所有请求 | 🔴 严重 |
| 带宽打满 | 返回大量 Key 占用网络带宽 | 🔴 严重 |
| 内存峰值 | 一次性加载所有 Key 到内存 | 🟡 中等 |
| 超时崩溃 | 大数据量时可能超时,导致同步失败 | 🔴 严重 |
真实案例:
1 | 某公司 10 万用户,每个用户 3 个窗口 = 30 万 Key |
✅ 正确方案 1:使用 SCAN 迭代器
1 | func SyncQuotaToMySQL() { |
SCAN vs KEYS 对比:
| 维度 | KEYS | SCAN |
|---|---|---|
| 阻塞 | 是(阻塞所有请求) | 否(迭代器模式) |
| 复杂度 | O(N) | O(1) 每次调用 |
| 带宽 | 一次性返回所有 | 分批返回 |
| 生产可用 | ❌ 禁止使用 | ✅ 推荐使用 |
✅ 正确方案 2:维护 Key 索引表(最佳实践)
在 MySQL 中维护配额索引,避免扫描 Redis:
1 | CREATE TABLE quota_index ( |
1 | func SyncQuotaToMySQL() { |
✅ 正确方案 3:事件驱动架构(大规模推荐)
每次配额变化时发送事件,异步同步到 MySQL:
1 | // 请求扣减时 |
生产环境建议:
| 用户规模 | 推荐方案 | 理由 |
|---|---|---|
| < 1 万 | SCAN 迭代器 | 实现简单,性能可接受 |
| 1 万 - 10 万 | Key 索引表 | 精确控制,避免扫描 |
| > 10 万 | 事件驱动 | 实时同步,完全解耦 |
网关层技术选型与集成
API 网关选型
flowchart TD
A[API 网关选型] --> B1["Kong"]
A --> B2["Nginx + Lua"]
A --> B3["Envoy"]
A --> B4["自研网关"]
B1 --> C1["✅ 插件生态丰富\n✅ 内置限流插件\n❌ 性能中等"]
B2 --> C2["✅ 性能极高\n✅ 灵活定制\n❌ 开发成本高"]
B3 --> C3["✅ 云原生支持\n✅ 可观测性强\n❌ 配置复杂"]
B4 --> C4["✅ 完全定制\n❌ 维护成本高"]
style A fill:#ff7043,color:#fff
style C1 fill:#e8f5e9
style C2 fill:#e3f2fd
style C3 fill:#fff3e0
style C4 fill:#fce4ec
推荐架构:Kong + Redis + Lua
flowchart TD
A[客户端请求] --> B[Kong API Gateway]
B --> C["Lua 限流插件"]
C --> D{限流检查}
D -->|通过| E[转发到后端服务]
D -->|拒绝| F["返回 429 Too Many Requests"]
C --> G[Redis 集群]
G --> H["存储用量计数"]
E --> I[大模型推理服务]
I --> J[返回响应]
J --> B
B --> K[更新 Redis 计数]
style A fill:#bbdefb
style B fill:#42a5f5,color:#fff
style G fill:#66bb6a,color:#fff
style I fill:#ffa726
style F fill:#ef5350,color:#fff
请求处理流程
flowchart TD
A[HTTP 请求到达] --> B["解析 Header\n提取 UserID, AppID"]
B --> C["计算本次 Token 消耗\n(预估或历史均值)"]
C --> D["查询 Redis 获取当前用量"]
D --> E{多窗口检查}
E -->|通过| F["放行请求"]
E -->|拒绝| G["返回 429 + Retry-After"]
F --> H["异步更新 Redis"]
H --> I["推送监控指标"]
G --> J["记录限流日志"]
style A fill:#e3f2fd
style E fill:#ffe0b2
style F fill:#66bb6a,color:#fff
style G fill:#ef5350,color:#fff
style H fill:#ab47bc,color:#fff
Redis Lua 脚本(原子性保证)
使用 Lua 脚本保证”检查 + 更新”的原子性:
1 | -- 多窗口限流 Lua 脚本 |
Go 调用示例:
1 | var rateLimitScript = redis.NewScript(` |
高并发场景优化方案
Redis 性能瓶颈
单节点 Redis 的 QPS 限制约为 10 万,高并发场景需要优化:
flowchart TD
A[高并发挑战] --> B1["Redis 单点瓶颈"]
A --> B2["网络延迟累积"]
A --> B3["热 Key 问题"]
B1 --> C["优化方案"]
B2 --> C
B3 --> C
C --> D1["Pipeline 批量操作"]
C --> D2["本地缓存 + 异步同步"]
C --> D3["Redis 集群分片"]
style A fill:#ff7043,color:#fff
style C fill:#42a5f5,color:#fff
style D1 fill:#66bb6a,color:#fff
style D2 fill:#ffa726
style D3 fill:#ab47bc,color:#fff
优化方案 1:Pipeline 批量操作
将多次 Redis 调用合并为一次网络往返:
1 | func (l *RateLimiter) CheckBatch(ctx context.Context, userIDs []string) map[string]bool { |
优化方案 2:本地缓存 + Redis 二级架构
flowchart TD
A[请求到达] --> B{本地缓存检查}
B -->|命中| C["本地计数器判断"]
B -->|未命中| D["查询 Redis"]
C --> E{本地是否超限?}
E -->|是| F["❌ 拒绝"]
E -->|否| G["✅ 放行"]
D --> H["写入本地缓存"]
H --> G
G --> I["本地计数器 +1"]
I --> J["异步批量同步到 Redis"]
style A fill:#e3f2fd
style B fill:#ffe0b2
style F fill:#ef5350,color:#fff
style G fill:#66bb6a,color:#fff
style J fill:#ab47bc,color:#fff
Go 实现:
1 | type LocalCacheLimiter struct { |
优化方案 3:Redis 集群分片
flowchart LR
A[请求] --> B[网关层]
B --> C1["Redis Shard 1\n用户 A, D, G"]
B --> C2["Redis Shard 2\n用户 B, E, H"]
B --> C3["Redis Shard 3\n用户 C, F, I"]
style A fill:#e3f2fd
style B fill:#42a5f5,color:#fff
style C1 fill:#66bb6a,color:#fff
style C2 fill:#66bb6a,color:#fff
style C3 fill:#66bb6a,color:#fff
通过用户 ID Hash 分片,分散单点压力。
完整代码实现(Go)
基于 Redis + Go 的多窗口限流器
1 | package ratelimit |
使用示例
1 | func main() { |
网关中间件集成
1 | package middleware |
实际应用场景与配置
1. SaaS 平台的 Tier 定价
flowchart TD
A[套餐类型] --> B1["免费版"]
A --> B2["专业版"]
A --> B3["企业版"]
B1 --> C1["5h: 1000 tokens\n7d: 5000 tokens\n30d: 10000 tokens"]
B2 --> C2["5h: 10000 tokens\n7d: 50000 tokens\n30d: 200000 tokens"]
B3 --> C3["5h: 100000 tokens\n7d: 500000 tokens\n30d: 2000000 tokens"]
style A fill:#ff7043,color:#fff
style C1 fill:#ef5350,color:#fff
style C2 fill:#ffa726
style C3 fill:#66bb6a,color:#fff
配置示例:
1 | type TierConfig struct { |
2. API 市场的用量包管理
flowchart LR
A[用户购买用量包] --> B["充值 100K tokens"]
B --> C["余额: 100000"]
C --> D["每次请求扣除"]
D --> E{余额不足?}
E -->|是| F["拒绝 + 提示充值"]
E -->|否| G["放行 + 扣减余额"]
style A fill:#e3f2fd
style C fill:#66bb6a,color:#fff
style F fill:#ef5350,color:#fff
style G fill:#42a5f5,color:#fff
3. 企业内部的部门配额分配
1 | 企业总配额: 1000K tokens/月 |
4. 突发流量削峰
flowchart TD
A[正常流量] --> B["1000 Tokens/min"]
A --> C[突发流量]
C --> D["10000 Tokens/min"]
D --> E{配额检查}
E -->|配额充足| F["2000 Tokens/min\n允许 2 倍突发"]
E -->|配额不足| G["8000 Tokens/min\n返回 429"]
style A fill:#e8f5e9
style D fill:#ffebee
style F fill:#66bb6a,color:#fff
style G fill:#ef5350,color:#fff
通过令牌桶算法允许短期突发,同时保护长期配额。
监控告警体系设计
用量可视化
flowchart TD
A[限流系统] --> B[数据采集]
B --> C[时序数据库]
C --> D[Dashboard]
D --> E1["实时用量曲线"]
D --> E2["各窗口剩余量"]
D --> E3["限流触发次数"]
D --> E4["用户 Top 排名"]
style A fill:#42a5f5,color:#fff
style C fill:#66bb6a,color:#fff
style D fill:#ffa726
告警策略
| 告警级别 | 触发条件 | 动作 |
|---|---|---|
| 预警 | 用量达到 80% | 发送邮件/消息通知 |
| 警告 | 用量达到 90% | 发送短信 + 限流预警 |
| 严重 | 用量达到 100% | 触发限流 + 通知运营 |
| 异常 | 短时间内大量限流 | 触发安全告警(可能是攻击) |
Go 实现告警检查:
1 | func (m *Monitor) CheckAndAlert(userID string, usage int, limit int) { |
异常检测
flowchart TD
A[限流日志] --> B[异常检测引擎]
B --> C1["频次异常\n短时间大量请求"]
B --> C2["模式异常\n非正常调用模式"]
B --> C3["地域异常\n异常 IP 来源"]
C1 --> D[触发安全策略]
C2 --> D
C3 --> D
D --> E1["临时封禁 IP"]
D --> E2["验证码验证"]
D --> E3["人工审核"]
style A fill:#e3f2fd
style B fill:#ab47bc,color:#fff
style D fill:#ef5350,color:#fff
系统局限性与改进方向
1. 配额精度的权衡
flowchart TD
A[配额精度] --> B1["高精度\n按请求记录"]
A --> B2["中精度\n配额计数器"]
A --> B3["低精度\n固定周期重置"]
B1 --> C1["✅ 可追溯\n❌ 存储开销大"]
B2 --> C2["✅ 平衡\n✅ 性能高"]
B3 --> C3["✅ 简单\n❌ 边界突变"]
style A fill:#ff7043,color:#fff
style C1 fill:#e3f2fd
style C2 fill:#c8e6c9
style C3 fill:#fff3e0
2. 跨地域分布式限流的挑战
- 数据同步延迟:多地域 Redis 同步导致计数不一致
- 网络分区:部分地域无法访问中心 Redis
- 解决方案:
- 本地限流 + 全局聚合
- CRDT(无冲突复制数据类型)
- 异步最终一致性
3. 预估 Token 消耗的误差
1 | 问题: 请求时无法精确知道实际 Token 消耗 |
4. 与计费系统的深度集成
限流系统需要与计费系统协同:
flowchart TD
A[请求到达] --> B[限流系统]
B --> C[计费系统]
C --> D[实际 Token 消耗]
D --> E[限流系统回写]
E --> F[修正计数器]
style A fill:#e3f2fd
style B fill:#66bb6a,color:#fff
style C fill:#42a5f5,color:#fff
style E fill:#ab47bc,color:#fff
总结与最佳实践
大模型限流系统是 API 网关的核心基础设施,通过多维度、多时间窗口的组合策略,实现资源保护、成本控制和公平分配。
flowchart TD
A[限流系统核心价值] --> B1["资源保护\n防止过载"]
A --> B2["成本控制\n避免滥用"]
A --> B3["公平分配\n多租户管理"]
A --> B4["计费基础\n支撑商业化"]
B1 --> C[高可用大模型服务]
B2 --> C
B3 --> C
B4 --> C
C --> D1["✅ 多窗口限流"]
C --> D2["✅ Redis + Lua 原子操作"]
C --> D3["✅ 高并发优化"]
C --> D4["✅ 监控告警"]
style A fill:#ff7043,color:#fff
style C fill:#42a5f5,color:#fff
style D1 fill:#66bb6a
style D2 fill:#ab47bc
style D3 fill:#26c6da
style D4 fill:#ffa726
技术选型建议:
| 场景 | 推荐方案 | 原因 |
|---|---|---|
| 初创项目 | Kong + Redis 插件 | 快速上线,生态成熟 |
| 高并发场景 | 自研网关 + Redis 集群 | 性能优先,灵活定制 |
| 云原生架构 | Envoy + Redis | 微服务友好,可观测性强 |
| 混合云 | 本地限流 + 全局聚合 | 降低跨地域延迟 |
架构要点回顾:
- ✅ 多窗口组合:5h(分钟级)+ 7d(小时级)+ 30d(天级)
- ✅ 原子操作:Redis Lua 脚本保证一致性
- ✅ 高并发优化:Pipeline + 本地缓存 + 集群分片
- ✅ 监控告警:实时可视化 + 多级预警
限流系统不仅是技术组件,更是业务策略的体现。合理设计限流规则,可以在保护资源的同时,为用户提供最佳服务体验。
参考资料
- Kong Documentation: Rate Limiting Plugin
- Redis Documentation: Rate Limiting Patterns
- Google SRE Book: Chapter 15 - Traffic Management
- Go-Redis Documentation: Pipeline and Lua Script
- Envoy Documentation: Local Rate Limit Filter
- Nginx Documentation: Limiting Request Rate