1、背景
在项目开发中,有一个流程性的方法执行,这个方法会调用各种方法,可能会导致时间比较长 ,如果一直等待响应结果的话,可能会造成超时,如果直接使用异步的方式的话,前端无法知道整体流程什么时候会结束,
2、解决方案
使用了DeferredResult 的方式,设置超时时间,当流程执行完了没有超过指定时间就可以直接返回结果,如果超过了指定时间就给前端先返回超时结果,并且指定一个唯一标志放到结果中返回,前端后续可以拿着这个唯一标志来轮询,知道返回执行完成
关于 DeferredResult :请求的处理线程(即 tomcat 线程池的线程)不会等DeferredResult#setResult() 被调用才释放,而是直接释放了。
也就是说 tomcat 线程安排好 DeferredResult 的一些配置后,不会等逻辑处理完(DeferredResult->setResult()的调用或者超时)。
而是直接释放了,这样 tomcat 线程就被回收到线程池中了,可以响应其他请求,不会傻傻地阻塞等着 DeferredResult->setResult() 被调用或超时。
我们都知道 tomcat 的线程池大小是有限的,如果我们的一些业务逻辑处理慢的话,会渐渐地占满 tomcat 线程,这样就无法处理新的请求,所以一些处理缓慢的业务我们会放到业务线程池中处理,但单纯的放到业务线程池中处理的话,我们无法得知其什么时候处理完,也无法将处理完的结果和之前的请求匹配上,所以常做的方式就是轮询。
而 DeferredResult 的做法就类似仅把事情安排好,不会管事情做好没,tomcat 线程就释放走了,注意此时不会给请求方(如浏览器)任何响应,而是将请求存放在一边,等后面有结果了再把之前的请求拿来,把值响应给请求方。
用简单的话来总结下 Spring DeferredResult :如果返回值类型是 DeferredResult 则表明其是异步请求,tomcat 线程不会等到应用程序处理完或者超时,而是会立即释放线程。
而这个未处理完的请求则会暂存,tomcat 知晓其为异步请求,也不会对客户端进行响应,直至 tomcat 线程扫描到请求超时或者应用线程将 result 塞入到 DeferredResult 中。
3、一些常用方法
public void onTimeout(Runnable callback)
public void onError(Consumer<Throwable> callback)
public void onCompletion(Runnable callback)
public boolean setResult(T result)
onTimeout()
:仅超时触发。onError()
:仅异步任务抛出异常时触发。onCompletion()
是兜底回调,无论何种结束方式都会执行,适合释放共享资源(如移除缓存、关闭连接)- setResult() 设置返回结果集
4、具体简单代码实现
// -1 表示任务未完成 0 表示任务失败 其它是具体值
private static final Map<String,String> EXEC_CACHE = new ConcurrentHashMap<>();
@Override
public DeferredResult<Map<String, String>> exec() {
DeferredResult<Map<String, String>> deferredResult = new DeferredResult<>(5000L);
// 设置超时回调(如果任务未在 2 秒内完成,返回超时响应)
String flag = UUID.randomUUID().toString();
Map<String, String> result = new HashMap<>();
deferredResult.onTimeout(() -> {
result.put("code", "410");
result.put("message", "任务未完成,已超时!");
result.put("flag",flag);
deferredResult.setResult(result);
EXEC_CACHE.put(flag, "-1");
});
//执行具体任务
CompletableFuture.runAsync(()->{
try {
//模拟执行耗时
Thread.sleep(1000);
//模拟获到执行结果
String execResult = UUID.randomUUID().toString();
//放入缓存
EXEC_CACHE.put(flag, execResult);
//完成响应
result.put("code", "200");
result.put("message", "任务完成");
result.put("flag",execResult);
deferredResult.setResult(result);
}catch (Exception e){
//完成响应
result.put("code", "500");
result.put("message", "任务完成");
result.put("flag",null);
deferredResult.setResult(result);
EXEC_CACHE.put(flag, "0");
}
});
return deferredResult;
}
@Override
public String queryExecResult(String flag) {
//前端根据具体值判断要不要继续轮询
return EXEC_CACHE.get(flag);
}
执行超时情况
轮询查询
未超时返回