Higress ai-quota 插件实战:基于 Redis 的按 Consumer AI Token 配额管理与管控接口
2026/9/16 19:58:58 网站建设 项目流程

Higress ai-quota 插件实战:基于 Redis 的按 Consumer AI Token 配额管理与管控接口

【免费下载链接】higress🤖 AI Gateway | AI Native API Gateway项目地址: https://gitcode.com/GitHub_Trending/hi/higress

本文围绕 Higress 官方 WASM 插件ai-quota展开,完整讲解其配置参数、部署示例,以及"配额校验—Token 扣减—查询/刷新/增减配额"的完整工作流。读完本文,你将掌握如何为每个 AI 调用方(consumer)分配固定 Token 配额、在流式响应结束时自动扣减配额,并内置一套基于管理 consumer 身份的配额管控 HTTP 接口,同时了解插件在 proxy-wasm 各阶段的底层实现逻辑。

功能定位与运行属性

ai-quota插件为特定 consumer 分配固定的 quota(配额),按配额策略对 AI 请求进行限流,同时提供配额管理能力,包括查询 quota、刷新 quota、增减 quota。其典型使用模式是:

  • 在网关路由上为不同调用方(如不同租户、不同 API Key 持有者)预先在 Redis 中写入一个 Token 数值;
  • 每次调用大模型完成接口时,插件校验该 consumer 是否还有剩余配额,无配额则直接拒绝;
  • 大模型响应结束(流式或非流式)后,插件从响应中解析本次请求消耗的 input/output Token,并从 Redis 配额中扣减;
  • 管理员可通过管理接口在线查询、刷新、增减某个 consumer 的配额,无需重启网关或手工操作 Redis。

根据文档说明,ai-quota需要配合认证插件(如key-authjwt-auth)获取认证身份的 consumer 名称,配合ai-statistics插件获取 AI Token 统计信息。

插件运行属性:

属性
插件执行阶段默认阶段(UNSPECIFIED_PHASE
插件执行优先级750(文档标称默认优先级)

在 plugin.yaml 的部署示例中,ai-quota 的priority设置为280,介于 key-auth(300)与 ai-statistics(250)之间。从源码结构看,这一顺序保证了认证插件先于配额插件执行、先写入 consumer 标识,配额插件再读取该标识完成校验。

配置参数详解

插件顶层配置项如下(与 README.md 一致,默认值已结合 main.go 中parseConfig的实际解析逻辑核对):

名称数据类型填写要求默认值描述
redis_key_prefixstring选填chat_quota:quota Redis key 前缀
admin_consumerstring必填-管理 quota 管理身份的 consumer 名称
admin_pathstring选填/quota管理 quota 请求 path 前缀
enable_path_suffixes[]string选填["/v1/chat/completions", "/v1/messages"]启用配额校验的请求路径后缀(仅用于 completion 请求,不影响管理接口路径)
redisobject必填-Redis 相关配置

parseConfig中的关键校验逻辑(见 main.go#L80-L151):

  • 未配置admin_consumer时直接返回错误missing admin_consumer in config,插件启动失败;
  • enable_path_suffixes必须是数组且不能为空,元素中的空白串会被过滤,空数组会触发enable_path_suffixes must not be empty
  • admin_path缺省回退为/quotaredis_key_prefix缺省回退为chat_quota:
  • redis.service_name不能为空,且会基于它创建FQDNCluster类型的 Redis 客户端。

redis子对象各字段:

配置项类型必填默认值说明
service_namestring必填-Redis 服务名称,带服务类型的完整 FQDN 名称,例如my-redis.dnsredis.my-ns.svc.cluster.local
service_portint服务类型为固定地址(static service,即名称以.static结尾)时默认 80,其他为 6379Redis 服务端口
usernamestring-Redis 用户名
passwordstring-Redis 密码
timeoutint1000Redis 连接超时时间,单位毫秒
databaseint0使用的数据库 id,例如配置为 1,对应SELECT 1

源码中对service_port的默认值处理见 main.go#L123-L131:仅当服务名以.static结尾时才取 80,这与文档描述一致。

完整配置示例

以下示例来自文档,表示按请求头中的 API Key 识别不同 consumer 并进行区别限流:

redis_key_prefix: "chat_quota:" admin_consumer: consumer3 admin_path: /quota redis: service_name: redis-service.default.svc.cluster.local service_port: 6379 timeout: 2000

结合仓库中 plugin.yaml 的完整部署形态,该插件通常与key-authai-statistics一起以WasmPluginCR 声明式下发。key-auth 侧声明了三个 consumer:

# 摘自 plugins/wasm-go/extensions/ai-quota/plugin.yaml defaultConfig: consumers: - credential: "Bearer credential1" name: consumer1 - credential: "Bearer credential2" name: consumer2 - credential: "Bearer credential3" name: consumer3 global_auth: true keys: - authorization in_header: true priority: 300

ai-quota 侧则配置admin_consumer: consumer3,即持有credential3的请求被视为配额管理员。matchRules 通过ingress: [qwen]将配额策略绑定到具体的 Ingress 路由上。

请求路径的模式识别:completion / admin / none

插件在请求头阶段会调用getOperationMode判定当前请求属于哪种模式(见 main.go#L289-L306):

模式判定条件行为
admin/refresh请求路径以/v1/chat/completions+admin_path+/refresh结尾刷新配额
admin/delta请求路径以/v1/chat/completions+admin_path+/delta结尾增减配额
admin/query请求路径以/v1/chat/completions+admin_path结尾查询配额
completion请求路径以enable_path_suffixes中任一后缀结尾执行配额校验与扣减
none以上均不匹配放行,不做配额处理

需要注意两个源码级细节:

  1. 管理接口路径是固定拼接的fullAdminPath := "/v1/chat/completions" + adminPath,也就是说无论路由前缀是什么,管理接口始终挂在/v1/chat/completions之后(默认即/v1/chat/completions/quota系列路径)。单元测试TestGetOperationMode中专门验证了/v1/messages/quota不会被识别为管理接口(messages admin path not supported用例),而自定义后缀(如/llm/invoke)可以被识别为 completion 路径;
  2. completion 判定与 admin 判定互不影响enable_path_suffixes只约束配额校验的完成接口路径,不改变管理接口的匹配逻辑。

该表驱动测试完整位于 main_test.go#L304-L394。

配额校验与 Token 扣减流程

请求头阶段:读取身份并校验配额

onHttpRequestHeaders(见 main.go#L153-L209)的处理链路:

  1. 读取请求头x-mse-consumer(由 key-auth 等认证插件在认证成功后写入):
    • 头不存在:返回401,状态详情ai-quota.no_key,正文 "Request denied by ai quota check. No Key Authentication information found.";
    • 头存在但为空:返回403,状态详情ai-quota.unauthorized
  2. 判定模式后:
    • none模式直接ActionContinue放行;
    • admin模式交由后续 body 阶段处理(refresh/delta 会先缓冲请求体);
    • completion模式跳过请求体读取,发起 RedisGET {redis_key_prefix}{consumer},以下任一情况判定为拒绝:Redis 调用出错、key 不存在(null)、配额值小于等于 0;拒绝时返回403,状态详情ai-quota.noquota,正文 "Request denied by ai quota check, No quota left"。

配额检查期间返回HeaderStopAllIterationAndWatermark,即挂起请求流水线直到 Redis 回调返回结果,这是 proxy-wasm 中典型的异步外部调用暂停/恢复模式。单元测试TestOnHttpRequestHeaders的 "chat completion mode" 用例验证了该行为:模拟 Redis 返回配额 1000 后,流恢复为ActionContinue(见 main_test.go#L111-L133)。

响应体阶段:解析 Token 并扣减配额

在流式响应体处理函数onHttpStreamingResponseBody(见 main.go#L239-L277)中:

  • 插件逐段解析 SSE 流,通过 wasm-go 通用包tokenusage.GetTokenUsage提取usage字段中的 input/output token 数;
  • 仅当endOfStream为 true 且 input/output token 均已解析成功时,计算totalToken = inputToken + outputToken,并执行 RedisDECRBY {redis_key_prefix}{consumer} totalToken
  • 中间分片数据原样透传,不改动响应内容。

TestOnHttpStreamingResponseBody用例模拟了流结束后的扣减调用,验证了 Redis 扣减回调被正确触发(见 main_test.go#L238-L302)。

配额管理接口实战

管理接口同样要求请求先通过认证并携带x-mse-consumer,且该 consumer 必须等于admin_consumer,否则返回403ai-quota.unauthorized,"Unauthorized admin consumer.")。以下命令以插件生效于example.com/v1/chat/completions路由、admin_consumerconsumer3(对应Bearer credential3)为前提。

刷新 quota

将指定 consumer 的配额直接重置为新值:

curl https://example.com/v1/chat/completions/quota/refresh \ -H "Authorization: Bearer credential3" \ -d "consumer=consumer1&quota=10000"

执行后 Redis 中 keychat_quota:consumer1的值被刷新为 10000,成功时插件返回refresh quota successfulrefreshQuota实现位于 main.go#L308-L341,请求体按application/x-www-form-urlencoded解析,consumer不能为空、quota必须是整数,否则返回 403。

查询 quota

查询指定 consumer 的剩余配额:

curl "https://example.com/v1/chat/completions/quota?consumer=consumer1" \ -H "Authorization: Bearer credential3"

返回 JSON:

{"quota": 10000, "consumer": "consumer1"}

若 key 不存在则返回quota: 0queryQuota在请求头阶段即可处理(无需请求体),Redis 出错时返回503ai-quota.error)。对应实现见 main.go#L343-L385,测试用例TestOnHttpRequestHeaders的 "admin query mode" 断言了返回值{"consumer":"consumer1","quota":500}

增减 quota

对指定 consumer 的配额做增量调整:

curl https://example.com/v1/chat/completions/quota/delta \ -H "Authorization: Bearer credential3" \ -d "consumer=consumer1&value=100"

Redis 中 keychat_quota:consumer1的值增加 100;value支持负数,传负值则减去对应值。deltaQuota(见 main.go#L387-L435)根据正负号分别走 RedisINCRBY/DECRBY,成功时返回delta quota successful

错误响应一览

结合 util/http.go 的SendResponse与各处理函数,插件可能返回的响应如下:

状态码状态详情(status detail)触发场景
401ai-quota.no_key请求头缺少x-mse-consumer(未通过认证插件)
403ai-quota.unauthorizedconsumer 为空;管理接口调用者不是admin_consumer;参数校验失败(consumer 为空、quota/value 非整数)
403ai-quota.noquotacompletion 请求无剩余配额(key 不存在、出错或值 ≤ 0)
503ai-quota.errorRedis 调用失败
200ai-quota.refreshquota/ai-quota.queryquota/ai-quota.deltaquota管理操作成功

这些带语义的 status detail 便于在网关访问日志中直接定位限流/管理请求的处理结果。

配置解析的测试覆盖

插件配置解析由TestParseConfig覆盖(见 main_test.go#L69-L106):

  • 基础配置解析后各字段符合预期(AdminConsumerRedisKeyPrefixAdminPathEnablePathSuffixes);
  • 缺少admin_consumer时插件启动状态为OnPluginStartStatusFailed,即网关侧会拒绝加载该配置,属于启动期强校验;
  • 未配置enable_path_suffixes时回退到默认值["/v1/chat/completions", "/v1/messages"],兼容 OpenAI 与 Anthropic 两种主流的 completion 路径。

落地建议小结

  • 先认证后限流:务必确保 key-auth / jwt-auth 等认证插件在 ai-quota 之前执行(部署示例中通过 priority 300 > 280 体现),否则所有请求都会以 401ai-quota.no_key被拒;
  • 预置配额:consumer 的配额 key(如chat_quota:consumer1)需提前写入 Redis,未写入的 consumer 会被判定为无配额而拒绝,这一点在源码中由IsNull()判断直接体现;
  • 管理面收敛admin_consumer的凭据只分发给运营/管理侧账号,管理接口与普通 completion 接口共用同一路由,路径前缀区分(/quota/quota/refresh/quota/delta),无需额外暴露管理端口;
  • 注意管理路径的固定形态:管理接口始终基于/v1/chat/completions拼接,若网关实际路由前缀不同,需要确保客户端 URL 与该形态一致,否则请求会落入none模式直接放行而不做配额处理。

核心源码均位于 plugins/wasm-go/extensions/ai-quota 目录:入口与全部业务逻辑在 main.go,本地响应构造工具在 util/http.go,行为验证在 main_test.go,可直接作为二次开发与排障的参考起点。

【免费下载链接】higress🤖 AI Gateway | AI Native API Gateway项目地址: https://gitcode.com/GitHub_Trending/hi/higress

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询