流式输出怎么实现?SSE 和 WebSocket 怎么选?
谁在问:一面·工程基础;几乎所有做过 LLM 产品的团队都会问,前后端都可能被问到
口语化问法
- 你们的流式输出是怎么做的?用的什么协议?
- SSE 和 WebSocket 有什么区别?为什么大模型 API 都用 SSE 不用 WebSocket?
- 如果本地开发流式好好的,上线之后变成一次性全吐出来,你会怎么查?
考察意图
这是一面的工程基础题,它本身很简单,真正的区分度全在追问上。面试官在判断:
- 你知不知道 SSE 是什么。 很多人以为它是一个和 WebSocket 并列的独立协议,其实它就是一次 HTTP 响应加上一套帧格式约定。
- 你有没有真的上线过流式。 判据非常明确——问到"上线后不流式了"时,你的第一反应是什么。真做过的人会直接问"中间过了几层代理"。
- 你知不知道流式会连带毁掉什么。 这是这道题从"易"变成"中"的地方:流式会让限流精度、响应侧护栏、结构化输出同时降级。
参考答案
60 分答案(及格线)
流式输出用的是 SSE(Server-Sent Events)。服务端不是一次性返回完整响应,而是保持连接、按帧把生成的内容推给客户端,前端收到一帧就渲染一帧,用户体验上就是字一个个出来。
SSE 和 WebSocket 的区别主要是:SSE 是单向的(只有服务端往客户端推),基于普通 HTTP,实现简单,浏览器有原生的 EventSource 支持,还能自动重连;WebSocket 是双向的,需要协议升级,适合聊天室、协同编辑这类两端都要主动发消息的场景。
大模型 API 用 SSE 是因为场景本身就是单向的——客户端发一次请求,服务端持续返回,中途不需要客户端再说话。所以 SSE 够用,而且比 WebSocket 简单。
为什么只有 60 分:结论全对,但完全停在"教科书对比"层面。没有任何具体的事件名、没有任何一个坑、也没提流式带来的连带后果。这样的回答面试官无法区分你是用过还是看过。
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 永远不能用它直连。实际做法是fetch加ReadableStream自己解帧。代价是规范白送的自动重连、断点续传、退避策略全都要自己重写一遍,这一点很多人换的时候没意识到。 - 代理缓冲是最经典的一个。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 截断还会把参数切一半)。所以上流式不是"加个参数",是要接受这三笔账。
追问链
SSE 断线之后会发生什么?规范里有什么机制?
期望只有网络错误才自动重连,重连带Last-Event-ID头可续传。分界:状态码非 200 或Content-Type不是text/event-stream就是永久失败、不重连 —— 后端返回 500 时 SSE 静默死掉。换fetch手工解帧后这些全部失效信号说得出Last-Event-ID或「非 200 不重连」→ 读过规范;只说「SSE 会自动重连」→ 听说过本地流式正常,上线后一次性全吐出来,你怎么查?
期望第一句应该是「中间过了几层代理」,而不是怀疑模型、SDK 或前端渲染 —— 开发直连、生产走网关,是环境不一致的典型来源。动作:curl直打后端确认流正常 → 再打生产域名对比 → 查网关的proxy_buffering,注意X-Accel-Buffering可能被忽略信号第一反应是查代理 → 一定排过这个障;先怀疑模型或前端 → 没上过线前端为什么不能直接用
EventSource连你们的 API?期望EventSource设不了Authorization头(只接受 URL 与withCredentials)。绕法:token 进 query(进日志)、cookie、fetch手工解帧。第三种的代价必须说 —— 自动重连与Last-Event-ID续传得自己实现信号能补「换 fetch 的代价」和「HTTP/1.1 同域 6 连接、HTTP/2 下不再是问题」→ 深挖过上了流式之后,你们的内容审核怎么做?
期望先承认响应侧护栏与流式在架构上互斥,只有三条路:① 先出后审,每 200 token 异步查,命中就中断撤回 —— 撤得回界面撤不回截图;② 后出先审,高风险场景关流式,代价是首字延迟变成完整生成时长;③ 前置为主,主防线放输入侧与执行层,响应侧只兜底。正解是按场景分级信号说得出「先出后审的违规内容已经在屏幕上了」这个具体后果 → 真做过审核;只说「加个审核接口」→ 没考虑过时序流式 + 严格 JSON + 审核前置三个都要,首字延迟不超 800 毫秒,怎么答?
期望① 先讲清冲突:流式 + 严格 JSON —— 流过程中可能拿到半截 JSON,严格校验就得攒到块结束;审核前置与流式架构性互斥 → ② 拆需求:用户要的是「感觉在响应」,所以自然语言段流式(先出后审)+ 结构化段攒完一次下发并严格校验 → ③ 先量 Pipeline / Model TTFT,差距大先修网关缓冲(免费)→ ④ 残余风险交回产品决策信号识别出「三个要求里有两组是架构性互斥」→ 真理解机制;给得出「自然语言段流式 + 结构化段攒完下发」这个折中 → 设计过真实产品
评分要点
- 说清 SSE 不是独立协议,是 HTTP 响应体的一种帧格式约定
- 给出至少一个具体事件名,并知道 Anthropic 没有
[DONE]、OpenAI Chat 有 - 选型判据明确:只有真双向、二进制、多路复用才值得上 WebSocket
- 知道
EventSource不能设请求头,以及换fetch手工解帧的代价 - 遇到"上线后不流式"时第一反应是查代理层
- 知道
proxy_buffering默认开、X-Accel-Buffering可能被忽略、空闲超时要靠心跳 - (加分)把 TTFT 拆成 Pipeline 与 Model 两个指标
- 说得出流式会同时让限流精度、响应侧护栏、工具入参结构化三件事降级
- 压力面下能识别架构性互斥,并给出"分段处理"的折中
常见错误
EventSource 就行"——不知道它带不了鉴权头,说明没写过。