treg Idempotent-Key 多租户隔离原理:你的请求为何不会拿到别人的结果
【免费下载链接】tregOpenRouter for agent tools. Join community here: https://discord.gg/6mQYYfFMAn项目地址: https://gitcode.com/GitHub_Trending/treg/treg
treg 是一个面向 Agent 的"工具版 OpenRouter"——它把成百上千家第三方数据 API 统一成一个调用入口,按团队(租户)计费。Agent 天然爱重试,而 treg 的 Idempotency-Key 幂等键机制保证:重试免费、结果不变、且绝不会把 A 团队的数据回放给 B 团队。本文讲清楚这套多租户隔离是怎么做到的。
为什么 Agent 场景特别需要幂等键?
先说清楚问题:Agent 的重试远比人类频繁,而其中一种重试最"贵"——
- treg 调用上游供应商,供应商成功且已扣费;
- 响应在回传途中丢失(网络抖动、客户端超时);
- Agent 发起重试,treg 又调用了一次供应商,又扣了一次费。
从账本上看这是两笔各自成功的调用。更棘手的是:仅仅"记住这个 key 已扣费、跳过第二次收费"是不够的——第二次上游调用照样发生,钱照样付给供应商,只是把成本从用户转嫁到了平台。所以真正的幂等必须做到:第二次请求根本不出去,直接回放第一次的应答。
这套设计在 docs/IDEMPOTENT-CALLS-PLAN.md 中有完整推演。
只存"成功且已计费"的响应,窗口 24 小时
实现规则非常克制(源码见 idempotency.py):
- 只有带 key 才生效:不传
Idempotency-Key的请求行为与从前逐字节一致,服务端绝不"替你想 key"; - 只存已计费的成功响应:失败本来就是免费的,没必要保护,存储因此有界;
- 24 小时窗口:重试发生在秒级,一天足够宽裕,过期行直接消失。
核心隔离:key 按"调用者"作用域,而不是全局唯一
这是整篇文章的重点,也是"你的请求不会拿到别人的结果"的根本保证。
IdempotentCall表上的唯一约束是(membership_id, key)复合键,而不是 key 单独唯一(定义在 models.py):
uq_idem_caller_key → UNIQUE (membership_id, key)这里的membership是 treg 的统一"调用者身份":一个人类成员的 token、一个 Agent token、一个 OAuth 授权,最终都解析为同一个 Membership。于是只有一条规则覆盖所有情形:这个 key 归"发它的人"所有。
为什么这个设计如此重要?
- key 是客户端自己起的。两个团队迟早都会起出
retry-1这样的名字。如果只按 key 隔离,先到的那个团队的响应体就会被回放给后到的团队——这是该功能里唯一会造成数据泄露(而不只是多扣钱)的失败模式; - 为什么不是"按团队"隔离?因为同一个团队里两个写得懒的 Agent 都会抢着用
retry-1。按团队隔离会让第二个 Agent 收到莫名其妙的拒绝;按调用者隔离,它们压根碰不到彼此。作用域越小越安全,Stripe 也是按 API key 划分的,同理。
再进一步:转售场景的"标签分区"
还有一个更隐蔽的跨租户漏洞:一个转售型开发者会把所有用户都挂在自己的一个 token 下调用 treg。此时两个不同用户的请求都带着同一个调用者身份,如果都用retry-1作 key,第二个用户就会拿到第一个用户的缓存结果——跨租户泄露在"下一层"重新出现。
解法在 _scoped_idempotency_key:把调用者声明的主标签(primary tag)折叠进存储的 key 里,相当于给每个终端用户切出一个独立命名空间,且不需要任何数据库迁移;如果调用还带强制的 pin,pin 也会并入命名空间,改 pin 绝不可能暴露旧缓存。多租户边界细节可参见 docs/context/architecture/multi-tenancy.md。
三道防线:拿错结果会被"大声拒绝"
即便有了隔离,还有两种误用会被当场拦下,而不是静默返回错误数据:
| 情况 | 行为 | 说明 |
|---|---|---|
| 同一个 key 用于不同的请求 | 返回422 | 请求指纹(方法+路径+query+body 的哈希)不匹配时拒绝,避免把旧答案塞给新问题 |
| 同一个 key 的请求还在飞 | 返回409 | 第一次调用尚未完成,告知"稍后再试",阻止第二次重复扣费 |
| 并发重试同时到达 | 数据库裁决 | 先写入pending占位行,输者靠唯一约束插入失败而等待赢家,绝不发起第二次上游调用 |
占位行即锁:两个重试同时到达、同时查不到缓存时,谁先插入(membership_id, key)谁赢,另一个只能等——"读后写"的竞态窗口由数据库唯一约束关闭,这与账本ledger.reserve的条件更新是同一套思路。
回放响应如何被识别?
回放不是静默的。命中的重试会带上明确的响应头(详见 docs/context/interface/api.md):
X-Treg-Idempotent-Replay: true—— 告诉调用者"这是回放的";X-Treg-Cost-Micro: 0—— 回放不再产生新费用,客户端的本地计费合计保持真实;X-Treg-Original-Cost-Micro: <原费用>—— 原始调用花了多少,一并给出。
通过 MCP 协议调用时同样是可选的idempotency_key参数,回放结果带replayed: true。
小结
treg 的幂等机制可以浓缩为四句话:
- 只存付费成功的答案,24 小时过期——重试免费,但绝不变成通用缓存;
- 唯一约束落在 (调用者, key) 上——key 撞车是常态,跨租户回放才是事故;
- 转售场景再按标签分区一层——共享 token 下每个终端用户各有独立命名空间;
- 指纹不符报 422、在途冲突报 409、并发靠占位行+唯一约束——错误大声暴露,数据永不串户。
对普通用户来说这意味着:放心让 Agent 重试,账单不会重复,结果不会串门。
【免费下载链接】tregOpenRouter for agent tools. Join community here: https://discord.gg/6mQYYfFMAn项目地址: https://gitcode.com/GitHub_Trending/treg/treg
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考