在 Spring AI 中,流式输出(Streaming Output)是一种逐步返回 AI 模型生成结果的技术,允许服务器将响应内容分批次实时传输给客户端,而不是等待全部内容生成完毕后再一次性返回。

这种机制能显著提升用户体验,尤其适用于大模型响应较慢的场景(如生成长文本或复杂推理结果)。

技术实现

在 Spring AI 中流式输出的实现有以下两种方式:

  1. 通过 ChatModel 实现流式输出。
  2. 通过 ChatClient 实现流式输出。

ChatModel 流式输出

Spring AI 中的流式输出实现非常简单,使用 ChatModel 中的 stream 即可实现:

@RequestMapping(value = "/streamChat", produces = "text/event-stream")
public Flux<String> streamChat(@RequestParam(value = "msg") String msg) {
return chatModel.stream(msg);
}

ChatClient 流式输出

ChatClient 流式输出实现也很简单,也是调用 stream().content() 返回 Flux 对象即可:

@RequestMapping("/stream")
public Flux<String> stream(String question) {
return chatClient.prompt(question)
.stream()
.content();
}

底层实现

那么问题来了流式输出的底层实现究竟是啥呢?

根据以往的经验我们知道,流式输出的实现技术基本有两种:

  1. Spring MVC(Servlet)+ SSE 实现流式输出。
  2. Spring WebFlux Reactor 模型实现流式输出。

SSE 介绍

SSE(Server-Sent Events)是一种允许服务器向浏览器或其他客户端推送实时更新的技术。它是一种单向通信机制,服务器可以主动向客户端发送数据,而客户端无需频繁轮询服务器请求数据。SSE 是基于 HTTP 协议的,使用标准的 text/event-stream MIME 类型来传输数据。

SSE 主要特点

  1. 单向通信:SSE 仅支持服务器到客户端的单向通信,客户端不能向服务器发送消息。如果需要双向通信,可以结合 WebSocket 或其他技术。
  2. 基于 HTTP:SSE 使用标准的 HTTP 协议,不需要额外的协议支持,因此兼容性较好。
  3. 自动重连:客户端在连接中断后会自动尝试重新连接。
  4. 数据格式:SSE 数据以特定的格式发送,每条消息以 data: 开头,以两个换行符 \n\n 结尾。
  5. 事件类型:可以为每条消息指定事件类型,客户端可以通过监听特定事件类型来处理不同的消息。

Spring MVC(Spring Web)底层是基于 Servlet 实现的,它是使用 SseEmitter 技术实现 SSE 协议实现流式输出的。

SseEmitter 基本用法

这里提供一个 SseEmitter 的简单使用案例,实现流式输出,让大家更好的理解这个技术点:

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException; @RestController
public class SseDemoController {
@GetMapping(value = "/sse-demo", produces = "text/event-stream")
public SseEmitter streamData() {
// 设置超时时间(单位:毫秒)
SseEmitter emitter = new SseEmitter(30_000L); // 30秒超时 // 异步任务模拟流式输出
new Thread(() -> {
try {
for (int i = 1; i <= 5; i++) {
String message = "第 " + i + " 条消息";
emitter.send(message);
Thread.sleep(1000); // 每秒发送一次
}
emitter.complete(); // 完成推送
} catch (IOException | InterruptedException e) {
emitter.completeWithError(e); // 异常处理
}
}).start(); return emitter;
}
}

Spring WebFlux 介绍

Spring WebFlux 是 Spring Framework 5 引入的响应式 Web 框架,旨在解决高并发场景下传统同步阻塞模型(如 Spring MVC)的性能瓶颈。其核心目标是通过非阻塞异步编程模型提升系统吞吐量,适用于 I/O 密集型任务(如微服务通信、实时数据流处理)。

Spring WebFlux 与 Spring MVC 不同,它基于 Reactive Streams 规范实现的,支持背压机制(Backpressure),防止数据生产者压垮消费者。

背压机制:通过订阅者主动控制数据流速,避免内存溢出。例如,消费者可动态调整请求量,生产者根据反馈调整数据生成速度.

Spring AI 流式输出

说完了前置知识,咱们回到主题:Spring AI 是如何实现流式输出的?

要搞清楚这个问题,我们需要看流式输出对象 Flux 的实现源码:

查看 Flux 源码我们发现它是属于 reactor.core.publisher 包下的抽象类:

并且看类注释和类所在的 jar 包我们就明白了:

Spring AI 中的流式输出是通过 Reactor Streams 模型实现的,和 Spring WebFlux 的底层实现是一样的技术。

具体执行流程:Reactor Streams 会订阅数据源,当有数据时,Reactor Streams 以分块流的方式发送给客户端(用户)。

Reactor 介绍

Reactor 是一种事件驱动的高性能网络编程模型,主要用于处理高并发的网络 I/O 请求。其核心思想是通过一个或多个线程监听事件,并将事件分发给相应的处理程序,从而实现高效的并发处理。

Reactor 模型的主要特征如下:

  1. 事件驱动:所有 I/O 操作都由事件触发并处理。
  2. 非阻塞:操作不会因为 I/O 而挂起,避免了线程等待的开销。
  3. 高效资源利用:通过少量线程处理大量并发连接,提升性能。
  4. 组件分离:将事件监听(Reactor)、事件分发(Dispatcher)和事件处理(Handler)解耦,使代码结构更清晰。

Reactor 实现方式有三种:

  1. 单线程 Reactor 模型:所有操作在一个线程完成,适用于低并发场景。
  2. 多线程 Reactor 模型:主线程处理连接,子线程池处理 I/O 和业务。
  3. 主从 Reactor 模型:主线程池处理连接,子线程池处理 I/O(进一步优化资源分配)。

生产级别使用的 Reactor 基本都是主从 Reactor 模型,它的执行流程如下:

小结

Spring AI 中的流式输出有两种实现,而通过查看这两种流式输出的实现源码可知,Spring AI 中的流式输出是通过 Reactor Streams 技术实现的,当客户端发送请求时,会建立连接并订阅数据源,当有数据时,Reactor Streams 以分块流的方式发送给客户端(用户)。

本文已收录到我的技术小站 www.javacn.site,其中包含的内容有:Spring AI、LangChain4j、MCP、Function Call、RAG、向量数据库、Prompt、多模态、向量数据库、嵌入模型等内容。

聊聊SpringAI流式输出的底层实现?的更多相关文章

  1. HttpURLConnection的流式输出的缺陷和解决方法

    转自:http://www.mzone.cc/article/198.html 最近在用applet写文件上传控件的时候发现使用URLConnection来对服务器进行流式输出时的一些问题.我们通常要 ...

  2. 文件下载(StreamingHttpResponse流式输出)

    文件下载(StreamingHttpResponse流式输出) HttpResponse会直接使用迭代器对象,将迭代器对象的内容存储成字符串,然后返回给客户端,同时释放内存.可以当文件变大看出这是一个 ...

  3. DJANGO的HTTPRESPONSE流式输出

    在项目当中遇到的问题,网上有样例代码,但都不行,后来,发现在了1.5版本之后,新的STREAMHTTPRESPONSE对象, 搞定. from django.http import HttpRespo ...

  4. Openresty的同步输出与流式响应

    Openresty的同步输出与流式响应 默认情况下, ngx.say和ngx.print都是异步输出的,先来看一个例子: location /test { content_by_lua_block { ...

  5. centos 正则,grep,egrep,流式编辑器 sed,awk -F 多个分隔符 通配符 特殊符号. * + ? 总结 问加星 cat -n nl 输出文件内容并加上行号 alias放~/.bash_profile 2015-4-10 第十三节课

    centos 正则,grep,egrep,流式编辑器 sed,awk -F 多个分隔符  通配符 特殊符号. * + ? 总结  问加星 cat -n  nl  输出文件内容并加上行号 alias放~ ...

  6. 流式处理的新贵 Kafka Stream - Kafka设计解析(七)

    原创文章,转载请务必将下面这段话置于文章开头处. 本文转发自技术世界,原文链接 http://www.jasongj.com/kafka/kafka_stream/ Kafka Stream背景 Ka ...

  7. 流式计算与计算抽象化------《Designing Data-Intensive Applications》读书笔记15

    上篇的内容,我们探讨了分布式计算中的MapReduce与批处理.所以本篇我们将继续探索分布式计算优化的相关细节,并且分析MapReduce与批处理的局限性,看看流式计算是否能给我们在分布式计算层面提供 ...

  8. “流式”前端构建工具——gulp.js 简介

    Grunt 一直是前端领域构建工具(任务运行器或许更准确一些,因为前端构建只是此类工具的一部分用途)的王者,然而它也不是毫无缺陷的,近期风头正劲的 gulp.js 隐隐有取而代之的态势.那么,究竟是什 ...

  9. Spark Streaming流式处理

    Spark Streaming介绍 Spark Streaming概述 Spark Streaming makes it easy to build scalable fault-tolerant s ...

  10. 流式计算新贵Kafka Stream设计详解--转

    原文地址:https://mp.weixin.qq.com/s?__biz=MzA5NzkxMzg1Nw==&mid=2653162822&idx=1&sn=8c4611436 ...

随机推荐

  1. dart变量声明和变量类型

    ps==>所有的代码必须放在main方法中 main方法有两种写法 1==> main() { print("你好,dart我们相遇了"); } 2==> voi ...

  2. 《中国电信天翼云PON SD-WAN技术白皮书》来了,这份技术指南不要错过!

    5月17日,在中国电信第三届科技节·上海站暨517世界电信日活动上,天翼云联合中国电信上海公司正式发布<中国电信天翼云PON SD-WAN技术白皮书>,为中国电信深入实施"云转数 ...

  3. 并发编程 - 线程同步(四)之原子操作Interlocked详解一

    上一章我们了解了原子操作Interlocked类的设计原理及简单介绍,今天我们将对Interlocked的使用进行详细讲解. 在此之前我们先学习一个概念--原子操作. 01.Read方法 该方法用于原 ...

  4. [记录点滴]Spring Boot Admin源码分析笔记

    [记录点滴]Spring Boot Admin源码分析笔记 0x00 摘要 本文是过去使用Spring Boot Admin时候分析源码的笔记.虽然比较简单,但是也可以看出Spring Boot Ad ...

  5. Luogu P5663 CSP-J2019 加工零件 题解 [ 绿 ] [ 分层图最短路 ]

    加工零件:非常好的一道图论题.CCF 普及组的题目大概也只有图论出的比较巧妙了. 题意简述:给你一张无向图,\(q\) 次询问,判断是否存在一条从 \(a\) 到 \(1\) 且长度为 \(L\) 的 ...

  6. datax从mysql迁移数据到OceanBase

    datax部署 下载datax datax下载地址 安装datax tar -zxvf datax.tar.gz 使用datax 使用配置文件 { "job": { "s ...

  7. 程序员的生产力神器Cursor -新手实操指南

    不得不说这个Cursor AI简直是神器中的神器! 代码自动补全就算了,关键是它能直接读懂我的意图,秒懂需求!为你自动生成整个项目级别的代码!开发过程丝滑得像在跟老朋友聊天,代码质量贼高,效率提升10 ...

  8. [TJOI2019] 甲苯先生的字符串 题解

    有点水了-- 考虑相邻的不能放在一起,不相邻的可以,那么很容易想到转移方程: \[dp_{i,j}=\sum_{k=0}^{25}dp_{i-1,k}[j,k不相邻] \] 其中 \(dp_{i,j} ...

  9. FANUC机器人M-410iB/700电机断轴维修方法

    发那科(FANUC)作为电机领域的领袖品牌,其伺服电机广泛应用于各种工业设备中,特别是在机床.自动化控制.机器人等领域.然而,即使是如此高品质的伺服电机,也难免会出现FANUC工业机械手电机故障,其中 ...

  10. C#(面向对象的托管语言)类库(区别于应用程序)的异常处理思路

    1.不要做出任何应用程序才需要考虑抉择策略,不能想当然的决定一些错误情形.具体的一个体现形式是什么异常都捕获.这不是类库的职责,因为无法掌握所有的调用者的使用情形,这些不确定性是委托.虚方法.接口等特 ...