SpringBoot + ResponseBodyEmitter 实时异步流式推送,优雅!

news2025/4/22 12:07:21

ChatGPT 的火爆,让流式输出技术迅速走进大众视野。在那段时间里,许多热爱钻研技术的小伙伴纷纷开始学习和实践 SSE 异步处理。

我当时也写过相关文章,今天,咱们换一种更为简便的方式来实现流式输出,那就是 ​​ResponseBodyEmitter​​。

其实,​​ResponseBodyEmitter​​ 并非新技术,早在 Spring Framework 4.2 版本就已被引入。直到最近,我们在开发一个滚动日志输出功能时,才深入了解到它的强大之处。

ResponseBodyEmitter 的作用

相较于 SSE 技术,​​ResponseBodyEmitter​​ 更加简单易用。它主要用于处理异步的 HTTP 响应,其核心优势在于 ​​允许逐步将数据发送到客户端,而非一次性发送所有内容​​。这一特性使得它在需要长时间处理或进行流式传输的场景中表现出色。需要注意的是,​​ResponseBodyEmitter​​ 本质上是一个接口。

使用场景

  1. 长轮询:服务器在有数据时会立即响应客户端请求,若暂无数据,则保持连接开放,等待数据到来。
  2. **服务器推送事件 (SSE)**:服务器能够持续不断地向客户端推送各类事件,实现实时交互。
  3. 流式传输:可逐步发送大量数据,像文件下载或者实时数据流传输等场景都适用。
  4. 异步处理:在处理耗时任务时,能逐步返回处理结果,避免客户端长时间等待,提升用户体验。

业务场景举例

在实际业务中,​​ResponseBodyEmitter​​ 有着广泛的应用,比如进度条的实时更新、实时聊天功能、股票价格的实时更新、系统日志的流式输出以及 AI 的流式响应等。

实时日志流实战

接下来,我们通过一个简单的实时日志流功能,来深入了解 ​​ResponseBodyEmitter​​ 的使用。假设我们有一个应用程序,需要实时查看服务器的日志,以便快速定位和解决问题。

创建控制器

首先,我们在 Spring Boot 应用中创建一个控制器,借助 ​​ResponseBodyEmitter​​ 实现实时日志流。

import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyEmitter;
@RestController
@RequestMapping("/api/log")
publicclass LogController {
    @GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
    public ResponseBodyEmitter streamLogs() {
        ResponseBodyEmitter emitter = new ResponseBodyEmitter();
        // 开启异步线程处理数据并发送
        new Thread(() -> {
            try {
                while (true) {
                    String logEntry = getLatestLogEntry();
                    if (logEntry != null) {
                        emitter.send(logEntry);
                    }
                    // 每秒检查一次日志更新
                    Thread.sleep(1000); 
                }
            } catch (Exception e) {
                // 出现异常时结束响应并传递错误信息
                emitter.completeWithError(e); 
            }
        }).start();
        return emitter;
    }
    private String getLatestLogEntry() {
        // 模拟从日志文件中获取最新日志条目
        return"2025-02-12 12:00:00 - INFO: User logged in successfully.";
    }
}

运行效果

当我们启动这个应用程序,并访问 ​​/api/log/stream​​ 路径时,就能看到一个实时更新的日志流。服务器会每秒向客户端推送一条新的日志条目,客户端会将其显示在页面上,效果如下:

运行效果

运行效果

ResponseBodyEmitter 的核心方法

  • ​send(Object data)​​:向客户端发送数据,该方法可以多次调用,实现数据的逐步发送。
  • ​complete()​​:用于结束响应流,表示数据已经全部发送完毕。
  • ​onTimeout(Runnable callback)​​:设置超时回调函数,当连接超时时,会执行该回调。
  • ​onCompletion(Runnable callback)​​:设置完成回调函数,当数据发送完成后,会执行该回调。

ResponseBodyEmitter 工作原理

异步数据生成与推送

在传统的 HTTP 请求 - 响应模式中,服务器通常需要等待整个响应数据生成完成后,才会将其一次性发送给客户端。而 ​​ResponseBodyEmitter​​ 打破了这种模式,它允许服务端在任务执行过程中异步地生成响应数据。

当有部分数据准备好时,就可以立即调用 ​​send()​​ 方法将这些数据推送给客户端,而无需等待整个任务完成。这就好比一场接力赛,每完成一段赛程(生成一部分数据),就马上将接力棒(数据)传递给客户端,大大提高了数据传输的实时性。

分块传输机制

​ResponseBodyEmitter​​ 采用了 HTTP 的分块编码(Chunked Encoding)方式来传输数据。在传统的 HTTP 响应中,通常需要在响应头中明确指定 ​​Content-Length​​,表示整个响应数据的长度。但在分块传输中,服务器不会提前设置 ​​Content-Length​​,而是将数据分成多个独立的块,每个块都有自己的长度标识。

客户端在接收到数据块后,可以立即对其进行处理,而不必等待整个响应数据接收完毕。这种方式使得数据可以边生成边传输,减少了客户端的等待时间,提高了用户体验。

连接生命周期管理

为了确保资源的合理使用,​​ResponseBodyEmitter​​ 提供了对连接生命周期的有效管理。当所有数据都发送完毕后,需要调用 ​​complete()​​ 方法来明确告知客户端响应结束,关闭连接。如果在数据传输过程中出现异常,可以调用 ​​completeWithError()​​ 方法,结束响应并向客户端传递错误信息。

这样可以避免连接长时间保持开放,造成资源浪费。

注意事项

  1. 客户端支持:虽然大多数浏览器和 HTTP 客户端库都支持分块传输,但某些老旧的客户端可能存在兼容性问题。
  2. 超时设置:为避免长连接长时间占用资源,可以为 ResponseBodyEmitter 设置超时时间,示例代码如下:
emitter.onTimeout(() -> emitter.complete());
  1. 线程安全ResponseBodyEmitter 的 send() 方法是线程安全的,但在使用时需要注意控制任务线程的生命周期,避免出现资源泄漏。
  2. 连接关闭:务必确保在任务结束时调用 complete() 或 completeWithError() 方法,否则可能导致连接无法正常关闭,造成资源浪费。

与 Streaming 和 SSE 的对比

  • Streaming:直接通过 OutputStream 向客户端写入数据,灵活性较高,但需要手动处理流的关闭,增加了开发的复杂度。
  • Server-Sent Events (SSE):基于 text/event-stream 协议,适用于服务端事件推送场景,但要求客户端支持 SSE 协议。
  • ResponseBodyEmitter:通用性更强,适用于任何支持 HTTP 的客户端,并且易于与 Spring 框架集成,是一种更为便捷的流式传输解决方案。

在处理类似 AI 这种响应式的流式输出场景时,相较于 SSE,​​ResponseBodyEmitter​​ 作为 Spring 提供的轻量级流式传输解决方案,在 HTTP 协议兼容性方面表现更优。

小结

​ResponseBodyEmitter​​ 是 Spring 框架提供的轻量级流式传输解决方案,它能够显著提升高并发和实时性场景下的用户体验。通过 ​​ResponseBodyEmitter​​,我们可以轻松实现服务器向客户端的实时数据推送。

无论是进度条的实时更新、实时聊天、股票价格的实时监控还是系统日志的流式输出,​​ResponseBodyEmitter​​ 都能帮助我们构建更加动态和互动的应用程序。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2315663.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

网络运维学习笔记(DeepSeek优化版) 016 HCIA-Datacom综合实验01

文章目录 综合实验1实验需求总部特性 分支8分支9 配置一、 基本配置(IP二层VLAN链路聚合)ACC_SWSW-S1SW-S2SW-Ser1SW-CoreSW8SW9DHCPISPGW 二、 单臂路由GW 三、 vlanifSW8SW9 四、 OSPFSW8SW9GW 五、 DHCPDHCPGW 六、 NAT缺省路由GW 七、 HTTPGW 综合实…

02 windows qt配置ffmpeg开发环境搭建

版本说明 首先我使用ffmpeg版本是4.2.1 QT使用版本5.14.2 我选择是c编译 在02Day.pro⾥⾯添加ffmpeg头⽂件和库⽂件路径 win32 { INCLUDEPATH $$PWD/ffmpeg-4.2.1-win32-dev/include LIBS $$PWD/ffmpeg-4.2.1-win32-dev/lib/avformat.lib \$$PWD/ffmpeg-4.2.1-win32-dev/l…

vue-treeselect 【单选/多选】的时候只选择最后一层(绑定的值只绑定最后一层)

欢迎访问我的个人博客 |snows_ls BLOGhttp://snows-l.site 一、单选 1、问题: vue-treeselect 单选的时候只选择最后一层(绑定的值只绑定最后一层) 2、方法 1、只需要加上 :disable-branch-nodes"true" 就行&#xff0…

微信小程序实现根据不同的用户角色显示不同的tabbar并且可以完整的切换tabbar

直接上图上代码吧 // login/login.js const app getApp() Page({/*** 页面的初始数据*/data: {},/*** 生命周期函数--监听页面加载*/onLoad(options) {},/*** 生命周期函数--监听页面初次渲染完成*/onReady() {},/*** 生命周期函数--监听页面显示*/onShow() {},/*** 生命周期函…

Elastic Stack 8.16.0 日志收集平台的搭建

简介 1.1 ELK 介绍 ELK 是 ‌Elasticsearch‌、‌Logstash‌、‌Kibana‌ 三款开源工具的首字母缩写,构成了一套完整的日志管理解决方案,主要用于日志的采集、存储、分析与可视化‌。 1)Logstash:数据管道工具,负责从…

江科大51单片机笔记【16】AD/DA转换(下)

写在前言 此为博主自学江科大51单片机(B站)的笔记,方便后续重温知识 在后面的章节中,为了防止篇幅过长和易于查找,我把一个小节分成两部分来发,上章节主要是关于本节课的硬件介绍、电路图、原理图等理论知识…

Podman 运行redis 报错

Podman 运行redis 报错 一、报错内容 find: .: Permission denied chown: changing ownership of .: Permission denied二、问题分析 SELinux 模式 SELinux(Security-Enhanced Linux)是一种安全模块,旨在通过强制访问控制(MAC)来增强 Linux 系统的安全性。SELinux 具有…

基于深度学习的多模态人脸情绪识别研究与实现(视频+图像+语音)

这是一个结合图像和音频的情绪识别系统,从架构、数据准备、模型实现、训练等。包括数据收集、预处理、模型训练、融合方法、部署优化等全流程。确定完整系统的组成部分:数据收集与处理、模型设计与训练、多模态融合、系统集成、部署优化、用户界面等。详…

以太坊AI代理与PoS升级点燃3月市场热情,2025年能否再创新高?

币热网深度报道:以太坊AI代理与PoS升级引爆3月热潮,2025年能否再攀历史新高? 原文来源:币热网 - 区块链信息资讯平台 以太坊升级,市场热情高涨 近期,以太坊市场犹如被一股神秘力量点燃,掀起了…

Web网页制作(静态网页):千年之恋

一、是用的PyCharm来写的代码 二、代码中所用到的知识点(无 js) 这段HTML代码展示了一个简单的注册页面,包含了多个HTML元素和CSS样式的应用。 这段HTML代码展示了一个典型的注册页面,包含了常见的HTML元素和表单控件。通过CSS样…

tomcat应用的作用以及安装,以及tomcat软件的开机自启动。

一.tomcat介绍 1.作用 tomcat是一款用来部署网站服务器的一款软件。 动态网站主流语言: PHP, lamp/lnmp平台 Java语言,运行在tomcat平台。【只要这个网站或者软件是Java语言写的,我们都可以在tomcat平台上去运行这个java程序。】 网站是…

Unity中WolrdSpace下的UI展示在上层

一、问题描述 Unity 中 Canvas使用World Space布局的UI,想让它不被3d物体遮挡,始终显示在上层。 二、解决方案 使用shader解决 在 UI 的材质中禁用深度测试(ZTest),强制 UI 始终渲染在最上层。 Shader "Custo…

Redis的缓存雪崩、缓存击穿、缓存穿透与缓存预热、缓存降级

一、缓存雪崩: 1、什么是缓存雪崩: 如果缓在某一个时刻出现大规模的key失效,那么就会导致大量的请求打在了数据库上面,导致数据库压力巨大,如果在高并发的情况下,可能瞬间就会导致数据库宕机。这时候如果…

leetcode:728. 自除数(python3解法)

难度:简单 自除数 是指可以被它包含的每一位数整除的数。 例如,128 是一个 自除数 ,因为 128 % 1 0,128 % 2 0,128 % 8 0。 自除数 不允许包含 0 。 给定两个整数 left 和 right ,返回一个列表&#xff…

vue3-computed计算属性和reactive响应式系统结合使用

1.前言 vue3中使用reactive函数创建一个响应式对象&#xff0c;当对象数据发生变化的时候&#xff0c;依赖这些数据的计算属性和模板会自动的更新。 2.实例 2.1 简写 <template><div><p>用户名: {{ userName }}</p><p>用户名的大写形式: {{ u…

Pycharm 社区版安装教程

找到安装包双击安装文件---点击下一步 一般路径是&#xff1a;C:\Rambo\Software\Development 选择完成后就是如下地址&#xff1a; C:\Rambo\Software\Development\PyCharm Community Edition 2024.3.3 点击上述3个位置就可以了----下一步 等待安装就可以了---完成后点击完成…

Linux红帽:RHCSA认证知识讲解(六)创建、管理和删除本地用戶和组

Linux红帽&#xff1a;RHCSA认证知识讲解&#xff08;六&#xff09;创建、管理和删除本地用戶和组 前言一、用户和组概念用户类型对比表格主要组和补充组对比表格&#xff1a; 二、本地用户账户增删改查三、本地组账户 前言 上篇博客我们详细了解了从红帽和 DNF 软件仓库下载…

分享vue好用的pdf 工具实测

vue3-pdf-app&#xff1a; 带大纲&#xff0c;带分页&#xff0c;带缩放&#xff0c;带全屏&#xff0c;带打印&#xff0c;带下载&#xff0c;带旋转 下载依赖&#xff1a; yarn add vue3-pdf-appornpm install vue3-pdf-app 配置类&#xff1a; 创建文件 pdfConfig.ts /…

Markdown Poster – 免费Markdown转图片工具|优雅图文海报制作与社交媒体分享

Markdown Poster是什么 Markdown Poster 是一款高效的 Markdown 转图片工具&#xff0c;利用灵活编辑和实时预览功能帮助用户轻松制作优雅的图文海报。该工具内置丰富的海报模板和多种主题选项&#xff0c;支持导出为图片和 HTML 代码&#xff0c;适用于社交媒体分享、网站集成…

掌握市场先机:9款销售渠道管理工具深度测评

本文主要介绍了以下9款销售渠道管理工具&#xff1a;1.纷享销客&#xff1b; 2.销帮帮&#xff1b; 3.小满CRM&#xff1b; 4.有赞&#xff1b; 5.Oracle NetSuite&#xff1b; 6.Salesforce Sales Cloud&#xff1b; 7.Cin7&#xff1b; 8.Pipedrive&#xff1b; 9.BigCommerc…