Skip to content

feat(gateway): add configurable daily usage limits - #714

Open
YorhaL wants to merge 1 commit into
fawney19:mainfrom
YorhaL:feat/daily-usage-limits
Open

feat(gateway): add configurable daily usage limits#714
YorhaL wants to merge 1 commit into
fawney19:mainfrom
YorhaL:feat/daily-usage-limits

Conversation

@YorhaL

@YorhaL YorhaL commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

需求背景

在上游 Token 资源或整体服务容量有限的情况下,仅使用 RPM 无法限制单个用户在一天内消耗的资源总量。部分用户即使请求频率不高,也可能通过大上下文或高价格模型占用大量额度,从而影响其他用户的可用性。

因此需要提供一个按日累计的最高使用量限制,用于控制单个用户或 API Key 每天最多能够使用多少资源,避免少数用户持续占用过多 Token,并确保有限 Token 资源能够在用户之间得到相对公平的分配。

该功能关注的是网关层面的资源使用控制,与现有机制关注的维度不同:

  • RPM 限制单位时间内的请求频率。
  • 每日使用限额限制自然日内的累计资源使用量。
  • 钱包余额负责判断用户是否具备继续消费的资金。
  • 套餐额度负责套餐权益、费用抵扣和结算。

因此,即使用户钱包余额充足、余额无限或仍有套餐额度,管理员也可以单独通过每日使用限额限制其当天的最高使用量。该限制不会增加余额、抵扣费用或改变现有计费结果。

功能概述

本 PR 增加了以美元计价的每日使用限额,作为一项独立的网关限流策略。

该功能与钱包余额、套餐额度相互独立。当用户当天已完成请求的累计用量达到配置上限后,后续正成本或无法确定成本的请求将返回 HTTP 429;能够明确证明为零成本的请求仍然允许执行。

默认不开启限制,因此升级后不会改变现有用户行为。

限额策略

系统设置

  • 新增 daily_usage_limit_usd
  • 未配置或配置为 0 表示默认不限额。

用户组

  • 支持 inheritsystemcustom 三种模式。
  • 任一自定义用户组配置为 0 时,该用户不限额。
  • 存在多个正数自定义额度时,取其中最大值。
  • 没有自定义配置时,跟随系统默认额度。

普通 API Key

  • 用户额度聚合该用户的所有普通 Key,不包含独立 Key。
  • 正数 Key 额度可以进一步收窄用户额度。
  • Key 额度为 0null 时,不增加 Key 级限制。
  • 用户额度和 Key 额度任一达到上限,都会拒绝请求。

独立 API Key

  • null:跟随系统默认额度。
  • 0:明确不限额。
  • 正数:使用自定义额度。
  • 只检查独立 Key 自身的使用量。

绕过规则

  • 管理员普通 Key 和 IP 白名单沿用现有 RPM 绕过规则。
  • 独立 Key 不会因为所有者是管理员而绕过限制。

每日窗口按照 APP_TIMEZONE 的自然日计算,默认时区为 Asia/Shanghai

执行与计量

  • 使用已完成请求的 actual_total_cost_usd 累计当天用量。
  • 即使用户当前未启用限额,也会继续记录当天用量,因此当天中途启用限额时可以直接使用 Redis 中已经累计的用量。
  • 达到限额后:
    • 正成本请求拒绝。
    • 无法确定成本的请求拒绝。
    • 能够明确证明为零成本的候选继续放行。
  • 已在途请求不会被中断,因此高并发场景下允许出现少量超额。

达限响应包括:

  • HTTP 429。
  • 错误类型 daily_usage_limit_exceeded
  • 指向下一个本地午夜的 Retry-After
  • limit、used、remaining、scope 和 reset 等响应信息。

性能设计

请求热路径完全基于 Redis,不会查询数据库。

  • 未启用限额的请求不读取 Redis 用量。
  • 有限额的每个客户端请求最多读取一次 Redis 状态。
  • 候选、凭证、Provider 重试以及 fallback 路径会复用请求级缓存结果。
  • Redis 通过一次运行时状态读取同时获得所需作用域的数据。
  • 系统配置读取和时区解析均已缓存。
  • 请求完成后根据实际成本更新 Redis 计数。

Redis 状态恢复

项目默认不启用 Redis 持久化,因此增加了 Redis 状态丢失后的数据库懒恢复:

  • Redis 状态缺失时,当前请求 fail-open。
  • 通过分布式锁确保同一时间只执行一次恢复。
  • 在异步任务中聚合数据库内当天已完成请求的实际用量。
  • 恢复查询使用 background_data
  • 前台请求不会等待数据库查询或后台数据库连接池。
  • 不执行启动时全量重建。
  • 不会把数据库查询放入每个请求的热路径。
  • 恢复完成后,后续请求继续使用 Redis 校验。

管理接口与前端

本 PR 补充了以下管理能力:

  • 系统默认额度限制。
  • 用户组额度配置。
  • 普通 API Key 额度配置。
  • 独立 API Key 额度配置。
  • 用户最终生效策略及来源展示。
  • 用户管理和 API Key 管理页面展示。
  • 系统配置导入和导出。
  • 中英文界面文案。

/api/monitoring/rate-limit-status 保持原字段兼容,并为每个 Key 增加嵌套的 daily_usage 状态,包括:

  • 生效额度及作用域。
  • 用户和 Key 各自的 limit、used、remaining。
  • 当前时区和自然日窗口。
  • 重置时间。
  • 查询不可用状态。

数据库兼容性

包含以下数据库的迁移:

  • PostgreSQL
  • MySQL
  • SQLite

同时更新了:

  • 逻辑 Schema。
  • 生成的 Schema 基线。
  • 内存仓库。
  • 数据导入和导出模型。

新增字段均为可空或默认继承配置。升级后,现有用户和 API Key 默认不会受到限制。

验证情况

  • Gateway 编译通过。
  • 每日限额策略及恢复测试:10/10 通过。
  • 候选循环和请求级缓存测试:22/22 通过。
  • Runtime State 每日用量计数测试通过。
  • SQLite 每日用量聚合测试通过。
  • 前端测试和类型检查通过。
  • Schema 校验通过。
  • cargo fmt --all -- --check 通过。
  • git diff --check 通过。

@YorhaL
YorhaL force-pushed the feat/daily-usage-limits branch from 7ae5049 to 2ae9763 Compare August 6, 2026 11:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant