SpringBoot中使用Flux實現(xiàn)流式返回的方法小結(jié)
背景
近期在使用deepseek/openai等網(wǎng)頁和APP時,發(fā)現(xiàn)大模型在思考和回復時,內(nèi)容是一點點的顯示出來的,于是好奇他們的實現(xiàn)方式。經(jīng)調(diào)研和使用開發(fā)者工具抓取請求,每次聊天會向后臺發(fā)送一個http請求,而這個接口跟普通接口一次性返回不一樣,而是以流式的返回。
流式返回的核心概念與優(yōu)勢
在傳統(tǒng)的 Web 開發(fā)中,接口通常以「一次性返回完整響應(yīng)體」的形式工作。而 ** 流式返回(Streaming Response)** 指的是服務(wù)器在處理請求時,將響應(yīng)結(jié)果分段逐步返回給客戶端,而非等待所有數(shù)據(jù)生成完成后再一次性返回。這種模式具有以下核心優(yōu)勢:
1. 提升用戶體驗
- 對于大數(shù)據(jù)量響應(yīng)(如文件下載、長文本流)或?qū)崟r交互場景(如聊天機器人對話),客戶端可邊接收數(shù)據(jù)邊處理,減少「空白等待時間」,提升實時性感知。
2. 降低內(nèi)存消耗
- 服務(wù)器無需在內(nèi)存中緩存完整響應(yīng)數(shù)據(jù),尤其適合處理高并發(fā)、大流量場景,降低 OOM(內(nèi)存溢出)風險。
3. 支持長連接與實時通信
- 天然適配實時數(shù)據(jù)推送場景(如日志監(jiān)控、股票行情更新),可與 SSE(Server-Sent Events)、WebSocket 等技術(shù)結(jié)合使用。
大模型的接口,尤其是那些帶推理的模型接口返回,數(shù)據(jù)就是一點點的返回的,因此如果要提升用戶體驗,最好的方式就是采用流式接口返回。
在SpringBoot中基于Flux的流式接口實現(xiàn)
1. 依賴配置
在 pom.xml 中引入 WebFlux 依賴:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>2. 流式接口實現(xiàn)(以模擬大模型對話為例)
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) {
// 調(diào)用大模型 API 并返回 Flux 流
return callLargeModelApi(request.message())
.doOnNext(chunk -> log.info("發(fā)送響應(yīng)片段: {}", chunk))
.doOnError(error -> log.error("流式處理出錯", error));
}
// 模擬調(diào)用大模型 API,返回 Flux 流
private Flux<String> callLargeModelApi(String prompt) {
// 實際項目中需替換為真實的大模型調(diào)用邏輯
return Flux.just(
"您好!",
"我是您的AI助手。",
"您的問題是:" + prompt,
"我將為您提供詳細解答..."
)
.delayElements(Duration.ofMillis(300)); // 模擬實時響應(yīng)延遲
}
}3. 關(guān)鍵配置說明
- 響應(yīng)格式:設(shè)置
produces = MediaType.TEXT_EVENT_STREAM_VALUE,符合 SSE 協(xié)議。 - 異步處理:Flux 流中的元素會被自動轉(zhuǎn)換為 SSE 格式(
data: <內(nèi)容>\n\n)并推送至客戶端。 - 背壓控制:通過
onBackpressureBuffer()或onBackpressureDrop()處理客戶端消費速率問題。
瀏覽器端 JS 調(diào)用方案
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;
// 解碼并處理數(shù)據(jù)塊
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;
});
}
}調(diào)用Deepseek模型實戰(zhàn)
寫一個接口,通過Spring AI Alibaba ,調(diào)用阿里云百煉的deepseek模型,返回Flux流數(shù)據(jù)
基礎(chǔ)使用詳見:快速開始-阿里云Spring AI Alibaba官網(wǎng)官網(wǎng)
這里只給出轉(zhuǎn)Flux的示例,即通過client/model的stream方法來轉(zhuǎn),并通過map方法將每個流轉(zhuǎn)成前端需要的數(shù)據(jù)(我這里是區(qū)分了thinking思考和content的數(shù)據(jù),便于前端顯示):
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()));
}
}
// 記錄用戶的輸入
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();
// 模擬流式響應(yīng)
return this.chatClient.prompt().messages(messages).user(request.getContent()).stream().chatResponse().doOnNext(response -> {
String content = response.getResult().getOutput().getText();
if (StringUtils.isNotBlank(content)) {
// 記錄完整的響應(yīng)對象
sb.append(content);
}
})
// 在流結(jié)束時記錄完整的會話內(nèi)容
.doOnComplete(() -> {
// 這里記錄消息到數(shù)據(jù)庫
String content = sb.toString();
LOGGER.info("收到模型原始響應(yīng)結(jié)束: " + 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
到此這篇關(guān)于SpringBoot中使用Flux實現(xiàn)流式返回的技術(shù)總結(jié)的文章就介紹到這了,更多相關(guān)SpringBoot Flux流式返回內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
- influxdb數(shù)據(jù)庫常用命令及SpringBoot整合
- SpringBoot深入分析webmvc和webflux的區(qū)別
- springboot webflux 過濾器(使用RouterFunction實現(xiàn))
- SpringBoot之webflux全面解析
- Springboot使用influxDB時序數(shù)據(jù)庫的實現(xiàn)
- Springboot WebFlux集成Spring Security實現(xiàn)JWT認證的示例
- SpringBoot2使用WebFlux函數(shù)式編程的方法
- springboot+RabbitMQ+InfluxDB+Grafara監(jiān)控實踐
相關(guān)文章
基于spring+springmvc+hibernate 整合深入剖析
這篇文章主要介紹了于spring+springmvc+hibernate整合實例,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-10-10
kafka生產(chǎn)者和消費者的javaAPI的示例代碼
這篇文章主要介紹了kafka生產(chǎn)者和消費者的javaAPI的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-06-06
Java實現(xiàn)MySQL數(shù)據(jù)實時同步至Elasticsearch的方法詳解
MySQL擅長事務(wù)處理,而Elasticsearch(ES)則專注于搜索與分析,將MySQL數(shù)據(jù)實時同步到ES,可以充分發(fā)揮兩者的優(yōu)勢,下面我們就來看看如何使用Java實現(xiàn)這一功能吧2025-03-03
SpringBoot集成validation校驗參數(shù)遇到的坑
這篇文章主要介紹了SpringBoot集成validation校驗參數(shù)遇到的坑,本文通過實例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2020-12-12
Mockito+PowerMock+Junit單元測試用途解析
本文介紹單元測試在開發(fā)和DevOps中的規(guī)范要求,詳解Mockito和PowerMock的使用,包括解耦依賴、模擬行為、驗證調(diào)用及參數(shù)匹配,同時說明SpringBoot測試注解(如@MockBean)的用法,并提及IDEA插件Squaretest的自動化測試生成功能,感興趣的朋友一起看看吧2025-06-06

