SpringBoot中使用Flux实现流式返回的方法小结

2025-06-18 17:50

本文主要是介绍SpringBoot中使用Flux实现流式返回的方法小结,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

《SpringBoot中使用Flux实现流式返回的方法小结》文章介绍流式返回(StreamingResponse)在SpringBoot中通过Flux实现,优势包括提升用户体验、降低内存消耗、支持长连...

背景

近期在使用deepseek/openai等网页和APP时,发现大模型在思考和回复时,内容是一点点的显示出来的,于是好奇他们的实现方式。经调研和使用开发者工具抓取请求,每次聊天会向后台发送一个http请求,而这个接口跟普通接口一次性返回不一样,而是以流式的返回。

流式返回的核心概念与优势

在传统的 Web 开发中,接口通常以「一次性返回完整响应体」的形式工作。而 ** 流式返回(Streaming Response)** 指的是服务器在处理请求时,将响应结果分段逐步返回给客户端,而非等待所有数据生成完成后再一次性返回。这种模式具有以下核心优势:

1. 提升用户体验

  • 对于大数据量响应(如文件下载、长文本流)或实时交互场景(如聊天机器人对话),客户端可边接收数据边处理,减少「空白等待时间」,提升实时性感知。

2. 降低内存消耗

  • 服务器无需在内存中缓存完整响应数据,尤其适合处理高并发、大流量场景,降低 OOM(内存溢出)风险。

3. 支持长连接与实时通信

  • 天然适配实时数据推送场景(如日志监控、股票行情更新),可与 SSE(Server-Sent Events)、WebSocket 等技术结合使用。

大模型的接口,尤其是那些带推理的模型接口返回,数据就是一点点的返回的,因此如果要提升用户体验,最好的方式就是采用流式接口返回。

在SpringBoot中基于Flux的流式接口实现

1. 依赖配置

在 pom.XML 中引入 WebFlux 依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-webflux</artifactId>
</dependency>

2. 流式接口实现(以模拟大模型对话为例)

import org.springframework.web.bind.annotation.*;
import reactor.core.publisher.Flux;
@RestController
@RequestMapping("/api/chat")
public class ChatController {
    @PostMapping(produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public Flux<String> streamChat(@RequestBody ChatRequest request) {
        // 调用大模型 API 并返回 Flux 流
        return callLargeModelApi(request.mandroidessage())
            .doOnNext(chunk -> log.info("发送响应片段: {}", chunk))
            .doOnError(error -> log.error("流式处理出错", error));
    }
    // 模拟调用大模型 API,返回 Flux 流
    private Flux<String> callLargeModelApi(String prompt) {
        // 实际项目中需替换为真实的大模型调用逻辑
        return Flux.just(
            "您好!", 
            "我是您的AI助手。", 
            "您的问题是:" + prompt, 
            "我将为您提供详细解答..."
        )
        .delayElements(Duration.ofMillis(300)); // 模拟实时响应延迟
    }
}

3. 关键配置说明

  • 响应格式:设置 produces = MediaType.TEXT_EVENT_STREAM_VALUE,符合 SSE 协议
  • 异步处理:Flux 流中的元素会被自动转换为 SSE 格式(data: <China编程;内容>\n\n)并推送至客户端。
  • 背压控制:通过 onBackpressureBuffer() 或 onBackpressureDrop() 处理客户端消费速率问题。

浏览器端 JS 调用方案

1. 使用 EventSource(简化版)

function connectWithEventSource() {
    const source = new EventSource("/api/chat");
    const chatWindow = document.getElementById("chat-window");
    source.onmessage = (event) => {
        chatWindow.innerhtml += `<div>${event.data}</div>`;
        chatWindow.scrollTop = chatWindow.scrollHeight;
    };
    source.onerror = (error) => {
        console.error("EventSource failed:", error);
        source.close();
    };
}

2. 使用 Fetch API(支持 POST 请求)

async function connectWithFetch() {
    const response = await fetch("/api/chat", {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ message: "你好" })
    });
    const reader = response.body.getReader();
    const decoder = new TextDecoder();
    const chatWindow = document.getElementById("chat-window");
    while (true) {
        const { done, value } = await reader.read();
        if (done) break;
        // 解码并处理数据块
        const chunk = decoder.decode(value, { stream: true });
        const messages = chunk.split('\n\n')
            .filter(line => line.trim().startsWith('data:'))
            .map(line => line.replace('data:', '').trim());
        messages.forEach(msg => {
            chatWindow.innerHTML += `<div>${msg}</div>`;
            chatWindow.scrollTop = chatWindow.scrollHeight;
        });
    }
}

调用Deepseek模型实战

写一个接口,通过Spring AI Alibaba ,调用阿里云百炼的deepseek模型,返回Flux流数据

基础使用详见:快速开始-阿里云SWiyJbpring AI Alibaba官网官网

这里只给出转Flux的示例,即通过client/model的stream方法来转,并通过map方法将每个流转成前端需要的数据(我这里是区分了thinking思考和content的数据,便于前端显示):                                

    public Flux<ChatMessageResponse> processRealMessage(ChatMessageRequest request) throws ChatBaseException {
        // 获取会话的历史消息
        List<Message> messages = new ArrayList<>();
        List<ChatMessage> chatMessages = this.chatMessageService.getConversationMessage(request.getSessionId(), 1, 20);
        for (ChatMessage chatMessage : chatMessages) {
            if (Constants.MESSAGE_ROLE_USER.equals(chatMessage.getRole())) {
                messages.add(new UserMessage(chatMessage.getContent()));
            } else {
                messages.add(new AssistantMessage(chatMessage.getContent()));
            }
        }
        // js记录用户的输入
        ChatMessage message = new ChatMessage();
        message.setContent(request.getContent());
        message.setType("text");
        message.setRole(Constants.MESSAGE_ROLE_USER);
        chatMessageService.insertMessage(request.getSessionId(), message);
        StringBuilder sb = new StringBuilder();
        // 模拟流式响应
        return this.chatClient.prompt().messages(messages).user(request.getContent()).stream().chatResponse().doOnNext(response -> {
                    String content = response.getResult().getOutput().getText();
                    if (StringUtils.isNotBlank(contChina编程ent)) {
                        // 记录完整的响应对象
                        sb.append(content);
                    }
                })
                // 在流结束时记录完整的会话内容
                .doOnComplete(() -> {
                    // 这里记录消息到数据库
                    String content = sb.toString();
                    LOGGER.info("收到模型原始响应结束: " + content);
                    ChatMessage assistantMessage = new ChatMessage();
                    assistantMessage.setContent(content);
                    assistantMessage.setType("text");
                    assistantMessage.setRole(Constants.MESSAGE_ROLE_ASSISTENT);
                    try {
                        chatMessageService.insertMessage(request.getSessionId(), assistantMessage);
                    } catch (ChatBaseException e) {
                        LOGGER.error("processMessage2 doOnComplete insertMessage error");
                    }
                }).map(response -> {
                    String content = response.getResult().getOutput().getText();
                    String thinking = response.getResults().get(0).getOutput().getMetadata().get("reasoningContent").toString();
                    if (StringUtils.isNotEmpty(content)) {
                        LOGGER.info("content" + content);
                        return new ChatMessageResponse("content", content);
                    } else if (StringUtils.isNotEmpty(thinking)) {
                        LOGGER.info("thinking" + thinking);
                        return new ChatMessageResponse("thinking", thinking);
                    } else {
                        LOGGER.info("done~~~~");
                        return new ChatMessageResponse("done", "");
                    }
                });
    }

完整代码:MaDiXin/madichat 

到此这篇关于SpringBoot中使用Flux实现流式返回的技术总结的文章就介绍到这了,更多相关SpringBoot Flux流式返回内容请搜索China编程(www.chinasem.cn)以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程China编程(www.chinasem.cn)!

这篇关于SpringBoot中使用Flux实现流式返回的方法小结的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1155118

相关文章

基于 HTML5 Canvas 实现图片旋转与下载功能(完整代码展示)

《基于HTML5Canvas实现图片旋转与下载功能(完整代码展示)》本文将深入剖析一段基于HTML5Canvas的代码,该代码实现了图片的旋转(90度和180度)以及旋转后图片的下载... 目录一、引言二、html 结构分析三、css 样式分析四、JavaScript 功能实现一、引言在 Web 开发中,

Spring @Scheduled注解及工作原理

《Spring@Scheduled注解及工作原理》Spring的@Scheduled注解用于标记定时任务,无需额外库,需配置@EnableScheduling,设置fixedRate、fixedDe... 目录1.@Scheduled注解定义2.配置 @Scheduled2.1 开启定时任务支持2.2 创建

Conda虚拟环境的复制和迁移的四种方法实现

《Conda虚拟环境的复制和迁移的四种方法实现》本文主要介绍了Conda虚拟环境的复制和迁移的四种方法实现,包括requirements.txt,environment.yml,conda-pack,... 目录在本机复制Conda虚拟环境相同操作系统之间复制环境方法一:requirements.txt方法

Nginx 重写与重定向配置方法

《Nginx重写与重定向配置方法》Nginx重写与重定向区别:重写修改路径(客户端无感知),重定向跳转新URL(客户端感知),try_files检查文件/目录存在性,return301直接返回永久重... 目录一.try_files指令二.return指令三.rewrite指令区分重写与重定向重写: 请求

MySQL 打开binlog日志的方法及注意事项

《MySQL打开binlog日志的方法及注意事项》本文给大家介绍MySQL打开binlog日志的方法及注意事项,本文通过实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要... 目录一、默认状态二、如何检查 binlog 状态三、如何开启 binlog3.1 临时开启(重启后失效)

Spring Boot 实现 IP 限流的原理、实践与利弊解析

《SpringBoot实现IP限流的原理、实践与利弊解析》在SpringBoot中实现IP限流是一种简单而有效的方式来保障系统的稳定性和可用性,本文给大家介绍SpringBoot实现IP限... 目录一、引言二、IP 限流原理2.1 令牌桶算法2.2 漏桶算法三、使用场景3.1 防止恶意攻击3.2 控制资源

Mac系统下卸载JAVA和JDK的步骤

《Mac系统下卸载JAVA和JDK的步骤》JDK是Java语言的软件开发工具包,它提供了开发和运行Java应用程序所需的工具、库和资源,:本文主要介绍Mac系统下卸载JAVA和JDK的相关资料,需... 目录1. 卸载系统自带的 Java 版本检查当前 Java 版本通过命令卸载系统 Java2. 卸载自定

springboot下载接口限速功能实现

《springboot下载接口限速功能实现》通过Redis统计并发数动态调整每个用户带宽,核心逻辑为每秒读取并发送限定数据量,防止单用户占用过多资源,确保整体下载均衡且高效,本文给大家介绍spring... 目录 一、整体目标 二、涉及的主要类/方法✅ 三、核心流程图解(简化) 四、关键代码详解1️⃣ 设置

Java Spring ApplicationEvent 代码示例解析

《JavaSpringApplicationEvent代码示例解析》本文解析了Spring事件机制,涵盖核心概念(发布-订阅/观察者模式)、代码实现(事件定义、发布、监听)及高级应用(异步处理、... 目录一、Spring 事件机制核心概念1. 事件驱动架构模型2. 核心组件二、代码示例解析1. 事件定义

SpringMVC高效获取JavaBean对象指南

《SpringMVC高效获取JavaBean对象指南》SpringMVC通过数据绑定自动将请求参数映射到JavaBean,支持表单、URL及JSON数据,需用@ModelAttribute、@Requ... 目录Spring MVC 获取 JavaBean 对象指南核心机制:数据绑定实现步骤1. 定义 Ja