DeferredResult使用方式和场景
为什么使用DeferredResult?
API接口需要在指定时间内将异步操作的结果同步返回给前端时;
Controller处理耗时任务,并且需要耗时任务的返回结果时;
当一个请求到达API接口,如果该API接口的return返回值是DeferredResult,在没有超时或者DeferredResult对象设置setResult时,接口不会返回,但是Servlet容器线程会结束,DeferredResult另起线程来进行结果处理(即这种操作提升了服务短时间的吞吐能力),并setResult,如此以来这个请求不会占用服务连接池太久,如果超时或设置setResult,接口会立即返回。
使用DeferredResult的流程:
- 浏览器发起异步请求
- 请求到达服务端被挂起
- 向浏览器进行响应,分为两种情况:
3.1 调用DeferredResult.setResult(),请求被唤醒,返回结果
3.2 超时,返回一个你设定的结果 - 浏览得到响应,再次重复1,处理此次响应结果
给人一种异步处理业务,但是却同步返回的感觉。
场景
浏览器向A系统发起请求,该请求需要等到B系统(如MQ)给A推送数据时,A才会立刻向浏览器返回数据;
如果指定时间内B未给A推送数据,则返回超时。
Demo代码
接口代码:
/get是调用A系统的接口返回数据;
/result模拟B系统向A推送数据进行setResult。
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.context.request.async.DeferredResult; @RestController
@RequestMapping(value = "/deferred-result")
public class DeferredResultController { @Autowired
private DeferredResultService deferredResultService; /**
* 为了方便测试,简单模拟一个
* 多个请求用同一个requestId会出问题
*/
private final String requestId = "haha"; @GetMapping(value = "/get")
public DeferredResult<DeferredResultResponse> get(@RequestParam(value = "timeout", required = false, defaultValue = "10000") Long timeout) {
DeferredResult<DeferredResultResponse> deferredResult = new DeferredResult<>(timeout); deferredResultService.process(requestId, deferredResult); return deferredResult;
} /**
* 设置DeferredResult对象的result属性,模拟异步操作
* @param desired
* @return
*/
@GetMapping(value = "/result")
public String settingResult(@RequestParam(value = "desired", required = false, defaultValue = "成功") String desired) {
DeferredResultResponse deferredResultResponse = new DeferredResultResponse();
if (DeferredResultResponse.Msg.SUCCESS.getDesc().equals(desired)){
deferredResultResponse.setCode(HttpStatus.OK.value());
deferredResultResponse.setMsg(desired);
}else{
deferredResultResponse.setCode(HttpStatus.INTERNAL_SERVER_ERROR.value());
deferredResultResponse.setMsg(DeferredResultResponse.Msg.FAILED.getDesc());
}
deferredResultService.settingResult(requestId, deferredResultResponse); return "Done";
}
}
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.web.context.request.async.DeferredResult; import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
import java.util.function.Consumer; @Service
public class DeferredResultService { private Map<String, Consumer<DeferredResultResponse>> taskMap; public DeferredResultService() {
taskMap = new ConcurrentHashMap<>();
} /**
* 将请求id与setResult映射
*
* @param requestId
* @param deferredResult
*/
public void process(String requestId, DeferredResult<DeferredResultResponse> deferredResult) {
// 请求超时的回调函数
deferredResult.onTimeout(() -> {
taskMap.remove(requestId);
DeferredResultResponse deferredResultResponse = new DeferredResultResponse();
deferredResultResponse.setCode(HttpStatus.REQUEST_TIMEOUT.value());
deferredResultResponse.setMsg(DeferredResultResponse.Msg.TIMEOUT.getDesc());
deferredResult.setResult(deferredResultResponse);
}); Optional.ofNullable(taskMap)
.filter(t -> !t.containsKey(requestId))
.orElseThrow(() -> new IllegalArgumentException(String.format("requestId=%s is existing", requestId))); // 这里的Consumer,就相当于是传入的DeferredResult对象的地址
// 所以下面settingResult方法中"taskMap.get(requestId)"就是Controller层创建的对象
taskMap.putIfAbsent(requestId, deferredResult::setResult);
} /**
* 这里相当于异步的操作方法
* 设置DeferredResult对象的setResult方法
*
* @param requestId
* @param deferredResultResponse
*/
public void settingResult(String requestId, DeferredResultResponse deferredResultResponse) {
if (taskMap.containsKey(requestId)) {
Consumer<DeferredResultResponse> deferredResultResponseConsumer = taskMap.get(requestId);
// 这里相当于DeferredResult对象的setResult方法
deferredResultResponseConsumer.accept(deferredResultResponse);
taskMap.remove(requestId);
}
} }
import lombok.Data;
import lombok.Getter; @Data
public class DeferredResultResponse {
private Integer code;
private String msg; public enum Msg {
TIMEOUT("超时"),
FAILED("失败"),
SUCCESS("成功"); @Getter
private String desc; Msg(String desc) {
this.desc = desc;
}
}
}
测试
1. 超时
设置超时时间为8s,会一直阻塞8s,如果超时,返回默认超时的结果

8s过去,没有setResult,返回超时

2. 进行setResult
在阻塞期间调用setResult,我这里模拟的是B系统推送数据给A时抛异常失败的情况,会立刻得到返回结果


参考:
https://my.oschina.net/ojeta/blog/806087
DeferredResult使用方式和场景的更多相关文章
- 物无定味适口者珍,Python3并发场景(CPU密集/IO密集)任务的并发方式的场景抉择(多线程threading/多进程multiprocessing/协程asyncio)
原文转载自「刘悦的技术博客」https://v3u.cn/a_id_221 一般情况下,大家对Python原生的并发/并行工作方式:进程.线程和协程的关系与区别都能讲清楚.甚至具体的对象名称.内置方法 ...
- 性能测试之Jmeter中场景设置与启动方式
Jmeter场景设置与启动方式 性能测试场景是用来模拟模拟真实用户操作的工作单元,所以场景设计一定要切合用户的操作逻辑,jmeter主要是通过线程组配合其他组件来一起完成场景的设置. 线程组设置 Jm ...
- Spring Web Async异步处理#Callable #DeferredResult
Spring MVC 对于异步请求处理的两种方式 场景: Tomcat对于主线程性能瓶颈,当Tomcat请求并发数过多时,当线程数满时,就会出现请求等待Tomcat处理,这个时候可以使用子线程处理业务 ...
- LR12.53—第5课:创建负载测试场景
在前面的课程中,您使用VuGen将验证您的Vuser脚本.在本课中,您将评估多个Vuser的负载下您的系统.您将模拟十个旅行代理同时使用航班预订系统的行动,以及这些用户的负载下观察系统的行为.设计和运 ...
- Spring工厂方式创建Bean实例
创建Bean实例的方式: 1) 通过构造器(有参或无参) 方式: <bean id="" class=""/> 2) 通过静态工厂方法 方式: &l ...
- 3DMAX 建立场景 工作流程
建立3D渲染首先建立房型.毕竟我们在做的是三维房间的渲染.建立房型线有几个环节都要用到 我们看一眼最终的渲染效果. 利用我们第一步建立的房型线做模型 房型线通过膨胀变成墙壁 再通过房型线生成屋顶天花和 ...
- LoadRunner的场景设置
loadrunner场景设置的方法: 1.逐步增加用户数,分多次去运行场景.比如:第一次运行50并发,第二次运行100并发…… 2.针对同一个脚本设置多个组,使用组策略(点击Edit Schedule ...
- Cocos2d-x 3.0 场景切换
场景切换要用到导演类Director,一般有两种方式,大多数是用替换场景(replaceScene),也可以用进栈(pushScene)出栈(popScene)的方式进行场景的替换. 场景切换代码: ...
- LoadRunner场景设置
创建负载测试场景场景目标:模拟10家旅行社同时登陆.搜索航班.购买机票.查看航班路线并退出打开Controller并创建一个新场景1.打开HP LoadRunner2.打开Controller在Loa ...
随机推荐
- [LeetCode] 330. Patching Array 数组补丁
Given a sorted positive integer array nums and an integer n, add/patch elements to the array such th ...
- swoole实战1-初识swoole
原文地址:https://www.jianshu.com/p/008d5702d01f 安装swoole 以mac操作系统为例,如果你是mac新手,推荐阅读 程序员如何优雅使用mac 环境要求:php ...
- 关于最火爆的服务器Tomcat你真的了解吗?
作为Apache 软件基金会的Jakarta 项目中的一个核心项目,Tomcat因技术先进.性能稳定,而且免费,因而深受Java 爱好者的喜爱并得到了部分软件开发商的认可,成为目前比较流行的Web 应 ...
- MySQL数据库-表操作-SQL语句(一)
1. 数据库操作与存储引擎 1.1 数据库和数据库对象 数据库对象:存储,管理和使用数据的不同结构形式,如:表.视图.存储过程.函数.触发器.事件等. 数据库:存储数据库对象的容器. 数据库分两种 ...
- lmir 随笔
近期需要研究一些特征工程的工作,就打算把微软之前公布出来的特征都复现一遍,今天遇到的特征是 LMIR, 其实也就是language model for information retrieval的简写 ...
- spring boot + vue实现图片上传及展示
转载:https://blog.csdn.net/weixin_40337982/article/details/84031778 其中一部分对我很有帮助 转载记录下 首先,html页面: <! ...
- win7远程服务器发生身份验证错误,要求的函数不受支持
远程服务器发生身份验证错误,要求的函数不受支持,远程登录服务器以前都是正常的,今天登录远程桌面就一直是这样的错误.记录一下解决方法. 方法一:卸载补丁KB41037181.打开控制面板,找到“程序和功 ...
- tomcat配置外部静态资源映射路径(windows和Linux部署)
如果你不想用ngnix配置的话,只单独使用tomcat的话可以看看这篇文章,接下来开始 使用场景 1.单机开发有时侯如果放在war下每次clean都会清理当前项目下静态文件特别折腾. 2.只有启动to ...
- Nginx惊群问题
Nginx惊群问题 "惊群"概念 所谓惊群,可以用一个简单的比喻来说明: 一群等待食物的鸽子,当饲养员扔下一粒谷物时,所有鸽子都会去争抢,但只有少数的鸽子能够抢到食物, 大部分鸽子 ...
- ApachShiro 一个系统 两套验证方法-(后台管理员登录、前台App用户登录)同一接口实现、源码分析
需求: 在公司新的系统里面博主我使用的是ApachShiro 作为安全框架.作为后端的鉴权以及登录.分配权限等操作 管理员的信息都是存储在管理员表 前台App 用户也需要校验用户名和密码进行登录.但是 ...