GPT 的流式交互,Spring 可以實(shí)現(xiàn)嗎?
先看一張和 GPT交互的圖片,讓 GPT 寫一篇200字的詩歌贊美 Java:
那么問題來了,我們是否也可以輕松地實(shí)現(xiàn)這種流式交互?
但是是必須的,這篇文章我們就來聊一聊主角:Spring SseEmitter。
1. 什么是 SSE?
SseEmitter 是 Spring MVC 中用于實(shí)現(xiàn)服務(wù)器發(fā)送事件(Server-Sent Events, 簡稱 SSE)的一個(gè)類,它是一種基于 HTTP 協(xié)議的標(biāo)準(zhǔn),用于服務(wù)器向客戶端單向推送事件。
SSE 允許服務(wù)器通過單向通道向客戶端持續(xù)推送數(shù)據(jù),適用于需要實(shí)時(shí)更新的應(yīng)用場(chǎng)景,如實(shí)時(shí)通知、消息推送、動(dòng)態(tài)數(shù)據(jù)展示等。SseEmitter 的工作原理主要涉及以下幾個(gè)方面:
SSE 的特點(diǎn):
- 單向通信:僅服務(wù)器可以主動(dòng)發(fā)送數(shù)據(jù)到客戶端。
- 持久連接:使用持久的 HTTP 連接,服務(wù)器可以持續(xù)發(fā)送事件。
- 自動(dòng)重連:瀏覽器在連接斷開后會(huì)自動(dòng)嘗試重連。
- 基于文本:傳輸?shù)臄?shù)據(jù)格式為純文本,通常為 UTF-8 編碼。
2. SseEmitter如何實(shí)現(xiàn)?
使用 SseEmitter 實(shí)現(xiàn)像 GPT一樣的流式交互,其實(shí)還是比較簡單的,在控制器中創(chuàng)建 SseEmitter 并返回,示例代碼如下:
@RestController
publicclass SseController {
@GetMapping("/sse")
public SseEmitter streamSseMvc() {
SseEmitter emitter = new SseEmitter();
// 異步處理發(fā)送事件
Executors.newSingleThreadExecutor().execute(() -> {
try {
for (int i = 0; i < 10; i++) {
emitter.send("Message " + i);
Thread.sleep(1000);
}
emitter.complete();
} catch (Exception e) {
emitter.completeWithError(e);
}
});
return emitter;
}
}
主要方法:
- send(Object object):發(fā)送事件數(shù)據(jù)給客戶端。
- complete():關(guān)閉連接。
- completeWithError(Throwable ex):在發(fā)生錯(cuò)誤時(shí)關(guān)閉連接并發(fā)送錯(cuò)誤信息。
效果如下圖:
3. 工作流程
(1) 客戶端請(qǐng)求 SSE 端點(diǎn)
客戶端通過 EventSource API 或其他方式向服務(wù)器的 SSE 端點(diǎn)發(fā)送 HTTP GET 請(qǐng)求。例如:
const eventSource = new EventSource('/sse');
eventSource.onmessage = function(event) {
console.log('Received event:', event.data);
};
eventSource.onerror = function(err) {
console.error('EventSource failed:', err);
};
(2) 服務(wù)器端建立 SseEmitter
當(dāng)服務(wù)器接收到 SSE 請(qǐng)求時(shí),控制器方法會(huì)創(chuàng)建一個(gè) SseEmitter 實(shí)例并返回。這會(huì)觸發(fā) Spring MVC 將響應(yīng)頭設(shè)置為 Content-Type: text/event-stream,以維持持久連接。
(3) 服務(wù)器端發(fā)送事件
通過 SseEmitter.send() 方法,服務(wù)器可以向客戶端發(fā)送事件數(shù)據(jù)。通常,這些操作會(huì)在異步線程中進(jìn)行,以避免阻塞主線程。
(4) 持久連接和生命周期管理
SseEmitter 管理著 SSE 連接的生命周期,包括處理超時(shí)、連接斷開和錯(cuò)誤等情況??梢酝ㄟ^配置超時(shí)時(shí)間來控制連接的最長持續(xù)時(shí)間:
SseEmitter emitter = new SseEmitter(30_000L); // 30秒超時(shí)
如果連接在指定時(shí)間內(nèi)未關(guān)閉,SseEmitter 會(huì)自動(dòng)觸發(fā)超時(shí)處理。
(5) 客戶端接收事件
客戶端通過 EventSource 接收并處理服務(wù)器發(fā)送的事件。當(dāng)服務(wù)器調(diào)用 emitter.complete() 或連接因超時(shí)等原因關(guān)閉時(shí),客戶端的 onclose 事件會(huì)被觸發(fā)。
4. 錯(cuò)誤處理與重試機(jī)制
- 服務(wù)器端:在發(fā)送事件過程中,如果發(fā)生異常,可以調(diào)用 emitter.completeWithError(e) 來通知客戶端錯(cuò)誤并關(guān)閉連接。
- 客戶端端:客戶端的 EventSource 會(huì)自動(dòng)嘗試重新連接,當(dāng)連接斷開時(shí),會(huì)觸發(fā) onerror 事件??梢栽诳蛻舳舜a中實(shí)現(xiàn)更復(fù)雜的重試邏輯,例如增加重試次數(shù)限制或延遲策略。
5. 適用場(chǎng)景與限制
(1) 適用場(chǎng)景
- 實(shí)時(shí)通知,如聊天應(yīng)用、社交媒體動(dòng)態(tài)更新。
- 實(shí)時(shí)監(jiān)控,如服務(wù)器狀態(tài)監(jiān)控、數(shù)據(jù)儀表盤。
- 需要頻繁推送更新但數(shù)據(jù)量不大的場(chǎng)景。
(2) 限制
- 僅支持服務(wù)器到客戶端的單向通信。
- 需要瀏覽器支持 SSE 協(xié)議(大多數(shù)現(xiàn)代瀏覽器支持,但部分老舊瀏覽器可能不兼容)。
- 對(duì)于需要高頻率、大數(shù)據(jù)量的實(shí)時(shí)通信,WebSocket 可能更為合適。
6. 總結(jié)
這篇文章,我們分析了如何使用SseEmitter實(shí)現(xiàn)客戶端和服務(wù)器的流式交互,SseEmitter提供了一個(gè)簡潔的方式在 Spring 應(yīng)用中實(shí)現(xiàn)服務(wù)器發(fā)送事件,通過維護(hù)持久連接和異步事件推送,滿足了大多數(shù)實(shí)時(shí)數(shù)據(jù)推送的需求。