spring boot+vue+sse(Server-Sent Events)教程一

1,044 阅读9分钟

参考学习资源

  1. EventSource文档
  2. @microsoft/fetch-event-source EventSource替代方案
  3. 视频讲解教程

什么是AI大模型

从基础出发,AI大模型通常是指那些在机器学习任务中表现出色的大型神经网络。这些模型往往包含数十亿甚至数千亿个参数。参数数量的增加,使得模型能够捕捉到更多的细节和模式,从而显著提升其在各种任务中的性能,如语言理解、图像识别等。 然而,为何这些模型被称为“大”模型呢? 这里的“大”不仅指模型的规模,更强调其在数据和计算资源上的需求。这些模型需要大量的数据进行训练,以达到高精度的表现。同时,它们在训练和推理过程中对计算资源的需求也非常庞大,通常需要借助GPU或TPU等高效的硬件加速器。

AI问答中最重要的一环

SSE,全称为Server-Sent Events,是一种允许服务器向客户端异步发送新数据的技术。与传统的轮询方式不同,SSE 提供了一种更为高效的方法,使得服务器可以在数据更新时立即推送给客户端,从而实现真正的实时数据交互。

在当前的人工智能领域,特别是结合了大型AI模型的应用中,SSE 可以发挥重要作用。例如,考虑一个基于云端的AI写作助手,如Kimi,通义千问,文心一言,ChatGPT等。当用户通过前端应用(如Vue.js)输入一个写作请求时,后端的Spring Boot服务可以利用SSE技术,将写作过程中的中间结果或提示信息实时推送给用户。

具体来说,假设用户在写作时需要一些灵感或建议。用户在前端应用中输入一个写作主题,前端应用通过SSE连接到后端的Spring Boot服务。后端服务调用一个大型的AI写作模型,如GPT-4,生成写作建议。随着GPT-4模型逐步生成建议,Spring Boot服务可以通过SSE连接将这些建议逐条推送给前端应用。这样,用户可以即时看到写作建议,而无需不断刷新页面或发送额外的请求。

这种实现实时交互的方法不仅提升了用户体验,还确保了写作过程的连贯性和灵感的即时捕捉。通过SSE,前端应用能够及时展示AI模型的输出,使整个写作过程更加流畅和高效。

综上所述,SSE在AI应用中扮演着至关重要的角色。通过提供一种高效、可靠且易于集成的数据传输机制,SSE使AI应用能够实现实时数据交互、持续学习、资源优化、高度交互的用户体验和强大的安全性。随着AI技术的不断进步,SSE将继续作为构建下一代智能应用的关键技术之一。

什么是sse

SSE协议,全称Server-Sent Events(服务器发送事件),是一种用于服务器主动向客户端推送数据的技术,也被称为事件流(Event Stream)。它基于HTTP协议,通过长连接的方式实现服务器向客户端的实时数据推送。以下是SSE协议的主要特点和详细说明:

主要特点

  1. 基于HTTP协议:SSE协议利用HTTP协议的长连接特性,在客户端与服务器之间建立一条持久化连接,并通过这条连接实现服务器向客户端的实时数据推送。
  2. 单向通信:SSE仅支持服务器向客户端的单向通信,客户端无法直接通过SSE连接向服务器发送数据。
  3. 轻量级:SSE在数据传输上相对轻量级,主要发送文本格式的数据,不需要进行复杂的编码或解码操作。
  4. 自动重连:SSE自带自动重连功能,如果连接断开,浏览器会尝试重新建立连接,确保客户端能够持续接收服务器发送的事件。
  5. 良好的兼容性:SSE在现代浏览器中都有良好的兼容性,可以广泛应用于Web应用程序和移动端应用。

工作原理

  1. 客户端发起请求:客户端通过EventSource对象向服务器发起一个HTTP GET请求,请求特定的SSE资源。
  2. 服务器响应:服务器接收到请求后,不会立即关闭连接,而是保持连接开启状态,并通过这条连接不断向客户端发送数据。服务器发送的数据被封装成事件流的形式,每个事件包含一定的数据内容。
  3. 客户端接收数据:客户端通过监听EventSource对象上的事件(如onmessage、onerror、onopen等),来接收服务器发送的数据,并根据需要进行处理。

代码案例

后端的sse的流式输出

后端使用spring boot中的 SseEmitter 类来实现流式输出(简单实现)

@RestController
@RequestMapping("/sse")
@Slf4j
public class SseTestController {

    @GetMapping(value = "/chat", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public SseEmitter chat() {
        // 创建一个sse协议的对象
        SseEmitter sseEmitter = new SseEmitter(100000L);
        // 用来处理流完成时的逻辑.流完成的意思是事件流已经结束(包括中间异常)或正常关闭出发该回调
        sseEmitter.onCompletion(() -> {
            log.info("SseEmitter completed");
        });
        // 用于处理 SSE 请求超时的情况.超时发生时.回调该方法
        sseEmitter.onTimeout(() -> {
            log.info("SseEmitter timeout");
        });

        // 设置了 SSE 流发生错误时的回调.e 代表捕获的异常信息,可以通过日志记录错误的详细信息.
        sseEmitter.onError(e -> {
            log.error("SseEmitter error -> {}", e.getMessage(), e);
        });

        // 使用了一个单线程执行器来异步执行数据发送的任务. SseEmitter 会在后台线程中执行数据推送,以避免阻塞主线程.数据推送的具体内容在 execute() 方法内部实现.
        Executors.newSingleThreadExecutor().execute(() -> {
            try {
                for (String s : getList()) {
                    sseEmitter.send(s);
                    TimeUnit.MILLISECONDS.sleep(100L);
                }
                sseEmitter.send("[DONE]");
            } catch (Exception e) {
                // 如果 SSE 流因错误而终止,可以调用 completeWithError(ex),这会结束流并将错误信息发送到客户端.在代码中,如果发生异常,completeWithError(e) 会被调用来报告错误.
                sseEmitter.completeWithError(e);
            } finally {
                // 正常关闭结束该流
                sseEmitter.complete();
            }
        });
        return sseEmitter;
    }

    public List<String> getList() {
        final String str = "从基础出发,AI大模型通常是指那些在机器学习任务中表现出色的大型神经网络。这些模型往往包含数十亿甚至数千亿个参数。参数数量的增加,使得模型能够捕捉到更多的细节和模式,从而显著提升其在各种任务中的性能,如语言理解、图像识别等。\n" +
                "然而,为何这些模型被称为“大”模型呢? 这里的“大”不仅指模型的规模,更强调其在数据和计算资源上的需求。这些模型需要大量的数据进行训练,以达到高精度的表现。同时,它们在训练和推理过程中对计算资源的需求也非常庞大,通常需要借助GPU或TPU等高效的硬件加速器。";
        return Stream.of(str.split(StringUtils.EMPTY)).toList();
    }
}

前端sse的请求和接收

前端使用原生的EventSource作为sse流式请求和接收

vue代码:

<script setup lang="ts">
// 获取sse请求地址
import { ref } from 'vue'

const sseUrl = `${import.meta.env.VITE_APP_BASE_API}/system/sse/chat`
const textValue = ref<string>()
// 发送流式的点击事件
const onChat = () => {
  // 创建持久化的sse链接
  let eventSource = new EventSource(sseUrl)

  // 链接在被打开创建时
  eventSource.onopen = () => {
    console.log('opened')
  }

  // 接收到数据时触发
  eventSource.onmessage = e => {
    console.log(e.data)
    if (e.data === '[DONE]') {
      // 用于关闭当前的链接
      eventSource.close()
      console.log('closed')
    }else {
      textValue.value = textValue.value + e.data
    }
  }

  // 当发生错误时或者接收到错误事件的时候触发
  eventSource.onerror = e => {
    console.log(e)
    eventSource.close()
  }
}
</script>

<template>
  <div>{{ textValue }}</div>
  <button @click="onChat">chat</button>
</template>

@microsoft/fetch-event-source替代

基本概念

  • EventSource

    • EventSource 是浏览器原生的 API,专门用于处理 服务器推送事件(SSE)。它通过一个持久化的单向 HTTP 连接从服务器接收事件,并将事件数据以 event 的形式传递到客户端。EventSource 是基于 HTTP 协议的,通常用于客户端从服务器接收数据流,例如实时更新、通知等。
  • @microsoft/fetch-event-source

    • @microsoft/fetch-event-source 是一个基于 fetch API 的第三方库,旨在通过现代的 fetch API 进行 SSE 支持,并为其提供更多的功能,例如 跨平台支持 和 更强的灵活性。它使用 fetch 实现 SSE,因此具备与 fetch 相关的许多优势(如更好的控制请求的配置、简化的流控制等)。

功能扩展和灵活性

EventSource(原生)

  • 原生的 EventSource 提供了非常基础的功能。它不允许灵活控制 HTTP 请求的细节,例如设置请求头、请求方法(只支持 GET)或者自定义超时等。它的配置选项较为简单,通常只能处理基础的 SSE 流。
  • 事件的处理较为基础,开发者只能监听 onmessageonopenonerror 等事件。

@microsoft/fetch-event-source

  • @microsoft/fetch-event-source 提供了更多的功能和灵活性。例如,它允许你:

    • 自定义 HTTP 请求的设置,包括请求头、请求方法、超时配置等。
    • 通过流(Streams API)处理 SSE 数据,使得开发者可以更加灵活地处理大规模的数据流。
    • 提供了内建的 自动重试 和 超时 功能,增强了在不稳定网络环境下的可靠性。
    • 能够处理复杂的 CORS 配置,允许更复杂的跨域请求。
    • 在某些环境下,它还可以通过polyfill 支持 EventSource,从而在不支持 EventSource 的环境中使用。

兼容性和扩展性

  • EventSource(原生)

    • EventSource 是一个浏览器原生实现的 API,所有现代浏览器都支持它。然而,早期的一些浏览器版本(如 IE 和某些旧版本的 Safari)不支持 EventSource,这可能限制了其跨平台的使用。
    • 对于不支持 EventSource 的环境,通常需要依赖 Polyfill 来使其工作,但这不是非常灵活。
  • @microsoft/fetch-event-source

    • fetch-event-source 是一个由 Microsoft 提供的现代 JavaScript 库,能够自动处理浏览器的兼容性问题,提供更好的跨平台支持。
    • 它通过 fetch API 和流的结合,能够更好地支持各种网络特性(如请求重试、超时处理等),并且还可以通过 polyfill 支持不原生支持 EventSource 的浏览器。