第 17 / 22 篇

第 16 章:从单进程实验推导生产边界

设想把教学网关部署成两个副本,再把预算设为每租户 1000 credits。每个副本各自允许预留 800,整体就承诺了 1600;其中一个进程重启后,又忘掉了正在执行和用量未知的请求。这个故障无需高并发,也不是换一个 HTTP 框架就能解决。生产化首先要替换状态与责任边界。

16.1 为每项保证找到实际证据

实验使用 Python 标准库,真实 HTTP 入口为 POST /v1/generate,身份来自 Bearer;请求携带 prompt、capabilities、max_output_units、request_id 和可选 allow_failover。SSE 按 token、usage、done 组织,显式取消使用 DELETE /v1/runs/<request_id>。测试能观察本地网络、并发、结算和取消行为。

它同时只有单进程内存、回环 HTTP 与本地替身,没有 TLS、持久账本、跨进程锁或真实模型 API 兼容保证。计量是 UTF-8 字节 credit,不能转换成任何供应商价格证明。下面讨论的是待实现设计;部署实验服务并不能自动获得这些能力。

16.2 把准入与终态写进持久账本

先用数据库或具备相应一致性保证的存储保存租户预算、预留和运行;在同一个事务中检查 (tenant, request_id) 唯一性、可用额度与并发资格,再建立运行。成功提交之后才联系上游。跨副本更新不能继续依赖进程内锁,状态转换还需条件更新,防止两个工作者重复结束同一运行。

状态可拆成“执行是否仍活跃”与“用量是否已确认”两个轴。工作进程崩溃时,租约过期可以触发接管和本地槽位核查,却不能证明远端从未执行。unknown 预留仍保留,等待供应商查询、账单或授权核对。跨节点并发租约还要用所有权版本阻止旧工作者继续提交,防止新旧工作者同时认为自己负责。

结算写入与对账事件发布可以采用事务内 outbox,再由消费者按结算标识去重。这能处理重复消息和发布失败,不能提供跨所有外部系统的“恰好一次”。接管者若缺少远端幂等合同,不应重新发送一个可能已经执行的 POST。

16.3 固定配置,也固定信任

每次请求保存路由策略、能力清单和适配器版本,才能重放决策过程。生产另需 TLS、密钥托管、租户权限、出口白名单和配置变更审核。紧急吊销应阻止尚未发起的调用,并记录影响;普通灰度则尽量保持请求内的配置视图一致。

不能把实验的 trust_not_started 布尔值当作真实供应商承诺。受控教学切换只在调用者显式允许、可信替身 503 明确未开始时成立;普通 503、首事件超时和流内断连不提供这个证据。真实适配器必须重新核验接受确认、幂等键与计费语义,遵守 S01 的重试边界。

16.4 数据面需要可计算的资源上限

连接数上限应由最慢请求占用时间和可用连接容量推导,再为租户设置并发与等待边界。队列同时约束事件数和字节数,限制单事件、总输入、总输出与写入期限。一次请求使用同一个总 deadline,各次尝试消耗剩余时间;具体代理计时器的范围需按所选版本重新核对 S04。

滚动发布时先停止新准入,给现有流一个明确排空期限。过期后取消本地运行并关闭连接,未取得完整用量的仍进入 unknown。取消接口返回“意图已接收”与“远端确认停止”是不同状态,也不能替代工具副作用补偿。

如果产品要求断线恢复,还需另建持久事件日志、游标、保留期限和按租户授权的读取协议,说明恢复交付还是重新生成。SSE 的事件 ID 本身没有这些存储保证,见 S03。没有恢复合同的客户端应显示不完整,不拼接新模型回答。

16.5 用故障验收控制上线范围

可以先让一个无写工具的内部应用接入已验证的文本能力,限制租户和预算,再验证真实供应商的协议结束与账单差异。演练至少覆盖准入事务后崩溃、usage 后 done 前断流、结算消息重复、取消与完成竞争,以及升级时长流排空。每项都要检查资源、账本和用户状态,不能只看返回码。

实验提供本地“完整且可信 usage 加 done 才结算,中断保留 unknown”的基线。真实供应商事件格式与单位各异,必须由适配器建立自己的结束合同,S07 与 S10 展示了不同形式;任务质量还要另用业务验收集评估。

练习: 租户额度 1000,已有结算 100、unknown 预留 200,两副本同时申请 500。写出一个事务顺序,证明最多一个申请通过;再在“提交准入、发送上游、写入终态”之间分别放置崩溃,说明重启后凭什么恢复。交付物应包含状态表、存储条件更新和故障断言,并明确哪些部分仍需实现。