很多人第一次遇到「接口执行很久、前端一直转圈、最后网关直接超时」这种问题时,通常的第一反应都是:加线程池、加缓存、改 SQL。但有些场景本质不是慢,而是需要持续输出,比如:
- 文件实时生成并回传
- AI 大模型流式回答
- 长时间任务进度推送
- 日志/监控连续输出
这类需求要是硬做成普通 HTTP 同步接口,超时是迟早的事。 好消息是:Spring 自带几套流式输出方式,能让接口不断推送内容,不占线程、不怕长耗时。
下面我用一个常见场景来举例:\
模拟一个"持续生成数据并实时返回给前端"的接口,三种方式逐个实现。
① 使用 ResponseBodyEmitter:最轻量、最好上手、兼容性好
如果你只是想让接口边算边返回内容,ResponseBodyEmitter 是最简单的。
它的特点是:
- 支持分段写出
- 不阻塞 servlet 容器线程
- 前端收到的内容会一段段实时流入
代码示例:
bash
@GetMapping("/stream/emitter")
public ResponseBodyEmitter emitter() {
ResponseBodyEmitter emitter = new ResponseBodyEmitter();
Executors.newSingleThreadExecutor().submit(() -> {
try {
for (int i = 1; i <= 5; i++) {
emitter.send("chunk-" + i + "\n");
Thread.sleep(1000);
}
emitter.complete();
} catch (Exception e) {
emitter.completeWithError(e);
}
});
return emitter;
}
拿这个接口用 curl 测试,你会看到每秒打印一条,不会等五秒后一次性返回。
适用场景
- 轻量级数据流
- 需要简单分片输出
- 内网 API、工具接口
缺点
- 大量并发下对容器线程还是有压力
- 控制能力不如 SSE
② Server-Sent Events(SSE):浏览器天然支持、适合实时推送
SSE 是浏览器原生支持的"后端到前端的单向实时通道",比 WebSocket 简单多了。SpringBoot 直接用 SseEmitter 就能输出 SSE 标准格式。
示例代码:
bash
@GetMapping("/stream/sse")
public SseEmitter sse() {
SseEmitter emitter = new SseEmitter(0L); // 不超时
Executors.newSingleThreadExecutor().submit(() -> {
try {
for (int i = 1; i <= 5; i++) {
emitter.send(SseEmitter.event()
.id(String.valueOf(i))
.data("msg-" + i));
Thread.sleep(1000);
}
emitter.complete();
} catch (Exception e) {
emitter.completeWithError(e);
}
});
return emitter;
}
前端只需要这样接收:
bash
const es = new EventSource('/stream/sse');
es.onmessage = e => console.log(e.data);
优点
- 浏览器天然支持
- 自动断线重连
- 语义明确、协议轻量
缺点
- 单向(只能后端→前端)
- 对网关(如 nginx)需要配置禁用缓冲
③ Spring WebFlux:真正的异步、非阻塞、适合高并发
如果你需要同时满足:
- 高并发大量
- 长连接持续
- 流式输出
那 WebFlux 才是最稳的解决方案。
使用 Flux 就能天然实现流输出:
bash
@GetMapping(value = "/stream/flux", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> fluxStream() {
return Flux.interval(Duration.ofSeconds(1))
.map(i -> "msg-" + i);
}
特点就是:
- Reactor 驱动,完全异步非阻塞
- 连接数大量增长也能扛
- 天生适合流式接口
适用场景
- AI 对话类接口
- 海量实时消息订阅
- 高频监控推送
缺点
- 需要上手 WebFlux 编程模型
- 与传统 Spring MVC 混用时需明确路由边界
到底什么时候用哪种?
可以简单这样选:
| 需求 | 推荐方式 |
|---|---|
| 小量流式输出 | ResponseBodyEmitter |
| 前端需要实时显示、中轻度并发 | SSE(SseEmitter) |
| 海量长连接、AI/日志推流 | WebFlux(Flux) |
实战经验:避免被网关干掉
很多人在本地测试都好好的,上线后却发现还是超时。 通常不是 Spring 的问题,而是网关缓冲机制导致。
如果你用 SSE 或分段输出,需要在 nginx 加:
bash
proxy_buffering off;
proxy_cache off;
否则 nginx 会把你辛辛苦苦的流式内容全部吞掉,凑够缓冲后一次性返回,你的"流"就没了。
"接口超时"有时候不是接口慢,而是它本来就不是"同步"该干的事情。 Spring 给了我们三种流式返回方式,只要用对,不但不卡、不断连,还能在体验上秒杀常规接口。