你的后端基础设施并非为流式响应而设计
流式传输(Streaming)是一项产品决策。设计团队的某个人看到竞争对手的聊天 UI 像打字机一样逐个吐出 Token,看到用户在第 200 毫秒看到第一个字符出现时肩膀放松了下来,而不是盯着 4 秒钟的空白屏幕发呆,于是决策就此达成:我们要做流式传输。这个拉取请求(PR)修改了 API 网关中的三个文件。现在,模型输出通过服务器发送事件(Server-Sent Events,SSE)增量刷新。功能在周二上线,周三的满意度评分就有了明显的提升。没人向基础设施团队提工单。
一个月后,值班工程师盯着三个互不一致的仪表盘发愁。自动扩缩容(Autoscaler)配置的 Pod 数量是 CPU 图表显示需求量的两倍。P99 延迟仪表盘坏了——不是出了故障,而是变得无法解读,因为直方图分桶(Histogram Buckets)止步于 5 秒,而现在大多数 Span 都落在溢出区间。上一季度定价时的容量模型显示,该服务每节点每秒可处理 1200 个请求。而值班人员面前的图表显示,它在处理 400 个请求时就已经难以为继。
流式响应在临门一脚时赢得了 UX,却暗中改写了请求路径最初设计时所针对的每一个下游契约。发布该功能的团队并不知道他们背负了基础设施债务,因为没有任何地方发出响亮的报错。成本账单、仪表盘和值班轮值承受了这一切。
忘记如何结束的连接
大多数 L7 负载均衡器都是针对过去十年常见的流量形态进行调优的:短促、突发且无状态的 HTTP。默认设置假设一个请求打开一个 TCP 连接,在不到一秒的时间内交换几 KB 的数据,然后连接被回收进连接池,供稍后的另一个请求使用。30 到 60 秒的空闲超时并非失误——它们是针对这种形态的优化,在连接堆积之前将其释放回池中。
流式传输反转了这种形态。现在,单个响应在整个生成期间都会占用连接。对于长推理任务或数千 Token 的回答,这个时间长达数十秒。更糟糕的是,连接甚至不是均匀繁忙的。字节以集群形式流动——一个 Token,模型进行下一次前向传播时的停顿,然后又是另一个 Token。从负载均衡器的角度来看,这与客户端停滞完全一致,默认行为是将其断开。
第一个症状是 SDK 会静默重试的一类间歇性断连。第二个症状是连接池回收速度跟不上流量突发,因为每个空闲槽位现在被绑定在模型生成的全过程,而不是 HTTP 请求的长度。第三个症状是每个 Pod 的连接上限变成了实际的容量瓶颈——不是 CPU,不是内存,而是 Pod 配置的文件描述符(File-descriptor)预算。一个原本可以轻松处理每秒一千个短请求的节点,现在只能处理 300 个流,因为这是它在不耗尽套接字表(Socket Table)的情况下所能维持的并发连接数。
补救措施并不深奥,但在第一次事故发生前很少被采用。需要告诉负载均衡器这是一个流式工作负载:延长空闲超时以匹配最坏情况下的生成时间;以短于链路上最激进代理的间隔插入保活(Keepalive)消息;在协议允许的情况下提高单连接并发上限;理想情况下,通过支持 HTTP/2 或 SSE 的代理进行路径处理,实现真正的多路复用,而不是为每个请求占用一个 TCP 槽位。否则,团队的可服务性指标就会被链路中默认超时最短的中间设备所挟持。
不仅仅跨越字节的 Span
追踪系统被配置为跟随请求穿过服务网格:请求进入时开启 Span,响应结束时关闭 Span。Span 的持续时间成为操作的延迟,而延迟数据进入分位值直方图,驱动着每一个仪表盘、告警和 SLO。在“响应结束”是大致已知间隔末尾的一个时间点时,这种做法行之有效。
对于流式响应,何时结束的问题变得模棱两可。操作是在模型生成最后一个 Token 时结束吗?是在最后一个字节发出时吗?还是在客户端确认接收时?答案本身并不重要,重要的是无论监测系统选择了哪种约定,原有的直方图分桶都不再适用了。对于一个 P50 为 300 毫秒、P99 为 2 秒的服务,原本合理的分桶边界在现在 P50 为 4 秒、P99 为 14 秒的情况下已失去意义。大多数 Span 落在溢出桶中,这意味着分位值估算器在尾部(这正是 SLO 关注的区间)的准确性退化为一个毫无背景信息的数字。
结构化的修复方案是承认流式请求不是一个操作,而是两个。一个是请求操作,在第一个字节处结束,其持续时间是首个 Token 的响应时间(Time-to-first-token)。另一个是流操作,从第一个字节开始,持续时间是 Body 的发送时间。前者是延迟指标,能清晰地映射到用户感知到的响应速度,是衡量“这个东西响应了吗”的合适 SLO。后者是类似于吞吐量的指标,映射到模型速度和基础设施占用时间,是衡量“这个东西完成了吗”的合适 SLO。
将它们混为一谈会导致仪表盘的分位线因团队无法解释的原因而波动——一个每秒产生更多 Token 的更快模型可能会让总 Span 时长上升,因为用户现在会问更长的问题;而连续几周“延迟变差”实际上可能是产品变得更好的结果。不拆分 Span 的团队构建了一套测量系统,这套系统在下一次发布时会误导他们。
信号读反了的自动伸缩器
- https://medium.com/@saifaliunity/why-gcp-load-balancers-struggle-with-stateful-llm-traffic-and-how-to-fix-it-1ff6736c1052
- https://theelderscripts.com/why-llm-api-calls-break-in-production/
- https://markaicode.com/fix-llm-api-timeout-errors-production/
- https://medium.com/restayway-tech/scaling-pains-why-your-sse-app-fails-under-load-balancers-d3b971165acc
- https://blog.pranshu-raj.me/posts/exploring-sse/
- https://medium.com/@daniakabani/how-we-used-sse-to-stream-llm-responses-at-scale-fa0d30a6773f
- https://futureagi.com/glossary/time-to-first-token/
- https://trackai.dev/tracks/performance/latency-ttft/ttft-explained/
- https://bentoml.com/llm/inference-optimization/llm-inference-metrics
- https://www.runpod.io/articles/guides/llm-inference-optimization-playbook
- https://apxml.com/courses/how-to-build-a-large-language-model/chapter-29-serving-llms-at-scale/handling-concurrent-requests
- https://scaleops.com/blog/kubernetes-hpa/
- https://sedai.io/blog/kubernetes-autoscaling
- https://oneuptime.com/blog/post/2026-02-06-otel-span-metrics-connector-red-metrics/view
- https://opentelemetry.io/docs/concepts/signals/traces/
