SpringMVC异步请求
背景
Tomcat等应用服务器的连接线程池实际上是有限制的;每一个连接请求都会耗掉线程池的一个连接数;如果某些耗时很长的操作,如对大量数据的查询操作、调用外部系统提供的服务以及一些 IO 密集型操作等,会占用连接很长时间,这个时候这个连接就无法被释放而被其它请求重用。如果连接占用过多,服务器就很可能无法及时响应每个请求;极端情况下如果将线程池中的所有连接耗尽,服务器将长时间无法向外提供服务
在常规场景中,客户端需要等待服务器处理完毕后返回才能继续进行其它操作,这个场景下每一步都是同步调用,如客户端调用 Servlet 后需要等待其处理返回,Servlet 调用具体的 Controller 后也需要等待其返回。这种情况是在服务器端开发中最常见的场景,适合于服务器端处理时间不是很长的情况;默认情况下 Spring 的 Controller 提供的就是这样的服务
当某项服务处理时间过长时,如邮件发送,需要调用到外部接口,处理时间不受调用方的控制,因此如果耗时过长会有两个比较严重的后果:一是如上文所说的会长时间的占用请求连接数,严重时有可能导致服务器失去响应; 二是客户端等待时间过长,导致前端应用的用户友好性下降,而且客户很有可能因为长时间得不到服务器响应而重复操作,从而加重服务器的负担,应用崩溃的机率变大!
为应对这种场景,一般会启用一个后台的线程池,处理请求的 Controller 会先提交一个耗时长操作如邮件发送到线程池中,然后立即返回到前台。因此处理响应的主线程耗时变短,客户感受到的就是在点击某个发送按钮后很快就得到服务器反馈结果,然后就放心的继续处理其它工作。实际上邮件发送这种事情延迟几秒对于客户来说根本感受不到。当然应用需要保证提交到线程池中的任务执行成功,或者是执行失败后在前端某个地方能够看到失败的具体情况
这种场景在 Spring 中可使用 TaskExecutor 或者是 Async 来处理,关于它们的用法请参考:Spring 基础学习-任务执行(TaskExecutor及Async)
通过以上两种场景,很容易就会想到,如果某个操作既耗时很长,客户端又必须要等待其返回才能进一步处理时,应该通过什么方式来处理?Servlet3.0 中引入异步请求处理来处理这种场景,相应的,Spring在3.2 版本中就引入相关机制来使用Servlet的该特性
SpringMVC 异步处理概述
为满足耗时任务占用应用服务器连接数,而客户端又必须等待这些耗时长任务返回才能处理下一步工作的场景,Spring 引入了以下机制来处理:
使用 Callable 或者 DeferredResult 作为 Controller 的返回值,能够处理异步返回单个结果的场景;使用 ResponseBodyEmitter/SseEmitter 或者StreamingResponseBody 来流式处理多个返回值;在 Controller 中使用响应式客户端调用服务并返回响应式的数据对象
Callable
Callable 直接使用在 Controller 中被 RequestMapping 所注解的方法上,做为其返回对象
使用示例
@RequestMapping("/testCallable")
public Callable < String > testCallable() {logger.info("Controller开始执行!");Callable < String > callable = () - > {Thread.sleep(5000);logger.info("实际工作执行完成!");return "succeed!";};logger.info("Controller执行结束!");return callable;
}
可以看到以下结果:
浏览器等待了大约5秒后返回结果
打印日志中,Controller 在 6ms 就执行结束
打印日志中,实际的任务执行在一个名称为 MvcAsync1 的线程中执行,并且在 Controller 执行完5s后才执行结束
因此可以得到结论:
返回 Callable 对象时,实际工作线程会在后台处理,Controller 无需等待工作线程处理完成,但 Spring 会在工作线程处理完毕后才返回客户端
它的执行流程是这样的
客户端请求服务
SpringMVC 调用 Controller,Controller 返回一个 Callback 对象
SpringMVC 调用 request.startAsync 并且将Callback提交到 TaskExecutor 中去执行
DispatcherServlet 以及 Filters 等从应用服务器线程中结束,但 Response 仍旧是打开状态,也就是说暂时还不返回给客户端
TaskExecutor 调用 Callback 返回一个结果,SpringMVC 将请求发送给应用服务器继续处理
DispatcherServlet 再次被调用并且继续处理 Callback 返回的对象,最终将其返回给客户端
DeferredResult
DeferredResult 使用方式与 Callable 类似,但在返回结果上不一样,它返回的时候实际结果可能没有生成,实际的结果可能会在另外的线程里面设置到 DeferredResult 中去
该类包含以下日常使用相关的特性:
超时配置:通过构造函数可以传入超时时间,单位为毫秒;因为需要等待设置结果后才能继续处理并返回客户端,如果一直等待会导致客户端一直无响应,因此必须有相应的超时机制来避免这个问题;就算不设置这个超时时间,应用服务器或者 Spring 也会有一些默认的超时机制来处理这个问题
结果设置:它的结果存储在一个名称为result的属性中;可以通过调用 setResult 的方法来设置属性;由于这个 DeferredResult 天生就是使用在多线程环境中的,因此对这个result属性的读写是有加锁的
接下来将对DeferredResult的处理流程进行说明,并实现一个较为简单的示例
DeferredResult 处理流程
DeferredResult 的处理过程与Callback类似,不一样的地方在于它的结果不是 DeferredResult 直接返回的,而是由其它线程通过同步的方式设置到该对象中,它的执行过程如下所示:
客户端请求服务
SpringMVC 调用 Controller,Controller 返回一个 DeferredResult 对象
SpringMVC 调用 request.startAsync
DispatcherServlet 以及 Filters 等从应用服务器线程中结束,但 Response 仍旧是打开状态,也就是说暂时还不返回给客户端
某些其它线程将结果设置到 DeferredResult 中,SpringMVC 将请求发送给应用服务器继续处理
DispatcherServlet 再次被调用并且继续处理 DeferredResult 中的结果,最终将其返回给客户端
DeferredResult 使用示例
本示例将在一个 Controller 中添加两个 RequestMapping 注解的方法,其中一个返回的是 DeferredResult 的对象,另外一个设置这个对象的值
注意:每个请求都要返回一个 新的 DeferredResult 对象,不能设置成单例,不能多个方法公用一个 DeferredResult
@RestController
@RequestMapping("/test")
public class TestController {private DeferredResult<String> deferredResult;/*** 返回DeferredResult对象*/@RequestMapping("/getDeferredResult")public DeferredResult <String> testDeferredResult() {deferredResult = new DeferredResult < > (5000 L, "请求失败(超时)"); // 设置 5 秒超时return deferredResult;}/*** 对DeferredResult的结果进行设置*/@RequestMapping("/setDeferredResult")public String setDeferredResult() {deferredResult.setResult("success");return "succeed";}
}
第一步先访问:http://localhost/test/getDeferredResult 此时客户端将会一直等待,5秒后会返回 “请求失败(超时)” 提醒
第二步新开页面访问:http://localhost/test/setDeferredResult 此时第一个页面会返回结果(第一个页面还没有返回超时错误的情况下)
上面只是演示了如何使用 DeferredResult 实现异步方法,我们发现,对于 /getDeferredResult 这个请求,每次都返回了新的 DeferredResult 对象,那么如何确保多个请求的时候,/setDeferredResult 能设值到对应的 DeferredResult?在实际开发中,需要对 DeferredResult 进行封装,一个简单的实现方式是把每次创建的新的 DeferredResult 放到 Queue(队列) 中,队列具有从尾部添加新元素,从头部获取新元素的特性,正好可以确保DeferredResult 的使用顺序,即先创建的先使用,后创建的后使用;参考示例代码如下
DeferredResult 封工具类
public class DeferredResultQueue {// 创建一个线程安全的链式队列private static Queue < DeferredResult > queue = new ConcurrentLinkedDeque < > ();// 添加 DeferredResult 方法, 从尾部添加public static void set(DeferredResult < Object > deferredResult) {queue.add(deferredResult);}// 获取并删除队列第一个 DeferredResult 对象public static DeferredResult < Object > get() {return queue.poll();}
}
所以上面的示例代码可修改为
@RestController
@RequestMapping("/test")
public class TestController {/*** 返回DeferredResult对象*/@RequestMapping("/getDeferredResult")public DeferredResult < String > testDeferredResult() {DeferredResult < String > deferredResult = new DeferredResult < > (5000 L, "请求失败(超时)"); // 设置 5 秒超时DefferredResultQueue.set(deferredResult); // 把 new 出来的 DeferredResult 对象放到工具类队列容器中return deferredResult;}/*** 对DeferredResult的结果进行设置*/@RequestMapping("/setDeferredResult")public String setDeferredResult() {DeferredResult < String > deferredResult = DefferredResultQueue.get(); // 获取到对应顺序的 DeferredResult 对象deferredResult.setResult("success");return "succeed";}
}
SseEmitter
Callback 和 DeferredResult 用于设置单个结果,如果有多个结果需要返回给客户端时,可以使用 SseEmitter 以及 ResponseBodyEmitter 等;
下面直接看示例,与 DeferredResult 的示例类似:
@RestController
@RequestMapping("/test")
public class TestController {private static final Logger logger = LoggerFactory.getLogger(TestController.class);private SseEmitter sseEmitter;/*** 返回SseEmitter对象*/@RequestMapping("/testSseEmitter")public SseEmitter testSseEmitter() {sseEmitter = new SseEmitter(10000 L); // 设置 10 秒超时return sseEmitter;}/*** 向SseEmitter对象发送数据*/@RequestMapping("/setSseEmitter")public String setSseEmitter() {try {sseEmitter.send(System.currentTimeMillis());} catch (IOException e) {logger.error("IOException!", e);return "error";}return "Succeed!";}/*** 将SseEmitter对象设置成完成*/@RequestMapping("/completeSseEmitter")public String completeSseEmitter() {sseEmitter.complete();return "Succeed!";}
}
第一步访问:http://localhost/test/testSseEmitter
第二步连续访问:http://localhost/test/setSseEmitter,第一步会不停返回第二步设置的值
第三步访问:http://localhost/test/completeSseEmitter
可以看到结果,只有当第三步执行后,第一步的访问才算结束
注意
(1)如果第一步请求时间已经到了,第二步没有设值,那么第一步所在请求会抛出异常
(2)如果第一步请求时间已经到了,第二步还在继续设值,那么第二步所在请求会抛出 ResponseBodyEmitter is already set complete 异常
(3)如果第三步执行,表示 sseEmitter 已被关闭,第二步执行就会抛出 ResponseBodyEmitter is already set complete 异常
StreamingResponseBody
用于直接将结果写出到 Response 的 OutputStream 中; 如文件下载等(可尝试文件下载进度实现),优点是不用考虑什么时候关闭流(实际上也根本无法确认什么时候操作完成)示例
@GetMapping("/download")
public StreamingResponseBody handle() {return new StreamingResponseBody() {@Overridepublic void writeTo(OutputStream outputStream) throws IOException {// write...}}
}
异步处理拦截器
在进行异步处理时,可以使用 CallableProcessingInterceptor 来对 Callback返回参数的情况进行拦截,也可以使用 DeferredResultProcessingInterceptor 来对 DeferredResult 的情况进行拦截。 也可以直接使用 AsyncHandlerInterceptor
拦截器的使用与普通拦截器一样,因此此处不再展开;具体可以参考:Spring Boot 拦截器示例及源码原理分析
注意:在使用异步机制前,要在 Web 配置,向 Servlet 添加对异步方法的支持
// 相当于配置 web.xml
public class WebInitializer implements WebApplicationInitializer {public void onStartup(ServletContext servletContext) throws ServletException {AnnotationConfigWebApplicationContext ctx = new AnnotationConfigWebApplicationContext();ctx.register(SpringConfig.class);ctx.setServletContext(servletContext);Dynamic servlet = servletContext.addServlet("dispatcher", new DispatcherServlet(ctx));servlet.addMapping("/");servlet.setLoadOnStartup(1);//开启异步方法支持servlet.setAsyncSupported(true);}
}
相关文章:
SpringMVC异步请求
背景 Tomcat等应用服务器的连接线程池实际上是有限制的;每一个连接请求都会耗掉线程池的一个连接数;如果某些耗时很长的操作,如对大量数据的查询操作、调用外部系统提供的服务以及一些 IO 密集型操作等,会占用连接很长时间&#…...
这七个100%提高Python代码性能的技巧,一定要知道
B站|公众号:啥都会一点的研究生 相关阅读 整理了几个100%会踩的Python细节坑,提前防止脑血栓 整理了十个100%提高效率的Python编程技巧,更上一层楼 Python-列表,从基础到进阶用法大总结,进来查漏补缺 Python-元组&…...
计算机网络笔记、面试八股(五)—— 浏览器输入URL
本章目录5. 从输入URL到浏览器显示页面过程中都发生了什么5.1 URL输入5.2 DNS解析5.2.1 域名的等级5.2.2 DNS解析的流程5.2.3 DNS查询方式5.3 建立TCP连接5.4 发送HTTP/HTTPS请求5.5 服务器处理请求并返回HTTP响应5.6 浏览器解析渲染页面5.7 HTTP请求结束,断开TCP连…...
【速记】快速调通算法项目的环境
1.创建新的conda环境,避免把原有的环境给搞坏。 在CMD中执行,而不是在anaconda的命令行中执行: conda create -n 环境名 --offline python3.8 2.在pycharm中配置conda环境: setting->Project Interpreter->齿轮->add-&g…...
开放开源开先河(上)
目录 1.唯一性定义品牌 2.打造爆款塑造品牌 3.构筑生态体系传播品牌 2022年7月28日,以“软件定义世界 开源共筑未来”为主题的全球数字经济大会开放原子开源峰会在北京开幕,承办主峰会和为捐赠人进行授牌仪式的开放原子开源基金会再次进入公众视野。基金…...
TencentOS 3.1安装MySQL 8.0.32
到官网下载安装包:https://dev.mysql.com/downloads/mysql/ 使用如下命令解包。 tar xf mysql-8.0.32-1.el8.x86_64.rpm-bundle.tar 使用rpm -qa |grep mysql 和rpm -qa |grep mariadb检查是否安装过mysql 如果有,使用下命令移除: rpm -e …...
Javascript的API基本内容(五)
一、js组成 JavaScript的组成 ECMAScript: 规定了js基础语法核心知识。 比如:变量、分支语句、循环语句、对象等等 Web APIs : DOM 文档对象模型, 定义了一套操作HTML文档的API BOM 浏览器对象模型,定义了一套操作浏览器窗口的API 二、loc…...
分层测试(2)单元测试【必备】
1. 什么是单元测试? 对代码中的逻辑隔离的最小代码片段进行测试,验证其逻辑是否符合预期,单元可以是函数,方法,类,功能模块。 2. 单元测试的优点 掌握代码:单元测试允许开发人员了解单元提供…...
代码随想录算法训练营day45 |动态规划之背包问题 70. 爬楼梯 (进阶) 322. 零钱兑换 279.完全平方数
day4570. 爬楼梯 (进阶)1. 确定dp数组以及下标的含义2.确定递推公式3.dp数组如何初始化4.确定遍历顺序5.举例来推导dp数组322. 零钱兑换1. 确定dp数组以及下标的含义2.确定递推公式3.dp数组如何初始化4.确定遍历顺序5.举例推导dp数组279.完全平方数1. 确…...
秒懂算法 | 基于图神经网络的推荐算法
图神经网络(Graph Neural Networks,GNN)是近几年兴起的学科,用来作推荐算法自然效果也相当好,但是要学会基于图神经网络的推荐算法之前,需要对图神经网络自身有个了解。 图卷积网络(Graph Convolutional Networks,GCN)提出于2017年。GCN 的出现标志着图神经网络的出现。深度学习…...
CANoe TC8测试脚本的结构介绍
CANoe TC8脚本是通过vTESTstudio平台编写。每个协议(ARP\ICMPv4\IPv4\UDP\TCP\SOMEIP\DHCP)都有自己的vtt文件。每个vtt文件的测试树结构为: Test Fixture Fixture Preparation Test Case Test Case … Test Case Test Case Fixture Completion 当Test Fixture里的Test Case…...
DP(4)--区间DP
将n(1≤n≤200)堆石子绕圆形操场摆放,现要将石子有次序地合并成一堆。 规定每次只能选相邻的两堆石子合并成新的一堆,并将新的一堆的石子数,记为该次合并的得分。 (1)选择一种合并石子的方案,使得做n-1次合并,得分的总…...
【C语言】“qsort函数详解”与“使用冒泡思想模拟使用qsort”
✨✨✨✨如果文章对你有帮助记得点赞收藏关注哦!!✨✨✨✨ 文章目录✨✨✨✨如果文章对你有帮助记得点赞收藏关注哦!!✨✨✨✨qsort的介绍:一、qsort函数的使用✨比较int类型数据比较字符型数据比较结构体数据冒泡思想…...
接口自动化框架---升级版(Pytest+request+Allure)
目录:导读 一、简单介绍 二、目录介绍 三、代码分析 写在最后 接口自动化是指模拟程序接口层面的自动化,由于接口不易变更,维护成本更小,所以深受各大公司的喜爱。 第一版入口:接口自动化框架(PytestrequestAllure…...
C语言循环语句简述
C 循环 有的时候,我们可能需要多次执行同一块代码。一般情况下,语句是按顺序执行的:函数中的第一个语句先执行,接着是第二个语句,依此类推。 编程语言提供了更为复杂执行路径的多种控制结构。 循环语句允许我们多次…...
STM32开发(16)----CubeMX配置DMA
CubeMX配置DMA前言一、什么是DMA?二、实验过程1.CubeMX配置2.代码实现3.实验结果总结前言 本章介绍使用STM32CubeMX对DMA进行配置的方法,DMA的原理、概念和特点,配置各个步骤的功能,并通过串口DMA传输实验方式验证。 一、什么是…...
让物流园区可视可控,顺丰供应链与亚马逊云科技的供应链新解法
导读:物流园区如何破解供应链断点?在物流园区附近,我们经常看到周边道路停满了集装箱卡车。这是物流园区的一个典型痛点,由于园区内部业务情况的不可见性,司机们往往到了园区才被告知业务繁忙,需要长时间排…...
2023年3月北京/西安/广州/深圳DAMA-CDGA/CDGP数据治理认证报名
DAMA认证为数据管理专业人士提供职业目标晋升规划,彰显了职业发展里程碑及发展阶梯定义,帮助数据管理从业人士获得企业数字化转型战略下的必备职业能力,促进开展工作实践应用及实际问题解决,形成企业所需的新数字经济下的核心职业…...
「TCG 规范解读」TCG 主规范-设计原则
可信计算组织(Ttrusted Computing Group,TCG)是一个非盈利的工业标准组织,它的宗旨是加强在相异计算机平台上的计算环境的安全性。TCG于2003年春成立,并采纳了由可信计算平台联盟(the Trusted Computing Platform Alliance,TCPA)所开发的规范。现在的规范都不是最终稿,都…...
【Spring源码】Spring AOP的核心概念
废话版什么是AOP关于什么是AOP,这里还是要简单介绍下AOP,Aspect Oriented Programming,面向切面编程,通过预编译和运行期间提供动态代理的方式实现程序功能的统一维护,使用AOP可以降低各个部分的耦合度,提高…...
华为OD机试用Python实现 -【任务混部】(2023-Q1 新题)
华为OD机试题 华为OD机试300题大纲任务混部题目输入输出示例一输入输出说明示例二输入输出说明备注Code代码编写思路华为OD机试300题大纲 参加华为od机试,一定要注意不要完全背诵代码,需要理解之后模仿写出,通过率才会高。 华为 OD 清单查看地址:blog.csdn.net/hihell/ca…...
Linux yum 命令
yum( Yellow dog Updater, Modified)是一个在 Fedora 和 RedHat 以及 SUSE 中的 Shell 前端软件包管理器。 基于 RPM 包管理,能够从指定的服务器自动下载 RPM 包并且安装,可以自动处理依赖性关系,并且一次安装所有依赖…...
package.json 字段配置
文章目录环境导入相关main 和 modulewebpack resolve.mainFieldsbrowserexports定义其他模块根据导入语句导出嵌套环境导出vue中 exports 用法自定义运行环境环境导入相关 main 和 module 根据导入模块时不同的模块规范语句查找不同的入口文件 "main": "dist…...
springboot中集成redis,二次封装成工具类
大家好,我是雄雄,欢迎关注微信公众号:** 雄雄的小课堂 ** 现在是:2023年2月28日11:01:56 前言 redis大家应该都不陌生,我们在好多场景下都会使用,最近在面试别人的时候,也会问一些关于redis的…...
Linux Vim 简介
文章目录01. 编辑器 Gedit 介绍02. 什么是 Vi(Vim)03. vim工作模式4.1 命令模式4.2 编辑模式4.3 末行模式04. vim教程05. vim基本操作06. vim实用操作7.1 命令模式下的操作7.2 末行模式下的操作01. 编辑器 Gedit 介绍 gedit 是一个 GNOME 桌面环境下兼容 UTF-8 的 文本编辑器。…...
软件测试面试题 —— 整理与解析(2)
😏作者简介:博主是一位测试管理者,同时也是一名对外企业兼职讲师。 📡主页地址:🌎【Austin_zhai】🌏 🙆目的与景愿:旨在于能帮助更多的测试行业人员提升软硬技能…...
HashMap与Hashtable的这九个区别,你知道吗
Hashtable Hashtable是原始的java.util的一部分,属于一代集合类,是一个Dictionary具体的实现 。Java1.2重构的Hashtable实现了Map接口,因此,Hashtable现在集成到了集合框架中。它和HashMap类很相似。 Hashtable与HashMap的区别 …...
Java奠基】掌握Java基础知识
目录 常见字面量 特殊字面量 数据类型 标识符 键盘录入 常见字面量 字面量就是数据在程序中的书写格式,字面量的分类如下: 字面量类型说明举例整数类型不带小数点的数字12,25小数类型带小数点的数字3.14,-5,20…...
Hive窗口函数-lead/lag函数
前面我们学习的first_value和last_value 取的是排序后的数据截止当前行的第一行数据和最后一行数据 Lag和Lead分析函数可以在一次查询中取出当前行后N行和前N行的数据,虽然可以不用排序,但是往往只有在排序的场景下取前面或者后面N 行数据才有意义 这种…...
2023JAVA面试题全集超全面超系统超实用!早做准备早上岸
2022年我凭借一份《Java面试核心知识点》成功拿下了阿里、字节、小米等大厂的offer,两年的时间,为了完成我给自己立的flag(拿下一线互联网企业offer大满贯),即使在职也一直在不断的学习与备战面试中!——或…...
网站基本要素/佛山seo优化外包
写在前面 本文严禁转载,只限于学习交流。课件分享在这里了。还有人工智能标准化白皮书(2018版)也一并分享了。 文章目录绪论人工智能的定义与发展定义起源与发展各种认知观目前人工智能主要有以下三个学派:几种学派各自不足之处:研究目标与内…...
装修网站应该怎么做/冯耀宗seo博客
有关剑指offer题目的解析:剑指offer 题目整理 题目要求 请实现一个函数按照之字形打印二叉树,即第一行按照从左到右的顺序打印,第二层按照从右至左的顺序打印,第三行按照从左到右的顺序打印,其他行以此类推。 如&…...
ps做网站的视频/软文代写多少钱一篇
1、修改数据库(修改字符集) alter database 数据库名称 character set 字符集名称; 2、删除数据库 drop database 数据库名称; 3、删除数据库(判断,如果存在则删除) drop database if exist 数据库名称; 4…...
枣庄专业做网站/seo优化的价格
链接:https://ac.nowcoder.com/acm/contest/330/E 来源:牛客网 题目描述 精通程序设计的 Applese 叕写了一个游戏。 在这个游戏中,有一个 n 行 m 列的方阵。现在它要为这个方阵涂上黑白两种颜色。规定左右相邻两格的颜色不能相同。请你帮它统…...
域名停靠app盘他射门下载/厦门seo百度快照优化
查看当前时区 ls -l /etc/localtime我目前这台服务器的时区时美国纽约时间 查看时区选择列表 tzselect执行tzselect后会出现上图的选择内容,我们输入5来选择亚洲时区,接着会有下图 然后接着输入9,表示选择中国 接着选择北京时间或者新疆…...
wordpress调用服务器/如何开网站呢
idstart_timeperiod_ytpeperiod_value11461427200day321461427200month2如上表,start_time 表示开始时间,period_ytpe 表示期限类型,period_value表示期限值,第一第记录表是 3天,第二条表示2个月如果查询出,…...