Spring WebFlux SSE 长连接,可以替换为 Java 21 虚拟线程吗?

Java 21 的虚拟线程可以用于 SSE,但不代表 SSE 必须从响应式编程切换到阻塞式 IO,也不代表切换后性能一定更好。

这里先区分三个概念:

  • SSE:HTTP 服务端向客户端持续推送事件的通信方式。

  • Flux:Reactor 提供的响应式数据流抽象。

  • Java 21 虚拟线程:让阻塞式代码能够以较低线程开销承载大量并发任务。

它们不是同一层面的技术,因此可以组合使用。

1. 现有 Flux SSE 架构

例如 Spring WebFlux 中常见写法:

java

复制代码
@GetMapping(
    value = "/chat/stream",
    produces = MediaType.TEXT_EVENT_STREAM_VALUE
)
public Flux<String> stream(@RequestParam String prompt) {
    return chatClient.prompt()
        .user(prompt)
        .stream()
        .content();
}

基本流程是:

  1. 浏览器或前端建立 HTTP SSE 连接;

  2. WebFlux + Reactor 管理异步请求、数据流与背压;

  3. ChatClient.stream() 返回 Flux;

  4. 大模型流式接口持续产生数据。

这种架构的优势是:当连接等待模型响应、网络数据或其他异步事件时,通常不需要为每条 SSE 连接独占一个平台线程。

但要注意:Flux 本身不保证整个调用链都是非阻塞的。如果模型客户端、数据库、工具调用或业务逻辑内部存在阻塞操作,仍然可能占用线程池,甚至影响 Reactor 的事件循环。

2. Java 21 虚拟线程 SSE 架构

如果希望继续使用熟悉的同步 Java 编程方式,可以考虑 Spring MVC + Servlet + 虚拟线程。

java

复制代码
@GetMapping(
    value = "/chat/stream",
    produces = MediaType.TEXT_EVENT_STREAM_VALUE
)
public SseEmitter stream(@RequestParam String prompt) {
    SseEmitter emitter = new SseEmitter(0L);

    Thread.startVirtualThread(() -> {
        try {
            // 同步调用模型并获得完整响应
            String answer = chatService.chat(prompt);

            emitter.send(SseEmitter.event()
                .data(answer));

            emitter.complete();
        } catch (Exception e) {
            emitter.completeWithError(e);
        }
    });

    return emitter;
}

这个例子演示的是同步生成完成后发送一个 SSE 事件,不是逐 token 推送。它也假设 chatService.chat() 是同步阻塞调用。

如果要做到真正的逐 token 流式输出,需要让模型调用本身提供流式数据,并在消费每个数据片段时调用 emitter.send(...),同时处理超时、断连、取消及异常。

还需要注意:仅仅启动虚拟线程,并不能自动把 WebFlux 变成虚拟线程架构。HTTP 服务端、模型客户端和业务调用链都需要分别考虑。

3. 两种方案怎么选?

维度 WebFlux + Flux Spring MVC + Java 21 虚拟线程
编程风格 响应式、异步 命令式、同步阻塞也可
HTTP I/O 非阻塞 I/O Servlet 异步处理,响应写入通常是阻塞的
长连接资源 通常不需要每连接独占线程 可以用虚拟线程降低线程开销
背压 Reactor 原生支持 需要自行管理生产与消费速率
阻塞式 JDBC、SDK 需要隔离阻塞调用 更容易直接使用
适合场景 大量异步 I/O、流式数据链路 阻塞式业务、传统 Java 代码

Spring MVC 本身也支持返回 Flux 进行 SSE 流式响应,但响应写入仍可能通过独立线程执行,并不等于使用了 WebFlux 的全链路非阻塞 I/O。

4. Java 21 虚拟线程如何开启?

如果使用 Spring Boot 3.2 或更高版本,并且相关版本支持该配置,可以尝试:

yaml

复制代码
spring:
  threads:
    virtual:
      enabled: true

这会让 Spring Boot 在符合条件的自动配置场景下使用虚拟线程执行任务,但不会自动把 WebFlux 的 Netty 事件循环替换成 Servlet 模型。

虚拟线程尤其适合大量等待网络、数据库等 I/O 的任务,但不是用来加速 CPU 密集型计算的。还要注意线程固定(pinning)、连接池容量以及下游服务的并发限制。

5. 如果是 AI 对话 SSE,建议怎么设计?

如果业务是大模型逐 token 返回,同时需要支持大量用户并发连接,建议优先考虑下面两种架构。

方案一:保留 Flux(优先推荐)
  • 使用 WebFlux 处理 SSE;

  • 使用 ChatClient.stream() 获取模型输出;

  • 将阻塞式数据库、工具调用放到合适的隔离执行器中;

  • 增加客户端断连取消、超时、心跳和并发限流。

适合已经使用 Spring AI 响应式流式接口、希望保持非阻塞链路的系统。

方案二:切换虚拟线程
  • 使用 Spring MVC + SseEmitter;

  • 用虚拟线程执行同步任务;

  • 将模型的流式响应逐段发送到前端;

  • 做好发送失败、任务取消和资源释放。

适合大量现有同步 Java 代码、阻塞式 SDK 或传统业务组件的系统。

有一个容易忽略的问题:如果使用的 Spring AI 模型客户端本身通过响应式 Flux 提供 token 流,即使 Web 层切换成 SseEmitter,也不必强行把模型客户端改成同步调用。可以在 MVC 中消费响应式流,再将数据写入 SSE,但需要正确处理订阅生命周期和取消传播。

官方文档也说明,Spring MVC 可以适配 Flux 等响应式返回类型,因此两种编程模型并非完全互斥。

相关推荐
瀚域智擎1 小时前
GEO 信源采信机制深度解析:大模型如何筛选和排序信源
大数据·人工智能·#geo #ai 搜索 #品牌增长 #数字化营销 #内容运营
niucloud-admin1 小时前
NIUCLOUD LITE AI开发文档—模块开发:AI技能使用
ai编程
Fang_YuanAI1 小时前
构图不是形容词,是坐标系:16种AI提示词与拍摄思路
人工智能·aigc·ai漫剧·ai短剧·aigc创作·构图·人像摄影
其实防守也摸鱼1 小时前
网络安全法竞赛知识总结与题库
前端·网络·人工智能·安全·web安全·重构·nc
axe_hu1 小时前
AI产品经理入门指南
人工智能·产品经理
灵动的艺术1 小时前
如一 Agent 架构解读(二十一):SubAgent 体系——封装、闭环与可交互
人工智能·agent·如一
chansonzhang1 小时前
Multi-Agent
ai·强化学习·multi-agent
孔令飞1 小时前
1024程序员节,OneX平台送3套免费Go实战项目
云原生·go·ai编程
小小程序猴11 小时前
【无标题】
人工智能