流式输出怎么实现?SSE 和 WebSocket 怎么选?

Q6-04流式输出与传输协议高频SSEWebSocket流式输出TTFT代理缓冲EventSource

谁在问:一面·工程基础;几乎所有做过 LLM 产品的团队都会问,前后端都可能被问到

口语化问法

  • 你们的流式输出是怎么做的?用的什么协议?
  • SSE 和 WebSocket 有什么区别?为什么大模型 API 都用 SSE 不用 WebSocket?
  • 如果本地开发流式好好的,上线之后变成一次性全吐出来,你会怎么查?

考察意图

这是一面的工程基础题,它本身很简单,真正的区分度全在追问上。面试官在判断:

  1. 你知不知道 SSE 是什么。 很多人以为它是一个和 WebSocket 并列的独立协议,其实它就是一次 HTTP 响应加上一套帧格式约定。
  2. 你有没有真的上线过流式。 判据非常明确——问到"上线后不流式了"时,你的第一反应是什么。真做过的人会直接问"中间过了几层代理"。
  3. 你知不知道流式会连带毁掉什么。 这是这道题从"易"变成"中"的地方:流式会让限流精度、响应侧护栏、结构化输出同时降级。

参考答案

图 2 · 60 分与 90 分差在哪:代价、演进、怎么验证

60

60 分答案(及格线)

流式输出用的是 SSE(Server-Sent Events)。服务端不是一次性返回完整响应,而是保持连接、按帧把生成的内容推给客户端,前端收到一帧就渲染一帧,用户体验上就是字一个个出来。

SSE 和 WebSocket 的区别主要是:SSE 是单向的(只有服务端往客户端推),基于普通 HTTP,实现简单,浏览器有原生的 EventSource 支持,还能自动重连;WebSocket 是双向的,需要协议升级,适合聊天室、协同编辑这类两端都要主动发消息的场景。

大模型 API 用 SSE 是因为场景本身就是单向的——客户端发一次请求,服务端持续返回,中途不需要客户端再说话。所以 SSE 够用,而且比 WebSocket 简单。

为什么只有 60 分:结论全对,但完全停在"教科书对比"层面。没有任何具体的事件名、没有任何一个坑、也没提流式带来的连带后果。这样的回答面试官无法区分你是用过还是看过。

90

90 分答案(有生产经验的回答)

先纠正一个常见误解:SSE 不是和 WebSocket 并列的协议,它就是一次普通的 HTTP 响应,只不过响应体是长连接、按 event: / data: 这套帧格式一帧一帧发,空行结束一帧。理解这一点之后,后面所有的坑都能推出来。

协议上三家是一致的,语义上完全不兼容。 截至 2026-08,OpenAI、Anthropic、Gemini 的文本推理流式全部是 SSE over HTTP POST,没有一家用 WebSocket。但事件语义各不相同:Anthropic 是块级状态机(message_start / content_block_delta / message_stop没有 [DONE] 哨兵),OpenAI Chat Completions 是 delta 增量加 data: [DONE],而 Responses 和 Gemini 的新接口是语义事件流。所以"换个 base_url 就能切模型"在流式场景是假的,网关必须写一层事件翻译。

选型判据我只用一条:只有满足"真双向、二进制音视频、单连接多路复用"任一条时,才值得上 WebSocket。 文本推理三条都不满足,所以三家都没用它。WebSocket 的代价也很清楚——连接是有状态的,绑死在单个实例上,滚动发布会踢掉活跃会话;而 SSE 每次是无状态 HTTP,重连即可换实例。

实践里我踩过几个坑,都挺典型:

  • 浏览器原生的 EventSource 不能设请求头,构造器只接受 URL 和一个 withCredentials,所以带 Authorization 的 API 永远不能用它直连。实际做法是 fetchReadableStream 自己解帧。代价是规范白送的自动重连、断点续传、退避策略全都要自己重写一遍,这一点很多人换的时候没意识到。
  • 代理缓冲是最经典的一个。nginx 的 proxy_buffering 默认是开的,会把上游响应先攒起来再下发——表现就是"本地好好的,上线后转圈几秒再整段砸出来"。上游可以发 X-Accel-Buffering: no 来关,但这个头可能被网关的 proxy_ignore_headers 吃掉
  • 空闲超时proxy_read_timeout 默认 60 秒,模型长时间思考不吐字就会被切。规范给的解法很明确:每 15 秒左右发一个注释行当心跳

因为踩过这些,我们把 TTFT 拆成了两个指标:Pipeline TTFT(第一个字节到达客户端)用于对外 SLO,Model TTFT(推理返回第一个 delta)用于隔离模型本身。两者背离时,问题一定在非模型环节——网关缓冲、检索、上下文拼装、鉴权。这个拆分让我们后来的排查快了很多。

最后是流式的连带后果,我觉得这才是这道题的真正深度:流式会同时让三件事降级——限流精度(流式下输入输出 token 都只能估算)、响应侧护栏(无法对每个 token 去查一次外部系统,只能"先出后审")、工具入参的结构化输出(API 不缓冲不校验,可能收到半截的非法 JSON,max_tokens 截断还会把参数切一半)。所以上流式不是"加个参数",是要接受这三笔账。

追问链

图 1 · 五层追问树:面试官会往哪儿挖
题面很简单,区分度全在追问:你上没上线过流式,一问代理就见分晓

  1. SSE 断线之后会发生什么?规范里有什么机制?

    期望只有网络错误才自动重连,重连带 Last-Event-ID 头可续传。分界:状态码非 200 或 Content-Type 不是 text/event-stream 就是永久失败、不重连 —— 后端返回 500 时 SSE 静默死掉。换 fetch 手工解帧后这些全部失效
    信号说得出 Last-Event-ID 或「非 200 不重连」→ 读过规范;只说「SSE 会自动重连」→ 听说过
  2. 本地流式正常,上线后一次性全吐出来,你怎么查?

    期望第一句应该是「中间过了几层代理」,而不是怀疑模型、SDK 或前端渲染 —— 开发直连、生产走网关,是环境不一致的典型来源。动作:curl 直打后端确认流正常 → 再打生产域名对比 → 查网关的 proxy_buffering,注意 X-Accel-Buffering 可能被忽略
    信号第一反应是查代理 → 一定排过这个障;先怀疑模型或前端 → 没上过线
  3. 前端为什么不能直接用 EventSource 连你们的 API?

    期望EventSource 设不了 Authorization(只接受 URL 与 withCredentials)。绕法:token 进 query(进日志)、cookie、fetch 手工解帧。第三种的代价必须说 —— 自动重连与 Last-Event-ID 续传得自己实现
    信号能补「换 fetch 的代价」和「HTTP/1.1 同域 6 连接、HTTP/2 下不再是问题」→ 深挖过
  4. 上了流式之后,你们的内容审核怎么做?

    期望先承认响应侧护栏与流式在架构上互斥,只有三条路:① 先出后审,每 200 token 异步查,命中就中断撤回 —— 撤得回界面撤不回截图;② 后出先审,高风险场景关流式,代价是首字延迟变成完整生成时长;③ 前置为主,主防线放输入侧与执行层,响应侧只兜底。正解是按场景分级
    信号说得出「先出后审的违规内容已经在屏幕上了」这个具体后果 → 真做过审核;只说「加个审核接口」→ 没考虑过时序
  5. 流式 + 严格 JSON + 审核前置三个都要,首字延迟不超 800 毫秒,怎么答?

    期望先讲清冲突:流式 + 严格 JSON —— 流过程中可能拿到半截 JSON,严格校验就得攒到块结束;审核前置与流式架构性互斥 → ② 拆需求:用户要的是「感觉在响应」,所以自然语言段流式(先出后审)+ 结构化段攒完一次下发并严格校验 → ③ 先量 Pipeline / Model TTFT,差距大先修网关缓冲(免费)→ ④ 残余风险交回产品决策
    信号识别出「三个要求里有两组是架构性互斥」→ 真理解机制;给得出「自然语言段流式 + 结构化段攒完下发」这个折中 → 设计过真实产品
面试官在这题上只等一句话:问到「上线后不流式了」,你的第一反应是不是「中间过了几层代理」。前三层考你读没读过规范,第 4、5 层考你知不知道流式会连带毁掉限流精度、响应侧护栏和结构化输出。

评分要点

  1. 说清 SSE 不是独立协议,是 HTTP 响应体的一种帧格式约定
  2. 给出至少一个具体事件名,并知道 Anthropic 没有 [DONE]、OpenAI Chat 有
  3. 选型判据明确:只有真双向、二进制、多路复用才值得上 WebSocket
  4. 知道 EventSource 不能设请求头,以及换 fetch 手工解帧的代价
  5. 遇到"上线后不流式"时第一反应是查代理层
  6. 知道 proxy_buffering 默认开、X-Accel-Buffering 可能被忽略、空闲超时要靠心跳
  7. (加分)把 TTFT 拆成 Pipeline 与 Model 两个指标
  8. 说得出流式会同时让限流精度、响应侧护栏、工具入参结构化三件事降级
  9. 压力面下能识别架构性互斥,并给出"分段处理"的折中

常见错误

"SSE 单向、WebSocket 双向,看场景选"——说完就停。这是最标准的教科书答案,也是最没有信息量的答案。
"前端用 EventSource 就行"——不知道它带不了鉴权头,说明没写过。
上线后不流式,先怀疑模型或前端渲染。这是"没排过障"最明确的信号。
把 SSE 当成一个需要单独实现的协议,比如说"我们自己实现了一套 SSE 服务器"。
认为 WebSocket 更先进所以更好。有状态连接在无状态服务架构里是负担,不是优势。
完全不提流式的连带后果。只讲怎么实现,不讲上了之后限流、护栏、结构化输出会怎么样。
含糊表述:"我们用的框架自带流式,配一下就好了"——问不出你知不知道底下发生了什么。
把"流式"和"低延迟"划等号。流式改善的是感知延迟(首字更快),端到端总时长通常不变甚至略增。

关联学习