共计 2714 个字符,预计需要花费 7 分钟才能阅读完成。
为什么流式渲染对 LLM 应用至关重要
在大型语言模型 (LLM) 应用中,传统的一次性响应方式会让用户等待完整的文本生成后才能看到结果。流式渲染通过分块传输实现逐字输出,带来两个核心优势:

- 降低感知延迟 :首字符到达时间(TTFB) 可控制在 200ms 内,即使总生成时间相同,用户感觉响应更快
- 提升交互体验:实时看到生成过程符合人类对话习惯,并能及时中断错误输出
技术选型:SSE vs WebSockets
协议层差异
| 特性 | SSE | WebSockets |
|---|---|---|
| 协议基础 | HTTP 长连接 | 独立双向协议 |
| 数据格式 | text/event-stream | 二进制帧 |
| 消息方向 | 服务端→客户端单工 | 全双工通信 |
| 自动重连 | 原生支持 | 需手动实现 |
浏览器兼容性
- SSE:除 IE/Edge Legacy 外全支持(CanIUse 数据 97% 覆盖率)
- WebSockets:IE10+ 全支持,但移动端可能受代理限制
服务端资源测试(4 核 8G 云主机)
// 测试条件:1000 并发连接持续 10 分钟
interface StressResult {
protocol: 'SSE' | 'WS';
memoryUsage: number; // MB
cpuLoad: number; // %
}
const results: StressResult[] = [{ protocol: 'SSE', memoryUsage: 420, cpuLoad: 65},
{protocol: 'WS', memoryUsage: 580, cpuLoad: 72}
];
Node.js+Express 实现方案
服务端 SSE 实现
import express from 'express';
import {createReadStream} from 'fs';
type ChunkData = {
id?: string;
event?: string;
data: string;
};
const app = express();
app.get('/stream', (req, res) => {res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
res.flushHeaders(); // 立即发送 headers
// 模拟 LLM 分块生成
const mockGenerator = createReadStream('llm-output.txt');
mockGenerator.on('data', (chunk: Buffer) => {
const payload: ChunkData = {data: chunk.toString()
};
res.write(`data: ${JSON.stringify(payload)}\n\n`);
});
req.on('close', () => {mockGenerator.destroy();
res.end();});
});
客户端实现
class SSEClient {
private eventSource: EventSource | null = null;
private retryDelay = 1000;
constructor(private url: string) {this.connect();
}
private connect() {this.eventSource = new EventSource(this.url);
this.eventSource.onmessage = (event) => {const data: ChunkData = JSON.parse(event.data);
// 渲染到 DOM
};
this.eventSource.onerror = () => {this.eventSource?.close();
setTimeout(() => this.connect(), this.retryDelay);
this.retryDelay = Math.min(this.retryDelay * 2, 5000);
};
}
}
性能优化策略
TCP 层优化
- 分块大小调整:根据 RTT 动态调整 chunk 大小(建议 1 -4KB)
- Nagle 算法禁用:设置 TCP_NODELAY 避免缓冲延迟
# Node.js 中设置
socket.setNoDelay(true);
背压处理
// 服务端背压感知
let highWaterMark = false;
res.on('drain', () => {
highWaterMark = false;
resumeGeneration();});
function sendChunk(chunk: string) {if (!highWaterMark) {const buffered = res.write(`data: ${chunk}\n\n`);
if (!buffered) {
highWaterMark = true;
pauseGeneration();}
}
}
客户端节流
let renderQueue: string[] = [];
let isRendering = false;
function scheduleRender() {if (!isRendering && renderQueue.length) {
isRendering = true;
requestAnimationFrame(() => {updateDOM(renderQueue.shift()!);
isRendering = false;
scheduleRender();});
}
}
避坑指南
Nginx 配置关键项
location /stream {
proxy_pass http://backend;
proxy_buffering off; # 禁用缓冲
proxy_cache off; # 禁用缓存
proxy_read_timeout 24h; # 保持长连接
}
会话保持方案
- Cookie+Session:适用于同域名场景
- JWT 鉴权:每次连接携带 token
- ConnectionID:服务端生成唯一 ID 标识会话
移动端网络切换
// 监听网络变化
window.addEventListener('online', () => {if (!sseClient.isConnected) {sseClient.reconnect();
}
});
未来演进方向
- 长上下文优化:
- 采用增量编码(如 delta encoding)
-
关键信息优先传输
-
WebTransport 潜力:
- 基于 QUIC 协议解决队头阻塞
- 多流复用降低连接开销
- 当前浏览器支持率约 78%
通过合理选择协议和优化实现,流式渲染能为 LLM 应用带来质的体验提升。建议新项目优先采用 SSE 方案,在需要双向通信时再考虑 WebSockets。
正文完
发表至: 技术分享
近两天内
