想象你在一家高级餐厅点了一道“佛跳墙”,后厨需要四十分钟才能做好。传统做法是后厨把所有食材炖好、摆盘、淋汁,然后一次性端上桌——你只能对着空桌子干等。但如果大厨每炖好一块鲍鱼、每煨熟一朵香菇,就通过传菜窗先端出一小碟,你边尝边等,四十分钟不知不觉就过去了。 大模型生成回答的原理与此惊人相似:一段 500 字的回答,模型可能要计算二三十秒。传统 HTTP 请求要求等模型算完整个回答才一次性返回,用户只能盯着 loading 转圈。而 SSE(Server-Sent Events,服务器推送事件)技术就像那条“传菜窗”——模型每生成一个词或一个标点,就立刻通过连接推送过来,前端逐字渲染,用户看到的仿佛模型在“实时打字”。 本文沿着一条完整工程链路展开:先讲为什么要流式,再讲 SSE 协议原理、后端分块传输、前端 EventSource 消费、断线重连续传,最后聊生产环境中的工程化细节。读完你不仅能听懂 SSE 流式的完整逻辑,还能亲手搭出一套可运行的大模型流式输出服务。

岗位关联

掌握 SSE 流式输出是构建大模型应用的基本功。聊天机器人、AI 搜索、智能写作等产品都依赖它改善用户等待体验,这也是大模型应用工程师面试中的高频考察方向。

目录
  1. 可视化演示
  2. 流式输出价值
  3. SSE协议原理
  4. 后端分块传输
  5. 前端EventSource
  6. 断线重连策略
  7. 工程化实践
  8. AI 应用场景
  9. Python 演示
  10. 常见误区
  11. 面试题

可视化演示

动态演示大模型SSE流式输出工程实现的核心机制,通过基础/核心/应用/扩展四种模式展示关键概念与交互流程。

本节配有交互动效(进入视口后自动加载,约 3KB)。

大模型SSE流式输出工程实现:SSSE S1:SSE协议原理与HTTP S2:后端分块传输与LLM推理 核心机制可视化 大模型SSE流式输出工程实现整体架构与关键组件

流式输出价值

想象你在餐厅点了一道需要四十分钟炖煮的佛跳墙。传统做法是后厨全部做好后一次性端上来,你只能对着空桌子玩手机。但若大厨每炖好一味食材就先端出一小碟,你边尝边等,等待就不再是煎熬。大模型输出正是如此——一个几百字的回答可能需要几十秒计算,“最后一次端上来”和“边做边端”的体验天差地别。

传统 HTTP 请求遵循“请求-响应”模式:客户端发送一个请求,服务器必须在计算出完整结果后才返回一次响应。对于大模型推理,一次完整响应少则几秒、多则几十秒,期间用户只能面对加载动画。研究数据表明,超过 2 秒的等待就会显著增加用户流失率——这就像客人等菜超过五分钟就会开始不耐烦。

流式输出的核心思路是边生成边返回。服务器将大模型逐词生成的结果切分成多个小块,每生成一小块就立刻推送给客户端。这样用户看到第一个词的时间被压缩到几百毫秒,后续内容陆续到达,形成“打字机”般的视觉效果。首 Token 延迟由此成为衡量大模型应用体验的核心指标之一。

正如佛跳墙的“边做边端”让客人从第一口就能确认菜的味道,SSE 流式让用户从第一个 token 就能确认模型的回答方向。如果第一句话就跑偏,用户可以立刻停止,不必白等三十秒。

T总等待=T首词+T剩余生成\displaystyle T_{总等待} = T_{首词} + T_{剩余生成}
总等待时间被拆成“看到第一个字的时间”和“剩余字全部到齐的时间”。流式输出虽然不能缩短总时长,但能把最煎熬的前半段压缩到极短。
  1. T_首词(首个 Token 时间)从客户端发出请求到收到模型输出的第一个词块,这段时间决定了用户“最先看到什么”。数值越小,用户越快感到“有响应了”。
    T首词=T请求+T排队+T计算\displaystyle T_{首词} = T_{请求} + T_{排队} + T_{计算}
  2. T_剩余生成(剩余内容生成时间)模型逐词生成剩余回答所需的时间。若响应总长度为 L、生成速率为 v,则该时间正比于 (L-1)/v。
    T剩余生成=L−1v\displaystyle T_{剩余生成} = \frac{L - 1}{v}
  3. 感知等待直觉传统方式让用户承受完整等待;SSE 方式下用户在第 T_首词 时刻就看到内容,感知等待几乎等于 T_首词,体验大幅改善。
    T感知≈T首词\displaystyle T_{感知} \approx T_{首词}

SSE协议原理

传菜窗不是随随便便开一个洞,它有自己的规矩:每道菜附一张小纸条,写清菜名、编号、内容,从窗口递出。收菜的服务员只需认准纸条格式,就知道这道菜是什么、该放哪一桌。SSE 协议就是大模型与前端之间的“传菜纸条规范”。

SSE(Server-Sent Events)是 HTML5 标准定义的一种服务器推送技术,基于 HTTP 长连接。服务器通过 text/event-stream 媒体类型持续发送数据,每个事件由若干字段组成:data(数据内容)、event(事件类型)、id(事件编号)、retry(重连间隔),事件之间用空行 \n\n 分隔。

SSE 是单向信道,只允许服务器向客户端推送。浏览器原生的 EventSource 对象会自动处理连接管理和断线重连,这是相比 WebSocket 的核心优势——不需要引入双向聊天式协议,只需一个 HTTP 接口就能实现实时推送。对比来看:WebSocket 像对讲机(双向、全双工),SSE 像传菜窗(单向、自动重连)。

有了这套“传菜纸条规范”,后厨(服务器)和传菜员(EventSource)就有了共同语言:菜名(event)决定放哪张桌(事件绑定),编号(id)用于重连时确认已经上到哪道菜(断点续传),空行则是两盘菜之间的分隔垫纸。

R事件=N事件T持续\displaystyle R_{事件} = \frac{N_{事件}}{T_{持续}}
事件速率表示单位时间内服务器推送的事件数量。事件速率越高,前端收到的数据切片越碎,UI 更新越频繁,打字机效果越流畅。
  1. R_事件(事件速率)一次流式响应中,每秒平均推送的 SSE 事件个数,直接决定前端渲染的细腻程度。
    R事件=N事件Δt\displaystyle R_{事件} = \frac{N_{事件}}{\Delta t}
  2. N_事件(事件总数)一次流式响应中发送的 SSE 事件个数,每个事件包含一段 data 字段。大模型流式中,一个事件通常对应一个 token。
    N事件=∑i=1M1事件i\displaystyle N_{事件} = \sum_{i=1}^{M} 1_{事件_i}
  3. T_持续(流式持续时间)从连接建立到流结束的时间跨度,等于首 token 时间加上全部 token 生成时间。
    T持续=t结束−t开始\displaystyle T_{持续} = t_{结束} - t_{开始}

后端分块传输

后厨的传菜流程讲究“炒好一碟端一碟”:大厨把食材下锅,翻炒几下,盛出第一碟递给传菜员,传菜员立刻送出,不需要等整桌菜齐。后端实现 SSE 正是这个模式——用生成器逐段产出数据,每产出一次就立刻推给前端。

在 FastAPI 等 Web 框架中,只需返回一个 StreamingResponse,设置 media_type="text/event-stream",并传入一个生成器函数。生成器内部每次 yield 一段符合 SSE 格式的文本,框架就会把该文本立即写入响应流。同时需要设置三个关键响应头:Cache-Control: no-cache(禁止缓存)、Connection: keep-alive(保持长连接)、X-Accel-Buffering: no(禁用 Nginx 代理缓冲)。

生产环境里,后端传菜还有一个常见陷阱:Nginx 默认会缓冲上游响应,把小块攒成大块再发出去,导致“传菜窗”被堵住。设置 X-Accel-Buffering: no 相当于告诉 Nginx:“这道菜要趁热端,别替我攒着。”

D端到端=D网络+S总B带宽\displaystyle D_{端到端} = D_{网络} + \frac{S_{总}}{B_{带宽}}
端到端延迟等于固定网络开销加上数据量除以带宽。SSE 的意义在于让“首块数据”尽早到达,而不是等全部数据打包完再发送。
  1. D_网络(固定网络开销)无论数据块多少,TCP 连接建立、TLS 握手等开销基本固定,通常为几十到几百毫秒。
    D网络=T连接+T握手\displaystyle D_{网络} = T_{连接} + T_{握手}
  2. S_总 / B(数据量与带宽之比)总数据量越大、带宽越小,传输耗时越长。分块传输让接收端尽早拿到第一批数据,而不是等整包数据。
    D传输=S总B带宽\displaystyle D_{传输} = \frac{S_{总}}{B_{带宽}}
  3. 首字节时间对流式而言,首字节时间才是最关键的指标,而它只取决于首块大小而非总大小。
    T首字节=D网络+S首块B带宽\displaystyle T_{首字节} = D_{网络} + \frac{S_{首块}}{B_{带宽}}
text8 行
event: tokendata: {"token":"你"} event: tokendata: {"token":"好"} event: donedata: {"done":true}

前端EventSource

传菜窗口的另一侧站着服务员(EventSource)。他的工作很简单:站在窗口前,听到铃声就伸手接菜(onmessage),接到菜就放到对应的餐桌上(渲染 DOM),如果窗口被堵住就大声报告(onerror)。浏览器原生提供的 EventSource 对象正是这位训练有素的“服务员”。

创建连接只需一行代码:new EventSource('/chat')。浏览器会自动发送 GET 请求并保持长连接。随后注册三个关键事件:open(连接成功)、message(收到数据)、error(连接出错)。大模型流式输出中,服务端通常把 token 封装成 JSON 放进 data 字段,前端在 message 回调里 JSON.parse 后追加渲染即可。

优秀的“服务员”不只是机械接菜,还会做好体验细节:在回答开始时显示光标闪烁的光标,在结束时移除;收到半截 JSON(网络分包导致)时先缓存等完整再解析;连接失败时在 UI 上温柔地提示“网络不太顺畅,正在重连…”。

FUI=N已渲染T渲染窗口\displaystyle F_{UI} = \frac{N_{已渲染}}{T_{渲染窗口}}
UI 更新频率等于已渲染 token 数除以渲染时间窗。事件推送越频繁,UI 更新越平滑,用户的“打字机”观感越真实。
  1. N_已渲染(渲染 Token 数)UI 上已经展示的内容量,随事件到达持续累加。
    N已渲染(t)=∑i:ti≤tlen(datai)\displaystyle N_{已渲染}(t) = \sum_{i: t_i \le t} len(data_i)
  2. T_渲染窗口(渲染时间窗)从首屏开始到当前结算点的时间长度。
    T渲染窗口=tnow−tstart\displaystyle T_{渲染窗口} = t_{now} - t_{start}
  3. UI 更新率事件频率越高,UI 更新越丝滑。实际渲染还要受浏览器帧率(约 60fps)约束,因此高频事件会被合并到同一帧渲染。
    FUI=N已渲染T渲染窗口\displaystyle F_{UI} = \frac{N_{已渲染}}{T_{渲染窗口}}
text7 行
var es = new EventSource('/chat');es.onmessage = function (e) {  var data = JSON.parse(e.data);  if (data.token) {    document.getElementById('answer').textContent += data.token;  }};

断线重连策略

餐厅营业中最怕传菜窗被临时堵住:服务员手里端着菜,窗口却推不开。此时他会放下菜、等几秒再推一次;如果还不行,就等更久再试。SSE 的断线重连策略与之如出一辙——自动重连、退避等待、断点续传。

EventSource 内置了自动重连能力:连接意外断开后,浏览器会自动重新发起请求。但重连后服务器如何知道客户端已经收到了哪些数据?这就用到事件 ID 机制:服务器为每个事件分配递增的 id 字段,浏览器断线后会在重连请求的 Last-Event-ID 头中带上最后收到的事件 ID,服务器据此从断点继续推送。

为了防止频繁重连压垮服务器,重连应采用指数退避策略:第一次等待 1 秒,失败后等 2 秒、4 秒、8 秒……直到封顶值。此外,长时间无数据的连接会被中间代理判定为空闲而切断,因此需要心跳保活——每隔一段时间发送一行注释(如 : ping)保持连接活跃。

还记得传菜单上记的“已上到第几道菜”的编号吗?Last-Event-ID 就是这张传菜单。断线重连后,服务端看一眼编号,就能从下一道菜继续端,用户不会吃到重复的菜,也不会漏掉主菜。

twait(n)=min⁡(tmax,  tbase×2n)\displaystyle t_{wait}(n) = \min\left(t_{max}, \; t_{base} \times 2^{n}\right)
指数退避公式:第 n 次重连的等待时间等于基础间隔乘以 2 的 n 次方,但不超过封顶值。这样既能在断线初期快速恢复,又不会在持续断网时狂轰滥炸。
  1. t_base(基础重试间隔)第一次重连前的等待时间,通常设为 1 秒。
    tbase=1 s\displaystyle t_{base} = 1\ \text{s}
  2. 2^n(指数增长因子)每重试失败一次,等待时间翻倍:1s、2s、4s、8s……避免服务端被重连风暴压垮。
    twait(n)=tbase×2n\displaystyle t_{wait}(n) = t_{base} \times 2^{n}
  3. t_max(封顶值)等待时间不无限膨胀,封顶后保持恒定频率重试,通常设为 15 到 30 秒。
    twait=min⁡(tmax,  tbase×2n)\displaystyle t_{wait} = \min\left(t_{max}, \; t_{base} \times 2^{n}\right)

工程化实践

餐厅生意火爆时,传菜窗要同时服务几十桌客人。后厨必须管理好多条上菜通道的并发、监控每扇窗是否拥堵、验证取菜人的身份、并在后厨故障时启动应急预案。SSE 工程的落地同样离不开这四件事。

第一,并发连接管理。每个 SSE 长连接都会占用服务器内存与文件描述符,连接数过多会耗尽资源。需要通过连接池限制单机最大连接数,并设置空闲超时自动清理僵尸连接。第二,监控告警体系。核心指标包括活跃连接数、事件推送延迟、错误率、重连次数,任何异常都要触发告警。第三,安全与鉴权。EventSource 的 GET 请求无法自定义 Header,令牌通常放在 URL 查询参数或 Cookie 中,服务端校验通过后才建立流。第四,降级兜底方案。当模型服务超时或过载时,返回普通 JSON 响应告知用户“稍后重试”,而不是让连接无限挂起。

就像餐厅会准备备用菜单一样,SSE 工程也要有 Plan B:当流式通道异常时,降级为非流式一次性返回,至少保证用户能得到完整回答。工程化的本质,就是让传菜窗在高峰、拥堵、故障时依然可靠运转。

C上限=M可用S单连接\displaystyle C_{上限} = \frac{M_{可用}}{S_{单连接}}
单机可承载的 SSE 连接数上限 ≈ 可用内存 ÷ 单连接内存开销。每个长连接都有内核缓冲区和用户态对象成本,必须心中有数。
  1. M_可用(可用内存)服务器能分配给长连接的内存总量,通常取系统内存的 60% 到 70%。
    M可用=M总量×γ\displaystyle M_{可用} = M_{总量} \times \gamma
  2. S_单连接(单连接开销)每个 SSE 连接占用的内核缓冲区与用户态对象大小,约几十 KB。
    S单连接=S内核+S对象\displaystyle S_{单连接} = S_{内核} + S_{对象}
  3. 连接上限超过上限会导致内存耗尽、拒绝新连接,因此需要连接池与并发控制。
    C上限=M可用S单连接\displaystyle C_{上限} = \frac{M_{可用}}{S_{单连接}}

AI 应用场景

AI 聊天助手逐字输出

阶段:模型推理与内容交付

解决的问题:传统一次性返回时,用户盯着 loading 转圈 20 秒,近半用户会直接关闭页面;SSE 流式让回答逐字出现,用户感知等待时间从 20 秒降到 1 秒,显著提升对话体验留存。

具体案例:某 SaaS 智能客服机器人接入 SSE 流式输出后,首 Token 延迟从 8 秒降至 900 毫秒,用户满意度提升 28%,人均对话轮次从 3 轮增加到 5 轮。

AI 搜索引擎流式摘要

阶段:检索增强生成

解决的问题:传统 AI 搜索需等待检索和摘要全部完成后才展示,用户焦虑感强。SSE 可以先推送检索到的引用来源,再流式输出摘要,用户边读摘要边扫视引用,信息获取效率翻倍。

具体案例:某 AI 搜索产品中,用户输入问题后 1 秒内先看到参考链接列表,摘要逐句呈现,点击引用即可定位原文;弱网环境通过 Last-Event-ID 续传,保证长摘要不因断网中断。

企业知识库问答流式推送

阶段:知识库问答与文档生成

解决的问题:企业文档问答的回复通常长达 500 至 1000 字,非流式返回令移动端用户长时间白屏。SSE 流式结合断线重连,确保弱网环境下回答不丢失、不重复。

具体案例:某移动端知识库 App 中,员工提问后答案逐段出现,可边读边决定是否需要继续等待;网络切换时自动重连并从断点续传,单次回答完整率从 76% 提升到 99%。

Python 演示

python64 行
"""大模型 SSE 流式输出完整示例: FastAPI 服务端 + requests 客户端 启动服务端: python sse_demo.py server启动客户端: python sse_demo.py client"""import jsonimport sysimport time import requestsimport uvicornfrom fastapi import FastAPIfrom fastapi.responses import StreamingResponse app = FastAPI()  def sse_format(event: str, payload: dict) -> str:    """把 dict 包装成 SSE 规范事件块"""    data = json.dumps(payload, ensure_ascii=False)    return f"event: {event}\ndata: {data}\n\n"  def token_generator():    """模拟大模型逐字生成结果, 每次 yield 一个事件块"""    answer = "SSE 让大模型边思考边输出, 用户不再干等"    for ch in answer:        yield sse_format("token", {"token": ch})        time.sleep(0.03)    yield sse_format("done", {"done": True})  @app.get("/chat")def chat():    headers = {        "Cache-Control": "no-cache",        "Connection": "keep-alive",        "X-Accel-Buffering": "no",    }    return StreamingResponse(        token_generator(),        media_type="text/event-stream",        headers=headers,    )  def consume():    """客户端消费 SSE 流: 逐行读取并打印 token"""    resp = requests.get("http://127.0.0.1:8000/chat", stream=True)    for line in resp.iter_lines(decode_unicode=True):        if line and line.startswith("data: "):            payload = json.loads(line[6:])            if "token" in payload:                sys.stdout.write(payload["token"])                sys.stdout.flush()    print()  if __name__ == "__main__":    mode = sys.argv[1] if len(sys.argv) > 1 else "server"    if mode == "client":        consume()    else:        uvicorn.run(app, host="0.0.0.0", port=8000)
目的:演示了大模型流式输出的最小闭环——FastAPI 服务端用生成器逐字产出 SSE 事件块,requests 客户端用 iter_lines 逐行消费并实时打印。
  1. def sse_format(event, payload)将字典序列化为 event: + data: 字段并用空行分隔,这是 SSE 规范的核心格式。
  2. def token_generator()生成器函数,每次 yield 一个事件块,time.sleep(0.03) 模拟模型逐字生成速度。
  3. StreamingResponse(token_generator(), media_type="text/event-stream", headers=headers)FastAPI 将生成器包装为流式响应,media_type 告诉浏览器这是 SSE 流。
  4. "X-Accel-Buffering": "no"禁用 Nginx 代理缓冲,否则数据会被攒批发送,失去流式效果。
  5. resp.iter_lines(decode_unicode=True)requests 流式逐行读取响应,每行以 \n 为边界切分 SSE 事件。
  6. line[6:]去掉 data: 前缀后得到 JSON 字符串,再 json.loads 解析出 token 内容。

常见误区

误区SSE 和 WebSocket 不都是实时通信吗?我直接用 WebSocket 就好了,SSE 是不是过时了?

正解两者服务场景完全不同。SSE 是单向传菜窗(服务器到客户端),WebSocket 是双向对讲机。SSE 建立在 HTTP 之上,浏览器原生支持、自动重连,实现极简,非常适合大模型单向内容推送;WebSocket 需要双向通信时才用(协同编辑、在线游戏),握手和协议复杂度明显更高。

为何常见:两者都能实现“实时推送”,初学者容易混淆传输方向和应用边界。

误区EventSource 自带自动重连,我只要创建连接就行,断网恢复后它自己会好。

正解EventSource 确实能自动重连,但重连后服务端并不知道客户端收到了哪些数据。如果不配合 Last-Event-ID 断点续传,服务端会从头推送,导致内容重复或错乱。正确做法是:服务端为每个事件分配递增 id,客户端在重连请求中携带 Last-Event-ID,服务端从断点之后的下一事件继续推送。

为何常见:文档只强调“自动重连”,忽略了上下文断点恢复这一关键环节。

误区只要把 `media_type` 设为 `text/event-stream`,浏览器就会自动逐字显示,前端不用写代码。

正解text/event-stream 只是告诉浏览器数据格式。前端仍需要创建 EventSource、监听 message 事件、解析 data 字段并手动更新 DOM。此外生产环境中的代理缓冲、响应头配置如果不处理好,数据可能会被攒批送达,流式效果荡然无存。

为何常见:把“协议类型标识”和“自动渲染”混为一谈,低估了前后端协同工作量。

误区连接建立后只要不断开就一直有效,不需要额外做保活处理。

正解大多数中间代理(Nginx、负载均衡)都有空闲超时机制,如果超过一定时间没有数据经过,连接会被自动切断。因此需要定时发送心跳数据(如注释行 : ping\n\n),让连接看起来“一直有活动”。这就像传菜窗安静久了,需要隔一会儿敲一敲确认通道还通畅。

为何常见:本地开发网络链路短,连接空闲超时问题难以暴露,到生产环境才频繁掉线。

误区SSE 是纯文本协议,只能广播简单消息,传不了 JSON 这类结构化数据吧?

正解SSE 的 data 字段可以携带任意 UTF-8 文本,把 JSON 序列化成字符串放进 data 字段即可。大模型流式应用恰恰就是用 SSE 传输 {"token": "你"} 这类结构化数据,一个 token 一个事件。二进制数据才需要额外编码(如 Base64),但也不是障碍。

为何常见:听说 WebSocket 支持二进制帧,误以为 SSE 能力受限,其实是协议定位不同。

面试题

Q1. SSE 流式传输使用的 HTTP 媒体类型是?

单选题 · easy

  • A.事件流媒体类型← 正确答案
  • B.JSON 数据类型
  • C.网页文本类型
  • D.二进制流类型

答案:A。SSE 规范定义的媒体类型是 text/event-stream,即“事件流媒体类型”。它是纯文本协议,用于传输以行为单位的事件流。JSON 是应用层数据格式,HTML 是普通网页,二进制流则对应流式下载,均不符合 SSE 规范。

Q2. SSE 连接的数据传输方向是?

单选题 · easy

  • A.服务端推送客户端← 正确答案
  • B.客户端发送服务端
  • C.双向实时通信
  • D.离线文件传输

答案:A。SSE 是单向信道,只允许服务器主动向客户端推送数据,客户端通过 EventSource 接收;客户端如需发送数据,需另发普通 HTTP 请求。双向实时通信是 WebSocket 的能力。

Q3. SSE 事件流中,相邻事件之间的分隔符号是?

单选题 · easy

  • A.单个换行符间隔事件
  • B.使用空行间隔事件← 正确答案
  • C.使用逗号分隔事件
  • D.分号作为事件分隔

答案:B。SSE 规范要求每个事件块以空行 \n\n 结尾,空行前是一个事件的全部字段(data、event、id 等)。单个换行只是字段内换行,逗号和分号不参与事件分隔。这就像传菜窗里两盘菜之间要靠垫纸(空行)隔开。

Q4. SSE 协议内置支持的能力包括?

多选题 · mid

  • A.自动断线重连能力← 正确答案
  • B.事件 ID 续传能力← 正确答案
  • C.自定义事件类型← 正确答案
  • D.双向数据通信

答案:ABC。SSE 规范内置自动重连、id 字段实现断点续传、event 字段自定义事件类型。但 SSE 是单向协议,不支持双向数据通信(那是 WebSocket 的职责)。

Q5. SSE 断线重连实现断点续传的依据是?

单选题 · mid

  • A.使用 HTTP 头传输 token
  • B.使用 Last-Event-ID 字段← 正确答案
  • C.使用 URL 参数保存状态
  • D.服务端无法感知重连

答案:B。EventSource 重连时会在请求头自动携带 Last-Event-ID,值为最后收到的事件 id。服务端读到该头后从对应事件的下一条开始推送。这相当于传菜单上的“已上到第几道菜”的编号。

Q6. 对比 WebSocket,SSE 在大模型输出场景中的优势是?

单选题 · mid

  • A.双向交互性能更强大
  • B.基于 HTTP 兼容性更好← 正确答案
  • C.传输二进制数据更快
  • D.不需要维持长连接

答案:B。SSE 基于 HTTP,天然穿过防火墙和代理,浏览器原生支持,无需额外协议握手;WebSocket 需要专门的握手和协议实现。大模型输出是单向推送,SSE 的自动重连和事件 ID 机制让它在体验上更省心,且并不需要双向通信能力。

Q7. 前端使用 EventSource 消费大模型流式输出的正确操作包括?

多选题 · mid

  • A.监听 message 获取数据← 正确答案
  • B.监听 error 事件做降级← 正确答案
  • C.使用 fetch 流式读取← 正确答案
  • D.用 Blob 对象解析流

答案:ABC。EventSource 通过 message 事件获取数据、error 事件感知连接异常;也可用 fetch 配合 ReadableStream 替代 EventSource 获得更细粒度控制。Blob 对象用于二进制大对象,不适合解析 SSE 文本事件流。

Q8. Nginx 反向代理下,避免 SSE 流被缓冲的关键配置是?

单选题 · hard

  • A.开启 gzip 压缩传输
  • B.关闭代理响应缓冲← 正确答案
  • C.增大连接超时时间
  • D.限制请求体大小

答案:B。Nginx 默认缓冲上游响应,会把小数据块攒成大数据块再转发,破坏流式效果。配置 proxy_buffering off 可让数据一到达就立即转发。gzip 与缓冲无关,超时设置影响的是空闲断开,请求体大小限制影响的是上传。

Q9. 当 SSE 长连接长时间无数据时,中间代理可能切断连接,解决方案是?

单选题 · hard

  • A.定时发送心跳注释保持活跃← 正确答案
  • B.加大客户端接收缓冲区
  • C.缩短服务端生成间隔
  • D.改用 HTTP 短连接轮询

答案:A。空闲超时是代理服务器的自我保护机制。SSE 服务端可以每隔一段时间发送一行注释(如 : ping\n\n),让代理认为连接仍在活跃。这就像传菜窗安静久了,敲一敲确认通道还开着。

Q10. 大模型 SSE 流式输出的工程化落地中,需要重点关注的环节是?

多选题 · hard

  • A.配置代理关闭响应缓冲← 正确答案
  • B.设置心跳保活长连接← 正确答案
  • C.监控连接数量防泄露← 正确答案
  • D.必须使用网络套接字

答案:ABC。SSE 工程化三板斧:代理缓冲关闭(保证数据流实时性)、心跳保活(防止空闲断连)、连接数监控(防止内存泄露)。SSE 基于 HTTP 长连接,根本不需要 WebSocket,因此“必须使用网络套接字”是错误的。