传输层帧开销对 token 流几乎无关紧要,真正需要比较的是断线重连、流中恢复、双工能力等工程特性。文章给出了一套以「80% 处断线」为起点的决策树。
大家做的对比是错的
传输层对比通常从帧开销、连接建立成本和每秒消息数来排名。对这类工作负载,这两点几乎无关紧要。真正决定胜负的,是手机在网络切换时——比如一个耗时四十秒、耗费真金白银的回答正读到一半——会发生什么。
大家做的对比是错的
传输层对比通常从帧开销、连接建立成本和每秒消息数来排名。这些对交易数据流或协作文档编辑器推送数千个小更新确实重要。
但 token 流不是这种场景。它在几十秒内传输几十到几千条小消息,生成它的模型自身的延迟比任何传输层差异都高好几个数量级。如果你是在效率层面选择传输层,那你优化的是系统中最廉价的组件。
真正有区别的特性是你能感知到的:连接断开时会发生什么、客户端能否重新加入进行中的流、客户端能否在半途回传数据、以及你的基础设施对协议的理解程度。
可恢复性是一个存储问题
从这里说起,因为它能重构整个决策。假设一个连接在回答进行到百分之八十时断开了,你能做什么?
重新生成。再次付费,用户再次等待,而且——因为模型是非确定性的——答案会和他们正在读的那个不同。对于一个长耗时的生成,这是所有选项中最差的。
恢复。继续发送遗漏的 token。这要求生成在客户端离开后仍在运行,且其输出被写入了某个可以用序号寻址的地方。
放弃。显示错误和重试按钮。对于短小廉价的回答完全合理,但对于一份三分钟的报告就不可接受了。
关键结论是:恢复不是传输层的特性。SSE 的 Last-Event-ID header 是一种告诉服务器你读到哪里的约定;除非服务器存有缺失的事件,否则它什么都做不了。如果你的处理器把提供商的分块直接 pipe 给客户端、什么都不保留,那么重连请求到来时你无内容可发。
所以可恢复性意味着将生成和交付解耦:模型调用运行在一个 worker 中,按序号将分块追加到以 run id 为 key 的持久化日志,而连接——无论使用什么传输层——就是这个日志上的一个游标。一旦你构建好了这个,所有传输层就都可以支持恢复了,包括轮询。一旦你没构建,哪个都不行。这就是为什么传输层问题取决于架构问题,为什么先争论传输层是争论错了东西。
三种传输层,实话实说
长轮询——服务器保持请求打开直到有内容可发——介于后两者之间,对于 SSE 被屏蔽的环境值得记住。它用你的基础设施已经理解的请求语义提供近乎推送的延迟,代价是每个等待的客户端占用一个连接。
按顺序来,遇到第一个适用项时停:
没人实时盯着看——轮询。批量作业、后台充实、任何结果稍后收集的场景。不要为没人正在读的结果保持连接打开。
客户端需要在生成过程中打断、引导或发送输入——WebSocket。语音、实时 agent 监督、用户对部分输出采取行动的任何场景。第二个 HTTP 请求可以取消一个 run,但它需要一个 run id 和一条路由,这时候 WebSocket 更简单。
多个并发流共享一个页面——WebSocket。在一个连接上复用多个 run 可以避免单连接限制,且给你一条重连路径而不是多条。
除此之外——单向 token 流到浏览器——SSE。它是能做到的最小实现,能穿透普通 HTTP 基础设施,且失败方式是你的现有工具能看到的。
注意默认选项是能力最弱的。这是故意的:WebSocket 是有状态连接,你的负载均衡器、日志、限流器和追踪都需要学习它,为一个单向文本流承担这个成本什么都买不到。
浏览器的 EventSource 会在连接断开时自动重连。如果你的端点在每次连接时都启动一个新的生成,自动重连就等于自动第二次付费。要么让端点成为持久 run 的游标,要么自己处理重连。另外:EventSource 只能 GET 且不能带自定义 header,所以认证必须靠 cookie 或查询参数——而查询参数里的 token 最终会出现在访问日志里,这是另一个问题。
规范没给你的东西现在都是你的。心跳,否则长时间思考期间中间人会关闭空闲连接。带抖动的退避重连,否则服务器短暂重启会产生重连风暴。消息分帧、序号、恢复握手。另外,按连接授权在握手时发生一次,所以长期存在的 socket 会超过 token 有效期除非你重新检查。
失败模式是成本,在你这边而不是提供商那边:一个客户端对九十秒的 job 每 500ms 轮询一次就是 180 个请求。随着 job 老化就退避,返回服务器建议的下次轮询间隔,并让端点足够廉价——理想情况是一次对 job 行及其分块的带索引的简单读取。
三种传输层共有的一条特性:客户端必须能区分"完成了"和"停止说话了"。发送一条带 finish reason 的明确终止消息。静默不是协议。
不把后端搞崩地流式推送 AI 调用
慢速 AI 调用的队列和后台任务