大模型流式输出的服务端架构:SSE与增量渲染的工程实践

发布时间:2026/6/8 15:12:07

大模型流式输出的服务端架构:SSE与增量渲染的工程实践 大模型流式输出的服务端架构SSE与增量渲染的工程实践一、等待焦虑大模型推理延迟的用户体验困境大模型推理的端到端延迟通常在数秒到数十秒之间如果采用传统的请求-响应模式用户需要等待整个生成过程完成后才能看到结果。这种黑箱等待体验在对话场景中尤为糟糕——用户不确定系统是否在工作不确定是否需要重新发送请求焦虑感随等待时间指数级增长。流式输出Streaming通过将大模型的生成过程拆分为多个Token增量返回让用户在首个Token生成后即可看到部分结果极大改善了感知延迟。然而流式输出的服务端实现远比一次性返回复杂需要管理长连接的生命周期、处理生成过程中的异常中断、实现增量内容的缓存与断点续传、以及协调多个下游服务的流式响应合并。本文将深入探讨大模型流式输出的服务端架构设计覆盖SSE协议实现、流式编排、背压控制和容错机制四个核心维度。二、流式输出架构设计2.1 端到端流式架构sequenceDiagram participant C as 客户端 participant G as API网关 participant S as 流式服务 participant M as 大模型推理 participant K as 缓存服务 C-G: SSE连接请求 G-S: 建立流式上下文 S-K: 检查缓存 alt 缓存命中 K--S: 返回缓存内容 S--C: 流式推送缓存内容 else 缓存未命中 S-M: 流式推理请求 loop 增量Token M--S: Token片段 S-K: 写入缓存 S--C: SSE推送Token end M--S: 生成完成信号 S--C: 流式结束标记 end2.2 SSE协议实现Server-Sent EventsSSE是流式输出的主流协议选择。相比WebSocketSSE基于HTTP协议实现简单天然支持断线重连和事件ID机制更适合单向流式推送场景。Controller public class StreamingController { private final StreamingService streamingService; GetMapping(value /v1/chat/stream, produces MediaType.TEXT_EVENT_STREAM_VALUE) public SseEmitter streamChat(RequestParam String sessionId, RequestBody ChatRequest request) { // 设置超时时间为5分钟适配长文本生成场景 SseEmitter emitter new SseEmitter(300_000L); // 注册生命周期回调 emitter.onCompletion(() - streamingService.cleanup(sessionId)); emitter.onTimeout(() - streamingService.handleTimeout(sessionId)); emitter.onError(e - streamingService.handleError(sessionId, e)); // 异步执行流式推理 streamingService.streamGenerate(request, sessionId) .subscribe( chunk - { try { emitter.send(SseEmitter.event() .id(chunk.getEventId()) .data(chunk.toJson())); } catch (IOException e) { emitter.completeWithError(e); } }, emitter::completeWithError, () - { try { emitter.send(SseEmitter.event() .name(done) .data([DONE])); emitter.complete(); } catch (IOException e) { emitter.completeWithError(e); } } ); return emitter; } }2.3 流式缓存与断点续传流式输出过程中的网络中断是常见问题。通过流式缓存机制客户端可以在重连后从断点处继续接收内容避免重复生成。Service public class StreamingCacheManager { private final RedisTemplateString, String redisTemplate; private static final Duration CACHE_TTL Duration.ofMinutes(30); /** * 追加写入流式内容到缓存 */ public void appendChunk(String sessionId, int sequenceId, String content) { String key stream: sessionId; // 使用Redis的Hash结构存储field为序号 redisTemplate.opsForHash().put(key, String.valueOf(sequenceId), content); redisTemplate.expire(key, CACHE_TTL); } /** * 获取已缓存的内容用于断点续传 */ public ListCachedChunk getCachedChunks(String sessionId, int fromSequence) { String key stream: sessionId; MapObject, Object entries redisTemplate.opsForHash().entries(key); return entries.entrySet().stream() .filter(e - Integer.parseInt(e.getKey().toString()) fromSequence) .sorted(Comparator.comparingInt(e - Integer.parseInt(e.getKey().toString()))) .map(e - new CachedChunk( Integer.parseInt(e.getKey().toString()), e.getValue().toString())) .collect(Collectors.toList()); } }三、流式编排与背压控制3.1 多模型流式编排在RAG等场景中一次请求可能需要先检索再生成甚至需要多个模型依次处理。流式编排需要协调多个下游服务的流式响应。Service public class StreamingOrchestrator { private final RetrievalService retrievalService; private final GenerationService generationService; public FluxStreamChunk orchestrate(ChatRequest request) { return Flux.create(sink - { // 阶段一检索非流式但需要快速返回 retrievalService.retrieve(request.getQuery()) .subscribe(context - { // 先推送检索状态 sink.next(StreamChunk.status(retrieving)); // 阶段二流式生成 generationService.streamGenerate( request, context) .subscribe( chunk - sink.next( StreamChunk.content(chunk)), sink::error, sink::complete ); }, sink::error); }); } }3.2 背压控制大模型的生成速度可能快于客户端的消费速度如客户端网络带宽受限此时需要背压机制防止服务端缓冲区溢出。Service public class BackpressureAwareStreamer { private static final int MAX_INFLIGHT_CHUNKS 100; public FluxStreamChunk streamWithBackpressure( FluxString modelOutput) { return modelOutput // 限制在途chunk数量超出时暂停从模型读取 .onBackpressureBuffer(MAX_INFLIGHT_CHUNKS, () - { /* 缓冲区满时的降级策略 */ }) .map(StreamChunk::content) // 使用rate limiter控制推送速率 .sample(Duration.ofMillis(50)); } }四、架构权衡与边界分析4.1 SSE与WebSocket的选型SSE基于HTTP实现简单天然支持断线重连但只能服务端向客户端单向推送WebSocket支持双向通信但实现复杂度更高断线重连需要自行实现。对于纯流式输出场景SSE是更优选择如果需要客户端在流式过程中发送控制指令如取消生成、调整参数则应选择WebSocket。4.2 缓存粒度与存储成本流式缓存的粒度越细如按Token缓存断点续传的精度越高但存储成本也越大粒度越粗如按句子缓存存储成本低但断点续传可能丢失部分内容。建议按句子粒度缓存在存储成本和续传精度之间取得平衡。4.3 长连接的资源消耗每个SSE连接都会占用一个服务端线程在使用Servlet时或文件描述符。当并发连接数达到数万时线程和文件描述符的资源消耗会成为瓶颈。建议使用WebFlux等异步非阻塞框架或配置合理的连接超时和最大连接数限制。五、总结大模型流式输出的服务端架构需要在协议选型、缓存续传、流式编排和背压控制四个层面进行精细化设计。SSE协议实现简单且天然支持断线重连流式缓存保障断点续传能力多模型编排协调复杂的流式流程背压控制防止缓冲区溢出。落地建议首先基于SSE协议实现基本的流式输出能力其次增加流式缓存和断点续传提升网络不稳定场景下的用户体验最后在并发量增长后引入背压控制和异步非阻塞框架保障系统在高负载下的稳定性。

相关新闻