当前位置: 首页 > news >正文

Spring 实现 3 种异步流式接口,干掉接口超时烦恼

大家好,我是小富~

如何处理比较耗时的接口?

这题我熟,直接上异步接口,使用 CallableWebAsyncTaskDeferredResultCompletableFuture等均可实现。

但这些方法有局限性,处理结果仅返回单个值。在某些场景下,如果需要接口异步处理的同时,还持续不断地向客户端响应处理结果,这些方法就不够看了。

Spring 框架提供了多种工具支持异步流式接口,如 ResponseBodyEmitterSseEmitterStreamingResponseBody。这些工具的用法简单,接口中直接返回相应的对象或泛型响应实体 ResponseEntity<xxxx>,如此这些接口就是异步的,且执行耗时操作亦不会阻塞 Servlet 的请求线程,不影响系统的响应能力。

下面将逐一介绍每个工具的使用及其应用场景。

ResponseBodyEmitter

ResponseBodyEmitter适应适合于需要动态生成内容并逐步发送给客户端的场景,例如:文件上传进度、实时日志等,可以在任务执行过程中逐步向客户端发送更新。

举个例子,经常用GPT你会发现当你提问后,得到的答案并不是一次性响应呈现的,而是逐步动态显示。这样做的好处是,让你感觉它在认真思考,交互体验比直接返回完整答案更为生动和自然。

使用ResponseBodyEmitter来实现下这个效果,创建 ResponseBodyEmitter 发送器对象,模拟耗时操作逐步调用 send 方法发送消息。

注意:ResponseBodyEmitter 的超时时间,如果设置为 0-1,则表示连接不会超时;如果不设置,到达默认的超时时间后连接会自动断开。其他两种工具也是同样的用法,后边不在赘述了

@GetMapping("/bodyEmitter")
public ResponseBodyEmitter handle() {// 创建一个ResponseBodyEmitter,-1代表不超时ResponseBodyEmitter emitter = new ResponseBodyEmitter(-1L);// 异步执行耗时操作CompletableFuture.runAsync(() -> {try {// 模拟耗时操作for (int i = 0; i < 10000; i++) {System.out.println("bodyEmitter " + i);// 发送数据emitter.send("bodyEmitter " + i + " @ " + new Date() + "\n");Thread.sleep(2000);}// 完成emitter.complete();} catch (Exception e) {// 发生异常时结束接口emitter.completeWithError(e);}});return emitter;
}

实现代码非常简单。通过模拟每2秒响应一次结果,请求接口时可以看到页面数据在动态生成。效果与 GPT 回答基本一致。

SseEmitter

SseEmitterResponseBodyEmitter 的一个子类,它同样能够实现动态内容生成,不过主要将它用在服务器向客户端推送实时数据,如实时消息推送、状态更新等场景。在我之前的一篇文章 我有 7种 实现web实时消息推送的方案 中详细介绍了 Server-Sent Events (SSE) 技术,感兴趣的可以回顾下。

SSE在服务器和客户端之间打开一个单向通道,服务端响应的不再是一次性的数据包而是text/event-stream类型的数据流信息,在有数据变更时从服务器流式传输到客户端。

整体的实现思路有点类似于在线视频播放,视频流会连续不断的推送到浏览器,你也可以理解成,客户端在完成一次用时很长(网络不畅)的下载。

客户端JS实现,通过一次 HTTP 请求建立连接后,等待接收消息。此时,服务端为每个连接创建一个 SseEmitter 对象,通过这个通道向客户端发送消息。

<body>
<div id="content" style="text-align: center;"><h1>SSE 接收服务端事件消息数据</h1><div id="message">等待连接...</div>
</div>
<script>let source = null;let userId = 7777function setMessageInnerHTML(message) {const messageDiv = document.getElementById("message");const newParagraph = document.createElement("p");newParagraph.textContent = message;messageDiv.appendChild(newParagraph);}if (window.EventSource) {// 建立连接source = new EventSource('http://127.0.0.1:9033/subSseEmitter/'+userId);setMessageInnerHTML("连接用户=" + userId);/*** 连接一旦建立,就会触发open事件* 另一种写法:source.onopen = function (event) {}*/source.addEventListener('open', function (e) {setMessageInnerHTML("建立连接。。。");}, false);/*** 客户端收到服务器发来的数据* 另一种写法:source.onmessage = function (event) {}*/source.addEventListener('message', function (e) {setMessageInnerHTML(e.data);});} else {setMessageInnerHTML("你的浏览器不支持SSE");}
</script>
</body>

在服务端,我们将 SseEmitter 发送器对象进行持久化,以便在消息产生时直接取出对应的 SseEmitter 发送器,并调用 send 方法进行推送。

private static final Map<String, SseEmitter> EMITTER_MAP = new ConcurrentHashMap<>();@GetMapping("/subSseEmitter/{userId}")
public SseEmitter sseEmitter(@PathVariable String userId) {log.info("sseEmitter: {}", userId);SseEmitter emitterTmp = new SseEmitter(-1L);EMITTER_MAP.put(userId, emitterTmp);CompletableFuture.runAsync(() -> {try {SseEmitter.SseEventBuilder event = SseEmitter.event().data("sseEmitter" + userId + " @ " + LocalTime.now()).id(String.valueOf(userId)).name("sseEmitter");emitterTmp.send(event);} catch (Exception ex) {emitterTmp.completeWithError(ex);}});return emitterTmp;
}@GetMapping("/sendSseMsg/{userId}")
public void sseEmitter(@PathVariable String userId, String msg) throws IOException {SseEmitter sseEmitter = EMITTER_MAP.get(userId);if (sseEmitter == null) {return;}sseEmitter.send(msg);
}

接下来向 userId=7777 的用户发送消息,127.0.0.1:9033/sendSseMsg/7777?msg=欢迎关注–>程序员小富,该消息可以在页面上实时展示。

而且SSE有一点比较好,客户端与服务端一旦建立连接,即便服务端发生重启,也可以做到自动重连

StreamingResponseBody

StreamingResponseBody 与其他响应处理方式略有不同,主要用于处理大数据量或持续数据流的传输,支持将数据直接写入OutputStream

例如,当我们需要下载一个超大文件时,使用 StreamingResponseBody 可以避免将文件数据一次性加载到内存中,而是持续不断的把文件流发送给客户端,从而解决下载大文件时常见的内存溢出问题。

接口实现直接返回 StreamingResponseBody 对象,将数据写入输出流并刷新,调用一次flush就会向客户端写入一次数据。

@GetMapping("/streamingResponse")
public ResponseEntity<StreamingResponseBody> handleRbe() {StreamingResponseBody stream = out -> {String message = "streamingResponse";for (int i = 0; i < 1000; i++) {try {out.write(((message + i) + "\r\n").getBytes());out.write("\r\n".getBytes());//调用一次flush就会像前端写入一次数据out.flush();TimeUnit.SECONDS.sleep(1);} catch (InterruptedException e) {e.printStackTrace();}}};return ResponseEntity.ok().contentType(MediaType.TEXT_HTML).body(stream);
}

demo这里输出的是简单的文本流,如果是下载文件那么转换成文件流效果是一样的。

总结

这篇介绍三种实现异步流式接口的工具,算是 Spring 知识点的扫盲。使用起来比较简单,没有什么难点,但它们在实际业务中的应用场景还是很多的,通过这些工具,可以有效提高系统的性能和响应能力。

文中 Demo Github 地址:https://github.com/chengxy-nds/Springboot-Notebook/tree/master/springboot101/%E9%80%9A%E7%94%A8%E5%8A%9F%E8%83%BD/springboot-streaming

相关文章:

Spring 实现 3 种异步流式接口,干掉接口超时烦恼

大家好&#xff0c;我是小富&#xff5e; 如何处理比较耗时的接口&#xff1f; 这题我熟&#xff0c;直接上异步接口&#xff0c;使用 Callable、WebAsyncTask 和 DeferredResult、CompletableFuture等均可实现。 但这些方法有局限性&#xff0c;处理结果仅返回单个值。在某…...

字节 HLLM 论文阅读

github连接&#xff1a;https://github.com/bytedance/HLLM 探讨问题&#xff1a; 推荐LLM的三个关键问题&#xff1a; LLM预训练权重通常被认为是对世界知识的概括&#xff0c;其对于推荐系统的价值&#xff1f;对推荐任务进行微调的必要性&#xff1f;LLM是否可以在推荐系统…...

Chromium html<iframe>对应c++接口定义

HTML <iframe> 标签 使用 <iframe> 标签 在当前 HTML 文档中嵌入另一个文档&#xff1a; <!DOCTYPE html> <html> <body><h1>iframe 元素</h1><iframe src"https://www.w3school.com.cn" title"W3School 在线教…...

Vue详细入门(语法【三】)

今天滴的学习目标&#xff01;&#xff01;&#xff01; Vue组件是什么&#xff1f;组件的特性和优势Vue3计算属性Vue3监听属性 在前面Vue详细入门&#xff08;语法【一】——【二】&#xff09;当中我们学习了Vue有哪些指令&#xff0c;它的核心语法有哪些&#xff1f;今天我们…...

快速构建SpringBoot项目

快速构建SpringBoot项目 下文将简述如何快速构建一个SpringBoot项目&#xff0c;使用SpringData JPA实现持久层访问&#xff0c;集成lombok、swagger2及集成thymeleaf进行页面展示。 准备环境&#xff1a; JDK版本&#xff1a;jdk17 IntelliJ IDEA版本&#xff1a; 2023.2.7…...

架构设计笔记-14-云原生架构设计理论与实践

知识要点 云原生&#xff08;Cloud Native&#xff09;架构原则&#xff1a; 服务化原则&#xff1a;通过微服务架构&#xff0c;小服务&#xff08;MiniService&#xff09;架构把不同生命周期的模块分离出来&#xff0c;分别进行业务迭代&#xff0c;避免迭代频繁模块被慢速…...

leetcode hot100 之【LeetCode 206. 反转链表】 java实现

LeetCode 206. 反转链表 题目描述 给你单链表的头节点 head &#xff0c;请你反转链表&#xff0c;并返回反转后的链表。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4,5] 输出&#xff1a;[5,4,3,2,1]示例 2&#xff1a; 输入&#xff1a;head [1,2] 输出&#x…...

基于Spring Cloud的电商系统设计与实现——用户与商品模块的研究(上)

操作系统&#xff1a;Windows Java开发包&#xff1a;JDK1.8 项目管理工具&#xff1a;Maven3.6.0 项目开发工具&#xff1a;IntelliJIDEA 数据库&#xff1a;MySQL Spring Cloud版本&#xff1a;Finchley.SR2 Spring Boot版本&#xff1a;2.0.6.RELEASE 目录 用户模块—user-…...

Spring Boot + Vue 前后端分离项目总结:解决 CORS 和 404 问题

Spring Boot Vue 前后端分离项目总结&#xff1a;解决 CORS 和 404 问题 在进行前后端分离的项目开发中&#xff0c;我们遇到了几个关键问题&#xff1a;跨域问题 (CORS) 和 404 路由匹配错误。以下是这些问题的详细分析和最终的解决方案。 问题描述 跨域请求被阻止 (CORS) 当…...

JVM篇(学习预热 - JVM正式展开 - (实战课程学习总结))(持续更新迭代)

目录 感觉也看了这么多&#xff0c;说一些乱七八糟的内容&#xff0c;完全没有实质的收获&#xff0c;那么现在让我们正式来预热下JVM 吧&#xff1f; 一、程序的执行方式 二、为什么使用 JVM 三、字节码和机器码的区别 四、JDK、JRE与JVM的关系 五、OracleJDK和OpenJDK …...

WebGL编程指南 - 入门续

相关内容&#xff1a;在attribute变量传递参数的基础上&#xff0c;通过JavaScript获取鼠标事件的坐标&#xff0c;再经过坐标转换传递给attribute变量&#xff1b;Web颜色缓冲区每次绘制之后都会重置相关函数&#xff1a;JavaScript鼠标事件onmousedown/onmouseup/onclick htm…...

EPS导出DWG存在地物缺失或者没有编码属性的情况

问题描述 使用eps导出dwg时&#xff0c;打开dwg会发现部分地物缺失或者没有编码属性。 这里就是一片空白&#xff1a; 解决办法 1 查看eps的图层信息&#xff0c;发现图层没有对应上&#xff0c;故此地物编码也是没有的。 2 可以右键全选本编码对象&#xff0c;实现批量快…...

跨境业务收款难?Zoho Books来帮忙

外贸跨境企业应收账款管理繁琐&#xff0c;ZohoBooks财务管理软件提供自动化解决方案&#xff0c;简化开票、跟进、收款和账户更新流程&#xff0c;提升效率和准确性&#xff0c;助力企业优化现金流和财务健康。 什么是应收账款&#xff1f; 应收账款指的是企业在提供商品或服…...

深入解析 Harris 角点检测算法:从孔径问题到响应函数的完整推导

在图像处理中&#xff0c;角点是非常重要的特征。为了快速、准确地检测角点&#xff0c;Harris 提出了 Harris 角点检测算法&#xff0c;它基于局部窗口内图像梯度的变化来判断角点。本文将从最基础的孔径问题&#xff08;Aperture Problem&#xff09;入手&#xff0c;通过泰勒…...

抖音视频制作怎么暂停画面,抖音视频怎么让它有暂停的效果

千万别滥用视频特效&#xff0c;不然它能毁掉你的抖音作品。在创作过程中&#xff0c;应尽量使用类似暂停画面、隐形字幕这样的视觉特效&#xff0c;可以显著提高作品的视觉体验。增强视频表现力的同时&#xff0c;也不会让画面看起来过于夸张。有关抖音视频制作怎么暂停画面的…...

Android GPIO方式解码红外数据

1 红外遥控协议 1.1 基本概念 1&#xff09;NEC协议&#xff0c;采用PWM方式调制。38KHz载波&#xff0c;一般是由引导码地址码地址反码数据数据反码构成。 遥控接收头端收到的信号为&#xff1a;逻辑1是560us低1680us高&#xff0c;逻辑0是560us低560us高。 地址&#xff1a;u…...

基于SpringBoot+Vue的益农智慧服务平台【提供源码+答辩PPT+参考文档+项目部署】

一、项目技术架构&#xff1a; 本项目是一款SpringBoot益农平台的设计与实现。 该SpringBootVue的益农平台的设计与实现&#xff0c;后端采用SpringBoot架构&#xff0c;前端采用VueElementUI实现页面的快速开发&#xff0c;并使用关系型数据库MySQL存储系统运行数据。本系统分…...

基于springboot的在线考试与学习交流网页

作者&#xff1a;计算机学长阿伟 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、ElementUI等&#xff0c;“文末源码”。 系统展示 【2024最新】基于JavaSpringBootVueMySQL的&#xff0c;前后端分离。 开发语言&#xff1a;Java数据库&#xff1a;MySQL技术&#xff1a;…...

JS异步编程进阶(二):rxjs与Vue、React、Angular框架集成及跨框架状态管理实现原理

在现代前端开发中&#xff0c;异步操作已经成为不可或缺的一部分。无论是处理网络请求、响应用户输入&#xff0c;还是监听外部事件&#xff0c;异步编程模式始终占据重要位置。而RxJS作为功能强大的异步编程库&#xff0c;广泛应用于多个框架之中&#xff0c;如Vue、React、An…...

nginx web代理

目录 1.nginx的简单介绍 2.正向代理的应用场景 2.1做访问控制 2.2审计 2.3负载分散 2.4隐私保护和匿名性 3.反向代理的应用场景如下 3.1.负载均衡 2.缓存静态内容 3.压缩和优化内容 4.提供故障转移 5.安全性和匿名性 4.正向代理 4.1web端 4.2lb01代理服务器…...

ETLCloud可能遇到的问题有哪些?常见坑位解析

数据集成平台ETLCloud&#xff0c;主要用于支持数据的抽取&#xff08;Extract&#xff09;、转换&#xff08;Transform&#xff09;和加载&#xff08;Load&#xff09;过程。提供了一个简洁直观的界面&#xff0c;以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...

Redis数据倾斜问题解决

Redis 数据倾斜问题解析与解决方案 什么是 Redis 数据倾斜 Redis 数据倾斜指的是在 Redis 集群中&#xff0c;部分节点存储的数据量或访问量远高于其他节点&#xff0c;导致这些节点负载过高&#xff0c;影响整体性能。 数据倾斜的主要表现 部分节点内存使用率远高于其他节…...

力扣-35.搜索插入位置

题目描述 给定一个排序数组和一个目标值&#xff0c;在数组中找到目标值&#xff0c;并返回其索引。如果目标值不存在于数组中&#xff0c;返回它将会被按顺序插入的位置。 请必须使用时间复杂度为 O(log n) 的算法。 class Solution {public int searchInsert(int[] nums, …...

如何在网页里填写 PDF 表格?

有时候&#xff0c;你可能希望用户能在你的网站上填写 PDF 表单。然而&#xff0c;这件事并不简单&#xff0c;因为 PDF 并不是一种原生的网页格式。虽然浏览器可以显示 PDF 文件&#xff0c;但原生并不支持编辑或填写它们。更糟的是&#xff0c;如果你想收集表单数据&#xff…...

【分享】推荐一些办公小工具

1、PDF 在线转换 https://smallpdf.com/cn/pdf-tools 推荐理由&#xff1a;大部分的转换软件需要收费&#xff0c;要么功能不齐全&#xff0c;而开会员又用不了几次浪费钱&#xff0c;借用别人的又不安全。 这个网站它不需要登录或下载安装。而且提供的免费功能就能满足日常…...

人机融合智能 | “人智交互”跨学科新领域

本文系统地提出基于“以人为中心AI(HCAI)”理念的人-人工智能交互(人智交互)这一跨学科新领域及框架,定义人智交互领域的理念、基本理论和关键问题、方法、开发流程和参与团队等,阐述提出人智交互新领域的意义。然后,提出人智交互研究的三种新范式取向以及它们的意义。最后,总结…...

莫兰迪高级灰总结计划简约商务通用PPT模版

莫兰迪高级灰总结计划简约商务通用PPT模版&#xff0c;莫兰迪调色板清新简约工作汇报PPT模版&#xff0c;莫兰迪时尚风极简设计PPT模版&#xff0c;大学生毕业论文答辩PPT模版&#xff0c;莫兰迪配色总结计划简约商务通用PPT模版&#xff0c;莫兰迪商务汇报PPT模版&#xff0c;…...

腾讯云V3签名

想要接入腾讯云的Api&#xff0c;必然先按其文档计算出所要求的签名。 之前也调用过腾讯云的接口&#xff0c;但总是卡在签名这一步&#xff0c;最后放弃选择SDK&#xff0c;这次终于自己代码实现。 可能腾讯云翻新了接口文档&#xff0c;现在阅读起来&#xff0c;清晰了很多&…...

tomcat指定使用的jdk版本

说明 有时候需要对tomcat配置指定的jdk版本号&#xff0c;此时&#xff0c;我们可以通过以下方式进行配置 设置方式 找到tomcat的bin目录中的setclasspath.bat。如果是linux系统则是setclasspath.sh set JAVA_HOMEC:\Program Files\Java\jdk8 set JRE_HOMEC:\Program Files…...

CVPR2025重磅突破:AnomalyAny框架实现单样本生成逼真异常数据,破解视觉检测瓶颈!

本文介绍了一种名为AnomalyAny的创新框架&#xff0c;该方法利用Stable Diffusion的强大生成能力&#xff0c;仅需单个正常样本和文本描述&#xff0c;即可生成逼真且多样化的异常样本&#xff0c;有效解决了视觉异常检测中异常样本稀缺的难题&#xff0c;为工业质检、医疗影像…...