LogoSiWei's Blog

单向推送就够:为什么 AI 对话要用 SseEmitter 而不是 WebSocket

PSW 2026-08-04 104 阅读 6 分钟

为什么 AI 对话的流式响应要用 SseEmitter

做 AI 对话接口的人,大概率都纠结过一个问题:大模型返回内容要等好久,是一次性等它说完再吐给你,还是一边生成一边推给你?

答案是后者。用过 ChatGPT 的都知道,AI 回答是逐字蹦出来的,不是加载半天突然蹦一整段。这个体验背后,靠的就是流式响应(Streaming)。而在 Spring 生态里,实现它最顺手的方式就是 SseEmitter

先看不用它是什么样

如果你用 Spring 写一个普通的接口,返回一个 String,前端会经历什么?

  1. 前端发请求,然后干等。
  2. 后端调大模型,等它把整段回答生成完(可能十几秒)。
  3. 生成完,一次性把整个字符串返回。
  4. 前端这才看到内容。

问题很明显:用户盯着一个转圈,十几秒一片空白。本质是"非流式"——数据攒齐了才发。

流式响应解决什么

流式响应是让后端一边拿大模型的输出,一边往连接里写,前端收到一点渲染一点。用户看到的是文字一行行/一字字冒出来,等待感大幅下降,甚至让人觉得"AI 边想边说",体验完全不一样。

而 SSE(Server-Sent Events)就是做这件事的协议:一条普通的 HTTP 连接保持不关,服务器不断往里面写数据,客户端逐块接收。Spring 把它的封装体叫做 SseEmitter

为什么是它,而不是别的方案

SseEmitter 之前,你有三个选项,对比一下就清楚了:

1. 轮询。 前端隔几秒问一次"生成了没"。落后、浪费请求,而且你做不出"逐字输出"的效果,只能拿"整段"或"半段"。

2. WebSocket。 强大,但重。它主打双向通信,而 AI 对话只需要服务器单方向往下推,客户端几乎不说话。为单向需求上双向武器,配置和心智负担都白费。

3. SseEmitter。 单向推送,基于普通 HTTP,天然支持断线重连,走代理负载均衡都顺。AI 对话恰恰就是"服务器单向推、客户端只等"的典型场景。它够用,而且刚好够用。

代码长什么样

核心就一个:接口返回 SseEmitter,在拿到大模型输出的回调里不断 send,最后 complete

@GetMapping("/chat")
public SseEmitter chat(@RequestParam String prompt) {
    SseEmitter emitter = new SseEmitter();

    // 模拟大模型逐字返回
    new Thread(() -> {
        try {
            String reply = "你好,我是 AI";
            for (char c : reply.toCharArray()) {
                emitter.send(String.valueOf(c));
                Thread.sleep(100);
            }
            emitter.complete(); // 流式结束
        } catch (Exception e) {
            emitter.completeWithError(e); // 出错也要收尾
        }
    }).start();

    return emitter;
}

前端用浏览器自带的 EventSource 就能接,不用引任何库:

const es = new EventSource(`/chat?prompt=你好`);
es.onmessage = (event) => {
    console.log(event.data); // 每收到一块就渲染一块
};

前端收到一块渲染一块,这就是"逐字输出"的真相。

两个必须注意的坑

1. 超时。 new SseEmitter() 默认 30 秒超时。大模型生成慢,很容易超时断连。长对话建议设长一点,或设 0 表示不过期:

SseEmitter emitter = new SseEmitter(0L); // 不超时

2. 代理缓冲。 开发环境好好的,线上却不推了,大概率是 Nginx 默认缓冲把流式数据攒住不往下发。记得关掉:

location /chat {
    proxy_buffering off;
    proxy_cache off;
    proxy_read_timeout 3600s;
}

一句话总结

AI 对话要"边生成边返回",流式是刚需;而单向推送恰好是 SSE 的主场,Spring 又把它封装成了 SseEmitter——比轮询实时,比 WebSocket 轻量,用一个普通 HTTP 接口就搞定了。

目录

评论

© 2026 SiWei's Blog. All rights reserved.