AI流式输出技术选型:SSE与WebSocket的深度对比与实践指南 📅 2026/8/26 3:31:29 1. 从一次“卡顿”的AI对话说起流式输出的技术抉择最近在折腾一个AI应用项目前端需要实时展示大模型的“思考”过程也就是流式输出。一开始图省事直接用了WebSocket毕竟“实时通信”听起来就是它的主场。但上线后没多久就遇到了一个让人头疼的问题在用户网络环境稍有不稳或者服务端推理压力大的时候前端偶尔会收不到消息或者消息顺序错乱导致生成的文本前言不搭后语。更麻烦的是有些用户的浏览器或企业防火墙对WebSocket连接并不友好经常出现连接建立失败控制台报错WebSocket connection to ws://... failed或者Error during WebSocket handshake: unexpected response code: 200。这迫使我停下来重新思考对于AI流式输出这种典型的“服务器单向推送”场景我们真的需要WebSocket这种全双工的重型协议吗一个更简单的技术——SSEServer-Sent Events——是不是被我们忽略了这次踩坑经历让我彻底梳理了一遍SSE和WebSocket的差异也让我对“实时通信选型”这个老话题有了新的认识。选型不当轻则影响用户体验重则增加不必要的架构复杂度和运维成本。今天我就结合AI流式输出这个具体场景把SSE和WebSocket掰开揉碎了讲清楚帮你做出最合适的技术决策。2. 核心概念辨析SSE与WebSocket的本质差异在深入选型之前我们必须抛开“都是用来推数据的”这种模糊印象从协议底层理解它们的根本不同。这决定了它们各自的能力边界和适用场景。2.1 SSE专注且优雅的服务器推送SSE的全称是Server-Sent Events顾名思义它就是一种允许服务器主动向客户端发送事件的技术。它的本质非常纯粹基于HTTP/HTTPSSSE完全构建在普通的HTTP协议之上。客户端通过发送一个标准的HTTP GET请求来建立连接并在请求头中携带Accept: text/event-stream。服务器则以一个特殊的Content-Type: text/event-stream响应并保持这个HTTP连接长开不闭。单向通信这是SSE最核心的特征。连接建立后数据流只能从服务器流向客户端。客户端无法通过这个连接向服务器发送数据。如果客户端需要上传数据比如发送新的用户提问它需要发起另一个独立的HTTP请求如Fetch API。文本协议SSE传输的数据格式是纯文本遵循简单的规范。每条消息由若干行组成以两个换行符\n\n分隔。最基本的格式是data: message content\n\n。它还支持定义事件类型event:、重连时间retry:和消息IDid:等字段协议简单易懂。自动重连SSE协议内建了重连机制。如果连接意外断开浏览器会自动尝试重新连接并可以通过上次收到的消息ID (Last-Event-ID请求头) 请求服务器重发遗漏的消息这对保证消息的可靠性很有帮助。一个典型的SSE服务器响应流看起来是这样的HTTP/1.1 200 OK Content-Type: text/event-stream Cache-Control: no-cache Connection: keep-alive data: 这是第一条消息\n\n event: update data: {status: processing}\n\n id: 42 data: 这是带ID的消息\n\n2.2 WebSocket强大而灵活的全双工通道WebSocket则是一个完全不同的协议。它在初次握手时使用HTTP但握手成功后协议便升级为一个独立的、基于TCP的双向通信协议。独立的双向协议WebSocket握手成功后状态码101后续的通信便脱离了HTTP的范畴在一个独立的TCP通道上进行。这个通道是全双工的服务器和客户端可以随时、任意地向对方发送数据没有请求-响应的概念。二进制与文本皆可WebSocket协议帧原生支持传输二进制数据Blob, ArrayBuffer和文本数据String这使得它非常适合传输图片、音频、文件等非文本内容。更低的协议开销相比HTTP每次请求都携带完整的头部WebSocket在建立连接后每个数据帧的头部开销非常小最低仅2字节在需要高频、小数据量通信的场景下效率优势明显。无自动重连WebSocket协议本身没有内建的重连和消息追溯机制。连接断开后需要客户端自己实现重连逻辑并且通常需要应用层自己处理消息确认、重发等可靠性问题。为了更直观地对比我们可以看下面这个表格特性维度SSE (Server-Sent Events)WebSocket通信模式单向(仅服务器 - 客户端)全双工(服务器 - 客户端)协议基础HTTP/HTTPS (长连接)独立的协议 (基于TCPHTTP仅用于握手)数据格式仅文本 (UTF-8)文本与二进制皆可浏览器支持除IE/Edge Legacy外现代浏览器广泛支持广泛支持 (包括较老版本)自动重连内置支持需手动实现消息追溯通过Last-Event-ID支持需应用层协议实现协议开销每次消息仍携带少量HTTP头连接建立后帧头开销极小开发复杂度非常简单使用标准HTTP API相对复杂需处理连接状态、心跳、重连典型场景实时通知、股票行情、新闻推送、AI流式输出在线聊天、协同编辑、实时游戏、多媒体通信注意上表中“浏览器支持”一项SSE在IE和旧版Edge中不被支持但这在当今的客户端环境下已非主要障碍。对于必须兼容这些浏览器的场景可以使用Polyfill库如eventsource来模拟SSE功能。3. AI流式输出场景的深度剖析为什么SSE往往是更优解现在让我们把焦点放回AI流式输出。这个过程通常是用户输入问题 - 服务器调用大模型API - 模型开始生成Token词元- 服务器将这些Token逐个或分批推送给前端 - 前端实时渲染。让我们分析这个场景下的核心需求数据流向绝对的单向主导。99%的数据流是从服务器流向客户端的Token流。客户端仅在会话开始时发送一次查询请求。数据格式纯文本。大模型输出的就是文本或JSON格式的文本完全不需要二进制传输能力。连接需求需要长连接来持续接收数据但连接的生命周期通常与一次问答会话绑定结束后即可断开。可靠性要求中等偏高。希望尽可能不丢失Token但偶尔丢失一两个可能不影响整体理解取决于应用。自动重连和断点续传是加分项。复杂度控制作为应用的一个功能点希望实现和维护成本尽可能低。将SSE的特性与上述需求逐一匹配你会发现契合度惊人地高单向性匹配SSE的单向推送特性完美匹配了“服务器推Token”这个核心动作。客户端用独立的HTTP请求发送问题逻辑清晰符合RESTful思维。文本协议匹配SSE的文本协议原生支持传输Token流无需任何编解码转换。开发简易性前端使用标准的EventSourceAPI几行代码即可搞定。后端以Spring Boot为例只需返回一个SseEmitter对象并定期发送数据无需处理复杂的连接池、心跳协议。内置可靠性SSE的自动重连和Last-Event-ID机制为网络波动提供了“免费”的容错能力。即使连接短暂中断恢复后也能尝试获取错过的内容。HTTP友好SSE就是一个长HTTP连接能无缝融入现有的HTTP基础设施。监控、日志、负载均衡需要支持长连接、身份认证如Spring Security拦截器都可以沿用现有方案大大降低了架构和运维的复杂性。这也是为什么在yudao-cloud这类项目中处理SSE与权限控制的集成比WebSocket要直观得多。反观WebSocket它在这个场景下则显得有些“杀鸡用牛刀”能力过剩我们并不需要双向通信却引入了全双工的复杂度。额外负担需要自己实现心跳保活、连接状态管理、重连逻辑。在Spring Boot中集成WebSocket无论是用ServerEndpoint还是STOMP的配置量远大于SSE。基础设施适配一些传统的HTTP代理、防火墙或企业网络策略可能对WebSocket支持不完善导致连接问题而SSE则很少遇到这类麻烦。我个人的实操心得是在第一个项目踩坑后我将AI输出模块从WebSocket迁移到了SSE。前后端代码量减少了约三分之一之前偶发的连接问题和消息错乱问题基本消失。前端同事也反馈EventSource的API比管理WebSocket连接状态要省心太多。这让我深刻体会到在技术选型上“最适合的”远比“最强大的”重要。4. WebSocket的用武之地何时该选择它那么WebSocket是不是在AI应用中就一无是处了呢当然不是。它的价值体现在需要真正双向、高频、低延迟交互的场景中。除了经典的在线聊天、协同文档、实时游戏外在AI应用领域以下场景WebSocket可能更合适AI智能体AI Agent的持续交互如果一个AI智能体需要长时间运行并主动观察环境、接收异步事件、持续执行任务那么客户端与智能体之间可能需要一个随时可以收发指令和状态的双向通道。这时WebSocket的全双工特性就派上了用场。沉浸式AI对话与角色扮演在需要极低延迟、且对话中穿插大量客户端事件如打断、切换话题、发送表情或动作指令的沉浸式场景中WebSocket的双向能力能提供更流畅的体验。结合其他二进制流如果AI应用不仅输出文本还需要实时混合输出或接收音频流、视频流如AI语音对话、实时视频生成预览那么WebSocket对二进制数据的原生支持将是关键优势。选型的关键判断点问自己一个问题“在我的应用里服务器需要主动、频繁、且低延迟地向客户端推送数据的同时客户端是否也需要以相当高的频率主动向服务器发送数据” 如果答案是“否”或者客户端的发送只是偶尔的请求如下一个新问题那么SSE几乎总是更好的选择。如果答案是“是”那么WebSocket才应该进入你的考虑范围。5. 实战指南Spring Boot中实现SSE流式输出理论说完了我们来点实际的。以下是在Spring Boot中为一个AI问答接口实现SSE流式输出的核心步骤和代码示例。你会发现这比配置WebSocket简单得多。5.1 后端控制器实现首先我们需要一个控制器来创建SSE连接端点并发送流式数据。import org.springframework.http.MediaType; import org.springframework.web.bind.annotation.*; import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; import java.io.IOException; import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; RestController RequestMapping(/api/ai) public class AIChatController { // 建议使用线程池来异步处理耗时的AI推理任务避免阻塞SSE连接线程 private final ExecutorService executor Executors.newCachedThreadPool(); GetMapping(value /stream-chat, produces MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter streamChat(RequestParam String question) { // 创建一个SseEmitter可以设置超时时间例如30分钟 SseEmitter emitter new SseEmitter(30 * 60 * 1000L); // 提交任务到线程池执行 executor.execute(() - { try { // 1. 模拟或实际调用AI大模型服务这里假设有一个流式生成器 StreamResponseGenerator generator callAIService(question); String token; while ((token generator.getNextToken()) ! null) { // 2. 将每个Token通过SseEmitter发送出去 // SseEmitter.event() 构建事件data()方法会自动格式化为 data: content\n\n emitter.send(SseEmitter.event() .data(token) // 核心发送方法 // .id(someId) // 可选设置消息ID用于重连 // .name(message) // 可选定义事件类型 ); // 模拟模型生成速度 Thread.sleep(50); } // 3. 流式输出完成发送一个特殊事件或直接结束 emitter.send(SseEmitter.event().data([DONE])); emitter.complete(); // 标记发送完成关闭连接 } catch (IOException e) { // 发送过程中发生IO异常通常是客户端断开连接 emitter.completeWithError(e); } catch (InterruptedException e) { Thread.currentThread().interrupt(); emitter.completeWithError(e); } catch (Exception e) { // 处理其他业务异常 emitter.completeWithError(e); } }); // 4. 设置连接结束时的回调用于资源清理 emitter.onCompletion(() - System.out.println(SSE连接完成)); emitter.onTimeout(() - System.out.println(SSE连接超时)); emitter.onError((ex) - System.out.println(SSE连接错误: ex.getMessage())); return emitter; } // 模拟的AI流式响应生成器 private StreamResponseGenerator callAIService(String question) { return new StreamResponseGenerator(question); } static class StreamResponseGenerator { private final String question; private int index 0; private final String[] simulatedTokens {思考, 你的, 问题, , 答案, 是, ...}; StreamResponseGenerator(String question) { this.question question; } String getNextToken() { if (index simulatedTokens.length) { return simulatedTokens[index]; } return null; } } }关键点解析produces MediaType.TEXT_EVENT_STREAM_VALUE这个注解至关重要它确保响应的Content-Type头是text/event-stream浏览器才会将其识别为SSE流。SseEmitter这是Spring提供的用于发送SSE事件的工具类。创建时可以设置超时时间防止连接永远挂起。异步处理AI模型推理通常是耗时的。绝对不要在控制器线程中同步执行这会导致连接线程被阻塞。务必使用线程池如示例或其他异步机制如Reactive编程的Flux来执行推理和发送逻辑。发送数据emitter.send(SseEmitter.event().data(content))是核心。Spring会帮我们将数据格式化为正确的SSE格式。资源清理通过onCompletion、onTimeout、onError回调来释放与连接关联的资源如中断AI模型调用。5.2 前端使用EventSource接收前端实现简单得令人愉悦// 假设用户的问题存储在变量 userQuestion 中 const eventSource new EventSource(/api/ai/stream-chat?question${encodeURIComponent(userQuestion)}); let accumulatedText ; // 监听默认的 message 事件对应服务器发送的未命名事件 eventSource.onmessage (event) { const token event.data; if (token [DONE]) { console.log(流式输出结束); eventSource.close(); // 关闭连接 return; } accumulatedText token; // 更新UI例如将 accumulatedText 设置到某个div或textarea中 document.getElementById(output).innerText accumulatedText; }; // 监听自定义事件如果服务器发送了命名事件 eventSource.addEventListener(update, (event) { console.log(收到更新事件:, event.data); }); // 错误处理 eventSource.onerror (error) { console.error(EventSource failed:, error); // 可以根据错误类型尝试重连但SSE通常会自动重连 eventSource.close(); };前端注意事项EventSource会自动处理连接建立、重连和消息解析。你只需要订阅事件。默认监听onmessage来接收服务器未指定事件类型的消息。如果连接出错onerror会被触发。注意EventSource在连接断开后会尝试自动重连并携带上一次收到的Last-Event-ID。当会话结束时如收到[DONE]信号主动调用eventSource.close()是一个好习惯。5.3 与Spring Security等权限框架的集成这是SSE相比WebSocket的另一大优势。由于SSE基于HTTP它可以被标准的HTTP过滤器链包括Spring Security的过滤器轻松拦截。这意味着你可以像保护普通API一样保护你的SSE端点。Configuration EnableWebSecurity public class SecurityConfig extends WebSecurityConfigurerAdapter { Override protected void configure(HttpSecurity http) throws Exception { http .authorizeRequests() .antMatchers(/api/ai/stream-chat).authenticated() // 要求认证才能访问SSE端点 .and() .formLogin() .and() .httpBasic(); // 注意通常不需要为SSE端点单独禁用CSRF因为它使用GET方法且是单向的。 // 但根据你的安全策略可能需要考虑。 } }用户访问/api/ai/stream-chat时会先经过Spring Security的认证流程。认证成功后连接才得以建立。这种方式非常直观管理起来和普通的REST API没有区别。相比之下WebSocket的握手虽然也是HTTP但握手升级后的连接就脱离了HTTP过滤器链需要更复杂的配置如Authorization头传递、SimpUserRegistry等来实现权限控制这也是在yudao-cloud等项目中集成时的一个常见痛点。6. 进阶考量与常见问题排查即使选择了SSE在实际部署和运行中仍然会遇到一些挑战。这里分享几个关键点的处理经验。6.1 连接管理与资源释放SSE连接是长连接不当管理会导致服务器资源如线程、内存泄露。设置合理超时创建SseEmitter时务必设置超时时间如30分钟。这能防止因客户端异常退出而导致的“僵尸连接”。主动完成与清理在流式输出正常结束或发生错误时务必调用emitter.complete()或emitter.completeWithError()。并在onCompletion回调中执行必要的资源清理操作比如中断正在进行的AI模型调用、释放数据库连接等。使用连接管理器对于大规模应用可以考虑维护一个SseEmitter的注册表例如以用户ID为Key在用户断开连接或会话结束时能从注册表中找到并清理对应的Emitter。6.2 负载均衡与代理配置当你的应用部署在多台服务器后面使用Nginx、HAProxy等负载均衡器时需要确保它们支持并正确配置了HTTP长连接。Nginx配置示例location /api/ai/stream-chat { proxy_pass http://backend_server; proxy_set_header Connection ; proxy_http_version 1.1; # 必须使用HTTP/1.1 proxy_buffering off; # **关键** 必须关闭代理缓冲否则数据会堆积而不是流式传输 proxy_cache off; # 关闭缓存 chunked_transfer_encoding off; # 对于SSE通常需要关闭分块传输编码 proxy_read_timeout 1800s; # 设置一个长的读超时时间 }proxy_buffering off;这一行至关重要。如果Nginx开启了缓冲它会尝试接收完整个后端响应再转发给客户端这就完全破坏了“流式”的效果客户端会一直等到所有数据都生成完毕才一次性收到。6.3 前端兼容性与降级方案虽然现代浏览器普遍支持EventSource但仍需考虑兼容性。Polyfill对于不支持的环境如旧版IE可以使用eventsource这个npm包作为polyfill。它用XMLHttpRequest或fetchAPI模拟了SSE的行为。降级方案作为最坏的打算可以实现一个降级逻辑。先尝试创建EventSource如果失败则回退到使用轮询Polling或长轮询Long Polling来模拟实时效果当然体验会大打折扣。6.4 常见错误排查前端收不到数据检查浏览器开发者工具的“网络”(Network)选项卡找到SSE请求查看响应头是否包含Content-Type: text/event-stream。检查服务器端逻辑确保emitter.send()被正确执行并且没有在发送前就关闭了连接或发生了未捕获的异常。检查负载均衡器和代理的配置确认proxy_buffering已关闭。连接意外断开检查服务器日志看是否有超时 (onTimeout) 或错误 (onError) 发生。检查服务器和客户端的防火墙、代理设置是否有关闭空闲长连接的策略。考虑在客户端实现一个简单的ping-pong机制通过SSE发送特殊事件来保持连接活跃尽管SSE协议本身有自动重连。与Spring Security集成失败确保你的SSE端点URL在安全配置中已被正确覆盖并且认证所需的Cookie或Token能够被前端在发起EventSource请求时自动携带同源情况下会自动携带。7. 总结回归场景审慎选型回顾开头的那个问题“SSE还是WebSocket”答案现在已经很清晰了。对于AI流式输出这个具体场景SSE在绝大多数情况下是更简单、更高效、更可靠的选择。它利用HTTP的普及性以极低的开发和运维成本完美地满足了服务器向客户端单向、持续推送文本数据的需求。技术选型没有银弹。WebSocket无疑功能更强大但它带来的复杂性也是实实在在的。在做决定前不妨再问自己几个问题我的数据流主要是单向还是双向我传输的数据主要是文本还是二进制我的团队对哪种技术的熟悉度更高我的生产环境网络、代理、防火墙对哪种协议更友好在这次的AI项目重构中从WebSocket切换到SSE不仅解决了具体的连接稳定性问题更让整个技术栈的某一部分回归了简洁与专注。这种“用合适工具做合适事”的体验本身就是一种技术上的愉悦。下次当你面临实时通信选型时不妨先想想SSE这个简单却常被低估的技术或许能给你带来惊喜。