1. 为什么需要流式响应中继想象一下你正在使用一个AI聊天应用输入问题后盯着屏幕干等十几秒才看到完整回复这种体验有多糟糕。大模型生成文本时就像挤牙膏——它是一点一点吐出来的传统接口却要等全部内容生成完毕才一次性返回。这就是我们要用SSEServer-Sent Events技术配合OkHttp实现流式传输的根本原因。我在实际项目中遇到过典型场景当用户询问请用300字介绍量子计算时大模型需要约8秒生成完整回答。如果采用传统HTTP请求用户会看到长达8秒的白屏而改用流式传输后用户几乎立即就能看到首个字符后续内容像打字机一样实时呈现。这种边生成边展示的体验提升是质的飞跃。技术选型上OkHttp-SSE组合有三大优势资源消耗低相比WebSocket双工通信SSE是服务端单向推送节省50%以上的连接开销兼容性好所有现代浏览器都原生支持SSE移动端也无须额外SDK断线恢复内置的event ID机制能实现断网自动续传2. 搭建SSE中继的核心架构2.1 整体数据流向用户提问 → 前端发起SSE连接 → 后端中继服务 → 大模型API → 逐块返回token → 中继实时转发 → 前端持续渲染这个过程中最关键的中继层要解决三个问题如何保持与大模型的稳定长连接如何高效处理分块响应如何管理可能出现的连接中断2.2 核心组件拆解// 典型的中继服务结构 src/ ├── main/ │ ├── java/ │ │ └── com/ │ │ └── demo/ │ │ ├── controller/ # 接口入口 │ │ ├── listener/ # SSE事件处理器 │ │ └── utils/ # 工具类 │ └── resources/ └── test/重点看SSEListener这个核心类它继承自OkHttp的EventSourceListener需要重写四个关键方法onOpen()连接建立时初始化响应头onEvent()处理每个数据块最重要onClosed()清理连接资源onFailure()异常处理和重连机制3. 手把手实现中继服务3.1 环境准备首先在pom.xml添加必要依赖dependency groupIdcom.squareup.okhttp3/groupId artifactIdokhttp/artifactId version4.10.0/version /dependency dependency groupIdcom.squareup.okhttp3/groupId artifactIdokhttp-sse/artifactId version4.10.0/version /dependency建议使用OkHttp 4.x版本这个系列对SSE的支持最完善。我在4.2.0版本踩过一个坑当大模型响应间隔超过30秒时会自动断开连接升级到4.10.0后问题解决。3.2 编写Controller层PostMapping(value /chat, produces text/event-stream) public void chatStream(RequestBody ChatRequest request, HttpServletResponse response) { response.setContentType(text/event-stream); response.setCharacterEncoding(UTF-8); // 创建自定义监听器 SSEListener listener new SSEListener(response); // 构建大模型API请求 RequestBody body RequestBody.create( JSON.toJSONString(request), MediaType.get(application/json) ); Request apiRequest new Request.Builder() .url(https://api.ai-provider.com/v1/chat) .post(body) .build(); // 发起SSE连接 EventSources.createFactory(new OkHttpClient()) .newEventSource(apiRequest, listener); }注意几个关键点必须设置produces text/event-stream不要手动关闭HttpServletResponse的输出流响应头必须包含Content-Type和编码声明3.3 实现事件监听器public class SSEListener extends EventSourceListener { private final HttpServletResponse response; private final AtomicBoolean isFirst new AtomicBoolean(true); Override public void onEvent(EventSource source, String id, String type, String data) { try { // 处理特殊控制指令 if ([DONE].equals(data)) { response.getWriter().write(event: done\n); response.getWriter().write(data: \n\n); return; } // 解析大模型返回的JSON JsonNode chunk objectMapper.readTree(data); String token chunk.get(choices).get(0) .get(delta).get(content).asText(); // SSE协议格式要求 if (isFirst.compareAndSet(true, false)) { response.getWriter().write(event: start\n); response.getWriter().write(data: \n\n); } response.getWriter().write(data: JsonUtils.escape(token) \n\n); response.getWriter().flush(); } catch (Exception e) { // 错误处理逻辑 } } }这里有个实际开发中的经验大模型返回的JSON结构各不相同需要根据具体API调整解析逻辑。比如OpenAI和Claude的响应格式就完全不同。4. 性能优化实战技巧4.1 连接池配置public class OkHttpSingleton { private static final OkHttpClient client new OkHttpClient.Builder() .connectionPool(new ConnectionPool(20, 5, TimeUnit.MINUTES)) .connectTimeout(30, TimeUnit.SECONDS) .readTimeout(300, TimeUnit.SECONDS) // 长轮询超时设置 .retryOnConnectionFailure(true) .build(); }建议参数最大空闲连接数 并发用户数 × 1.5keepAlive时间 ≥ 5分钟读超时 ≥ 大模型平均响应时间 × 24.2 背压处理当客户端处理速度跟不上服务端推送时需要实现背压控制。我在项目中采用令牌桶算法class RateLimiter { private final Semaphore semaphore new Semaphore(100); void acquire() throws InterruptedException { semaphore.acquire(); } void release() { semaphore.release(); } } // 在onEvent中使用 rateLimiter.acquire(); try { // 处理事件 } finally { rateLimiter.release(); }4.3 断线重连策略Override public void onFailure(EventSource source, Throwable t, Response resp) { if (t instanceof InterruptedIOException) { // 用户主动取消不重试 return; } // 指数退避重试 long delay Math.min(1000L * (1 retryCount), 30000L); scheduler.schedule(() - { source.reconnect(); retryCount; }, delay, TimeUnit.MILLISECONDS); }5. 常见问题排查指南5.1 连接立即断开现象前端显示连接建立后马上断开 排查步骤检查服务端是否忘记设置Content-Type用Wireshark抓包查看TCP握手过程验证Nginx配置是否有proxy_buffering off5.2 数据堆积不刷新现象前端收到多个data事件但未实时渲染 解决方案// 前端需要手动触发渲染 const eventSource new EventSource(/chat); eventSource.onmessage (e) { if (e.data ) return; requestAnimationFrame(() { outputEl.textContent e.data; }); };5.3 内存泄漏重点检查OkHttpClient实例是否重复创建响应输出流是否未被正确关闭监听器中是否持有大对象引用可以用以下JVM参数启动服务监控-XX:HeapDumpOnOutOfMemoryError -XX:HeapDumpPath/tmp/heapdump.hprof