跳转到内容

多租户配置

多租户支持使单个网关实例能够为多个组织、团队或项目提供服务,并提供隔离的资源限制和访问控制。每个租户通过其 API 密钥进行标识,每次请求都携带租户上下文,使得配额、模型访问和成本追踪按租户划分。

每次传入的 AI 请求都携带一个 API 密钥。网关将密钥解析为租户身份,然后强制执行该租户的策略,再将请求转发到模型提供商。解析基于预构建的索引进行常数时间查找,因此租户检查增加的延迟可以忽略不计。

网关维护三类按租户的约束:

  1. 配额限制:令牌和请求的速率限制,按滑动的分钟和天窗口强制执行。
  2. 模型白名单:允许该租户使用的模型。
  3. 成本限制:租户累计支出的月度美元上限。

通过全部三项检查的请求正常继续。未通过任何一项检查的请求在到达模型提供商之前被拒绝并返回相应错误。

当使用租户列表创建 TenantManager 时,它在构造函数中构建两个数据结构:

  • 一个以 tenant_id 为键的 tenants 哈希表,用于租户定义查找。
  • 一个 api_key_index 哈希表,将每个租户的每个 API 密钥映射到其所属的 tenant_id,实现 O(1) 解析。

索引在启动时主动构建。如果在运行时添加或删除租户,必须使用完整的更新后租户列表重建 TenantManager。没有增量添加或删除的 API。

每个租户定义包含身份标识、一组 API 密钥、配额参数、模型访问规则以及可选的成本上限:

字段类型描述
tenant_idstring租户的唯一标识符(例如 acme-corp)。
api_keyslist of string映射到此租户的一个或多个 API 密钥。
quota.tokens_per_minuteu64每分钟滑动窗口允许的最大令牌数。0 表示无限制。
quota.tokens_per_dayu64每 24 小时滑动窗口允许的最大令牌数。0 表示无限制。
quota.requests_per_minuteu64每分钟滑动窗口允许的最大请求数。0 表示无限制。
allowed_modelslist of string此租户可以访问的模型。空列表表示允许所有模型。
cost_limitfloat or null月度美元成本上限。null 表示无限制。

API 密钥是传入请求与其租户之间的绑定。当网关收到请求时,它从 Authorization 头中提取密钥,并在 api_key_index 哈希表中查找。如果找不到该密钥,resolve 返回 None,请求被拒绝为未经授权。

多个密钥可以映射到同一租户。当同一组织内的不同团队或服务使用不同密钥但共享同一配额池和模型访问权限时,这很有用。在初始化期间,每个租户的每个密钥都被摊平到索引中,因此无论一个租户有多少密钥,查找成本始终为 O(1)。

每个租户声明一个 allowed_models 列表。调用 check_model_access 时:

  • 如果找不到租户,拒绝访问(false)。
  • 如果白名单为空,允许任何模型(true)。
  • 如果白名单非空,请求的模型必须匹配列表中的一项。

这提供了三级访问控制:无限制(空列表)、限制为特定模型以及完全拒绝(租户未注册)。检查是对白名单的线性扫描。对于小型白名单这是很快的;对于有大量允许模型的租户,需考虑性能权衡。

配额按租户追踪,使用 DashMap 中的滑动窗口以实现并发访问。每个租户获得一个 TenantQuotaState,追踪以下内容:

计数器范围重置触发条件
minute_tokens每分钟滑动窗口Instant::now() 超过 minute_reset
day_tokens每 24 小时滑动窗口Instant::now() 超过 day_reset
minute_requests每分钟滑动窗口Instant::now() 超过 minute_reset

调用 check_quota 时:

  1. 函数检查分钟或天窗口是否已过期。如果是,则重置相关计数器并将重置截止时间前移。
  2. 计算在加上请求的令牌数和一次请求后,三个计数器的候选值。
  3. 与租户的配额限制进行比较。任何配额字段值为 0 表示不强制执行该限制。
  4. 如果全部检查通过,更新计数器并返回 true。如果任何检查失败,返回 false 而不更新计数器。

配额检查在请求发送到模型提供商之前运行。传递给 check_quota 的令牌数是估计的提示令牌数,因为补全大小此时尚未可知。

当租户从注册表中移除时,它们在 DashMap 中的配额状态条目变得陈旧。为防止无限的内存增长,管理器执行机会性清理:每 1000 次 check_quota 调用触发一次 maybe_cleanup,删除 tenant_id 已不在注册表中的所有 QuotaState 条目。这使内存使用量与活跃租户数量成正比,而无需为每次请求增加清理开销。

每个租户可以有一个 cost_limit 美元上限。check_cost_limit 将租户的累计成本与这个上限进行比较。如果累计成本超出上限,检查返回 false,请求被阻止。成本累积由成本追踪模块处理,租户管理器在每次请求时查询它。

没有 cost_limit 的租户(配置中设为 null,内部表示为 Option::None)支出无上限。

此示例定义了一个租户 acme-corp,拥有两个 API 密钥,模型限制为 gpt-4oclaude-3-sonnet,配额限制为每分钟 100 万令牌、每天 5000 万令牌,月度成本上限为 $5000:

tenants:
- tenant_id: acme-corp
api_keys:
- sk-proj-abc123xyz
- sk-proj-def456uvw
allowed_models:
- gpt-4o
- claude-3-sonnet
quota:
tokens_per_minute: 1000000
tokens_per_day: 50000000
requests_per_minute: 500
cost_limit: 5000.00

携带 sk-proj-abc123xyzsk-proj-def456uvw 的请求解析为 acme-corp 租户。网关检查请求的模型是否为 gpt-4oclaude-3-sonnet,租户是否在其令牌和请求配额内,以及累计月度成本是否未超过 $5000。