Spring 实现 3 种异步流式接口,干掉接口超时烦恼

很多人第一次遇到「接口执行很久、前端一直转圈、最后网关直接超时」这种问题时,通常的第一反应都是:加线程池、加缓存、改 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 给了我们三种流式返回方式,只要用对,不但不卡、不断连,还能在体验上秒杀常规接口。

相关推荐
专注VB编程开发20年9 分钟前
c#Type数组转成字符串的名称
java·开发语言
中年程序员一枚13 分钟前
多数据源的springboot进行动态连接方案
java·spring boot·后端
w***765513 分钟前
SpringBoot集成MQTT客户端
java·spring boot·后端
HABuo20 分钟前
【Linux进程(五)】进程地址空间深入剖析-->虚拟地址、物理地址、逻辑地址的区分
linux·运维·服务器·c语言·c++·后端·centos
编程饭碗20 分钟前
【多线程编程】
java·开发语言
北鹿不麋鹿31 分钟前
自学Java手记:Map集合,Arrays工具类和Lambda表达式
java
码头整点薯条34 分钟前
对接第三方服务踩坑:属性大小写不匹配导致数据解析失败,一个注解搞定!
java
Wpa.wk35 分钟前
性能测试工具 - JMeter工具组件介绍一
java·经验分享·测试工具·jmeter·性能测试
虫小宝36 分钟前
个微iPad协议场景下Java后端的协议解析异常排查与问题定位技巧
java·svn·ipad
程序媛徐师姐42 分钟前
Java基于微信小程序的鲜花销售系统,附源码+文档说明
java·微信小程序·鲜花销售小程序·java鲜花销售小程序·鲜花销售微信小程序·java鲜花销售系统小程序·java鲜花销售微信小程序