上游限流
Aidy Gateway 支持上游限流能力,用于在请求转发到上游服务前进行流量控制。该功能通过 UpstreamRateLimitPluginConfig 配置,基于 token 数或字符数进行限速。
概述
上游限流插件运行在 ForwardRequest 阶段,在请求即将转发到上游服务时执行限速检查。与入口限速(基于租户 QPS)不同,上游限流支持更灵活的维度控制和基于用量的计费模式。
核心概念
- 限速桶 (Bucket): 确定限速的维度,决定「谁」被限速
- 限速条件 (Condition): 触发限速检查的前提条件
- 限速配额 (Quota): 限速的具体数值和时间窗口
限速规则
一条限速规则由「限速条件」和「限速桶」组成。在满足条件的前提下,查询对应的限速桶进行配额检查。
规则特性
- 一一对应: 每条规则的「限速条件」与「限速桶」一一对应,不支持多对多的复合逻辑
- 顺序执行: 多条规则自上而下依次执行
- 规则上限: 每个路由最多配置 10 条限速规则
- 事务补偿: 当同时命中多条规则时,若前面的规则通过(已扣除配额)而后面的规则未通过,会进行事务补偿回滚已扣除的配额
- 无兜底规则: 如果不满足任何条件,不做任何限速
隐含桶前缀
每条规则的限速桶会自动添加 route:{RouteID}:rule:{RuleIndex} 作为前缀,确保不同路由、不同规则之间的限速桶隔离。
配置结构
路由配置
在路由的 plugin_config.upstream_rate_limit 中配置限速规则:
plugin_config:
upstream_rate_limit:
rules:
- bucket:
header:
name: "X-Tenant-ID"
conditions:
- header:
name: "X-Model"
starts_with: "gpt-4"
quota:
limit: 100000
window: "3600s" # 1h
unit: RATE_LIMIT_UNIT_TOKENS
限速桶类型
| 类型 | 说明 | 示例 |
|---|---|---|
header | 基于请求 Header 的值 | header['X-Tenant-ID'] → 按租户限速 |
source_addr | 基于客户端 IP | 按来源 IP 限速 |
Header 桶
bucket:
header:
name: "X-Tenant-ID"
客户端 IP 桶
bucket:
source_addr: {}
限速条件
条件用于判断是否触发限速检查。当配置多个条件时,需要全部满足才会触发。条件为空时始终触发。
Header 条件
| 匹配方式 | 说明 | 示例 |
|---|---|---|
equals | 等于指定值 | header['X-Model'] = 'gpt-4' |
starts_with | 前缀匹配 | header['X-Model'] starts with 'gpt-4' |
contains | 包含指定值 | header['X-Model'] contains 'turbo' |
regex | 正则匹配 | header['X-Model'] match '^gpt-4.*' |
exists | Header 存在 | header['X-Priority'] exists |
conditions:
- header:
name: "X-Model"
starts_with: "gpt-4"
- header:
name: "X-Priority"
exists: true
客户端 IP 条件
| 匹配方式 | 说明 | 示例 |
|---|---|---|
equals | 等于指定 IP | source_addr = '192.168.1.100' |
starts_with | 前缀匹配 | source_addr starts with '192.168.1.' |
cidr | CIDR 网段匹配 | source_addr in '192.168.0.0/16' |
conditions:
- source_addr:
cidr: "10.0.0.0/8"
限速配额
| 字段 | 类型 | 说明 |
|---|---|---|
limit | int64 | 限速数量 |
window | Duration | 滑动窗口时间(仅支持秒为单位),如 1s、60s、3600s、86400s |
unit | RateLimitUnit | 计量单位 |
计量单位
| 值 | 说明 |
|---|---|
RATE_LIMIT_UNIT_TOKENS | 按 token 数计量 |
RATE_LIMIT_UNIT_CHARACTERS | 按字符数计量 |
quota:
limit: 1000000
window: "86400s" # 24h
unit: RATE_LIMIT_UNIT_TOKENS
配置示例
示例 1: 按 IP 每小时 token 限额
对每个客户端 IP 进行每小时 token 限速:
plugin_config:
upstream_rate_limit:
rules:
- bucket:
source_addr: {}
conditions:
- source_addr:
exists: true
quota:
limit: 100000
window: "3600s" # 1h
unit: RATE_LIMIT_UNIT_TOKENS
示例 2: 按自定义用户每小时 token 限速
使用 X-User-ID 请求头标识用户,对每个用户进行每小时 token 限速(仅当 X-User-ID 头存在时触发):
plugin_config:
upstream_rate_limit:
rules:
- bucket:
header:
name: "X-User-ID"
conditions:
- header:
name: "X-User-ID"
exists: true
quota:
limit: 50000
window: "3600s" # 1h
unit: RATE_LIMIT_UNIT_TOKENS
示例 3: 按 IP 限速(针对内网)
仅对来自内网(10.0.0.0/8 网段)的请求进行限速:
plugin_config:
upstream_rate_limit:
rules:
- bucket:
source_addr: {}
conditions:
- source_addr:
cidr: "10.0.0.0/8"
quota:
limit: 500000
window: "60s" # 1min
unit: RATE_LIMIT_UNIT_CHARACTERS
示例 4: 多条规则组合
同时应用多条限速规则,按用户等级实现差异化限速,并叠加整体 IP 限速:
- 规则 1: 对 VIP 用户(
X-User-Level: vip)每小时限 20 万 token - 规则 2: 对普通用户(
X-User-Level: normal)每小时限 1 万 token - 规则 3: 对所有请求按 IP 每分钟限 1 万 token(防止突发流量)
plugin_config:
upstream_rate_limit:
rules:
# 规则 1: VIP 用户每小时 20 万 token
- bucket:
header:
name: "X-User-ID"
conditions:
- header:
name: "X-User-Level"
equals: "vip"
quota:
limit: 200000
window: "3600s" # 1h
unit: RATE_LIMIT_UNIT_TOKENS
# 规则 2: 普通用户每小时 1 万 token
- bucket:
header:
name: "X-User-ID"
conditions:
- header:
name: "X-User-Level"
equals: "normal"
quota:
limit: 10000
window: "3600s" # 1h
unit: RATE_LIMIT_UNIT_TOKENS
# 规则 3: 所有请求按 IP 每分钟 1 万 token(防止突发流量)
- bucket:
source_addr: {}
conditions:
- source_addr:
exists: true
quota:
limit: 10000
window: "60s" # 1min
unit: RATE_LIMIT_UNIT_TOKENS
行为说明
限速触发
当请求触发限速时,插件会返回 429 Too Many Requests 响应,并在响应头中包含限速相关信息。
事务补偿机制
当一个请求匹配多条规则时:
- 按顺序检查每条规则的条件
- 条件满足时,扣除对应桶的配额
- 如果后续规则检查失败(配额不足),会回滚之前所有已扣除的配额
- 确保要么所有规则都通过,要么不扣除任何配额
算法:固定窗口
限速采用固定窗口算法,窗口从首次计数时开始计算。例如配置 window: "3600s" 且首次请求发生在 13:23,则窗口为 13:23 ~ 14:23。
目前窗口最低精度为 1s,存在小于 1s 精度的窗口会被向下取整;另外,如果本身窗口就小于 1s,则会被视为无效窗口,不进行限速(后续可能会增加对于这种的检测进行报错)。
预扣(请求阶段)与复扣(响应阶段)
- 请求进入时,会根据规则的
quota.unit进行“预扣”:- 若单位为
TOKENS,会估算请求 token 数(prompt 侧)。 - 若单位为
CHARACTERS,会计算请求字符数。
- 若单位为
- 下游完成后,会根据响应体内容(若能解析出
usage或估算响应)进行“复扣”:TOKENS单位:- 计算 completion 的实际用量:优先使用
usage.completion_tokens,缺失时回退为基于响应文本的估算; - 若响应中包含
usage.prompt_tokens,将与预扣时的请求估算做“差额调整”:delta = usage.prompt_tokens - 预估请求tokens,复扣时会额外加上该差额(差额可为负数,表示回退预扣阶段的多扣部分); - 最终复扣量 = completion 实际用量 + 差额(若可用)。
- 计算 completion 的实际用量:优先使用
CHARACTERS单位:按响应文本字符数计算扣除。
- 当前实现没有在下游失败/取消/超时的情况下“回滚”预扣,因此在错误的场景中,配额可能出现偏大计入。
- 估算 token 算法目前使用 OpenAI gpt-4o 及更新模型的同款算法(o200k_base),但未来可能会调整。
Redis 异常时的策略(Fail-Open)
当与 Redis 交互发生错误(例如网络异常、脚本执行失败等)时,网关当前策略为“放行”(Fail-Open):
- 会记录错误日志;
- 但不会拦截请求。
Redis 集群兼容性
当前版本暂不支持在 Redis Cluster(分片集群)环境下运行该限流功能。
原因:限流通过 Lua 脚本一次性操作多个 Key;在 Cluster 模式下,如这些 Key 不在同一哈希槽(slot),会触发 CROSSSLOT 错误,导致脚本执行失败。
- 建议:如需生产使用,请采用单实例/主从或哨兵模式的 Redis。
- 风险提示:若误用 Cluster,脚本可能失败;按当前策略网关将记录错误并 Fail-Open 放行(见上节)。