007-优化web请求三-异步调用【WebAsyncTask】
一、什么是同步调用
浏览器发起请求,Web服务器开一个线程处理,处理完把处理结果返回浏览器。好像没什么好说的了,绝大多数Web服务器都如此般处理。现在想想如果处理的过程中需要调用后端的一个业务逻辑服务器
请求处理线程会在Call了之后等待Return,自身处于阻塞状态。这也是绝大多数Web服务器的做法。一般此种做法主要适用于,后端处理响应比较快,并且并发数比较低的情况。
主要弊端,在高并发请求下,请求处理线程的短缺!因为请求处理线程的总数是有限的,如果类似的请求多了,所有的处理线程处于阻塞的状态,那新的请求也就无法处理了,也就所谓影响了服务器的吞吐能力。要更加好地发挥服务器的全部性能,就要使用异步。
二、什么是异步
最大的不同在于请求处理线程对后台处理的调用使用了“invoke”的方式,就是说调了之后直接返回,而不等待,这样请求处理线程就“自由”了,它可以接着去处理别的请求,当后端处理完成后,会钩起一个回调处理线程来处理调用的结果,这个回调处理线程跟请求处理线程也许都是线程池中的某个线程,相互间可以完全没有关系,由这个回调处理线程向浏览器返回内容。这就是异步的过程。
带来的改进是显而易见的,请求处理线程不需要阻塞了,它的能力得到了更充分的使用,带来了服务器吞吐能力的提升。
三、使用Spring MVC 和Servlet3异步线程
3.1、前提
要使用Spring MVC的异步功能,你得先确保你用的是Servlet 3.0或以上的版本,Maven中如此配置:
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
Spring MVC 3.2以后版本开始引入了基于Servlet3的异步请求处理
3.2、概述
相比以前,控制器方法已经不一定需要一个值,而是可以直接返回一个Callable对象,并通过Spring MVC所管理的线程来产生返回值,与此同时,Servlet容器的主线程则可以退出并释放其资源,同时也允许容器去处理其它请求。通过一个TaskExecutor,Spring MVC可以在另外的线程中调用Callable。当Callable返回时,请求在携带Callable返回的值,再次被分配到Servlet容器中恢复处理流程。
官方文档中说DeferredResult和Callable都是为了异步生成返回值提供基本的支持。简单来说就是一个请求进来,如果你使用了DeferredResult或者Callable,在没有得到返回数据之前,DispatcherServlet和所有Filter就会退出Servlet容器线程,但响应保持打开状态,一旦返回数据有了,这个DispatcherServlet就会被再次调用并且处理,以异步产生的方式,向请求端返回值。
这么做的好处就是请求不会长时间占用服务连接池,提高服务器的吞吐量。
1、Servlet 3.0异步请求运作机制的部分原理
a.Servlet请求ServletRequest可以通过调用request.startAsync()方法而进入异步模式,这样做的主要结果就是该Servlet以及所有的过滤器都可以结束但其相应(Response)会留待异步处理结束后在返回调用。
b.request.startAsync()方法会返回一个AsyncContext对象,可以用他对异步处理进行进一步的控制和操作,比如说他也提供了一个与反转(forward)很相似的dispatch方法,只不过他允许应用恢复Servlet容器的请求处理进程。
c.ServletRequest提供了获取当前DispatherType的方式,后者可以用来区别当前处理的是原始请求,异步分发请求,转向或者是其它类型的请求分发类型。
2、Callable的异步请求被处理时所发生的事件
官方介绍
Controller returns a Callable.
Spring MVC calls request.startAsync() and submits the Callable to a TaskExecutor for processing in a separate thread.
Meanwhile the DispatcherServlet and all Filter’s exit the Servlet container thread but the response remains open.
Eventually the Callable produces a result and Spring MVC dispatches the request back to the Servlet container to complete processing.
The DispatcherServlet is invoked again and processing resumes with the asynchronously produced return value from the Callable.
1》Controller返回Callable
2》Spring MVC调用request.startAsync()并将Callable提交给TaskExecutor,以便在单独的线程中进行处理。
3》同时DispatcherServlet和所有Filter都退出Servlet容器线程,但响应仍保持打开状态。
4》最终,Callable生成一个结果,Spring MVC将请求调度回Servlet容器以完成处理。
5》再次调用DispatcherServlet,并使用来自Callable的异步生成的返回值继续处理。
流程上大体与DeferredResult
类似,只不过Callable
是由TaskExecutor
来处理的,而TaskExecutor
继承自java.util.concurrent.Executor
。我们来看一下它的源代码,它也是在WebAysncManager
中处理的:
/**
* Use the given {@link WebAsyncTask} to configure the task executor as well as
* the timeout value of the {@code AsyncWebRequest} before delegating to
* {@link #startCallableProcessing(Callable, Object...)}.
* @param webAsyncTask a WebAsyncTask containing the target {@code Callable}
* @param processingContext additional context to save that can be accessed
* via {@link #getConcurrentResultContext()}
* @throws Exception if concurrent processing failed to start
*/
public void startCallableProcessing(final WebAsyncTask<?> webAsyncTask, Object... processingContext) throws Exception {
Assert.notNull(webAsyncTask, "WebAsyncTask must not be null");
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null"); Long timeout = webAsyncTask.getTimeout();
if (timeout != null) {
this.asyncWebRequest.setTimeout(timeout);
} AsyncTaskExecutor executor = webAsyncTask.getExecutor();
if (executor != null) {
this.taskExecutor = executor;
} List<CallableProcessingInterceptor> interceptors = new ArrayList<CallableProcessingInterceptor>();
interceptors.add(webAsyncTask.getInterceptor());
interceptors.addAll(this.callableInterceptors.values());
interceptors.add(timeoutCallableInterceptor); final Callable<?> callable = webAsyncTask.getCallable();
final CallableInterceptorChain interceptorChain = new CallableInterceptorChain(interceptors); this.asyncWebRequest.addTimeoutHandler(new Runnable() {
@Override
public void run() {
logger.debug("Processing timeout");
Object result = interceptorChain.triggerAfterTimeout(asyncWebRequest, callable);
if (result != CallableProcessingInterceptor.RESULT_NONE) {
setConcurrentResultAndDispatch(result);
}
}
}); this.asyncWebRequest.addCompletionHandler(new Runnable() {
@Override
public void run() {
interceptorChain.triggerAfterCompletion(asyncWebRequest, callable);
}
}); interceptorChain.applyBeforeConcurrentHandling(this.asyncWebRequest, callable);
startAsyncProcessing(processingContext);
//启动线程池的异步处理
try {
this.taskExecutor.submit(new Runnable() {
@Override
public void run() {
Object result = null;
try {
interceptorChain.applyPreProcess(asyncWebRequest, callable);
result = callable.call();
}
catch (Throwable ex) {
result = ex;
}
finally {
result = interceptorChain.applyPostProcess(asyncWebRequest, callable, result);
}
//设置当前的结果并转发
setConcurrentResultAndDispatch(result);
}
});
}
catch (RejectedExecutionException ex) {
Object result = interceptorChain.applyPostProcess(this.asyncWebRequest, callable, ex);
setConcurrentResultAndDispatch(result);
throw ex;
}
}
对比DeferredResult
,在这里刚开始也是添加拦截器,只不过拦截器的名称是CallableProcessingInterceptor
,同时也需要设置WebAsyncRequest的超时处理,完成时处理的响应操作。这其中最大的区别就是使用TaskExecutor
来对Callable
进行异步处理
3、DeferredResult对象请求的处理顺序也非常类似,区别在于应用可以通过任何线程来计算返回一个结果
官网描述
DeferredResult processing: Controller returns a DeferredResult and saves it in some in-memory queue or list where it can be accessed.
Spring MVC calls request.startAsync().
Meanwhile the DispatcherServlet and all configured Filter’s exit the request processing thread but the response remains open.
The application sets the DeferredResult from some thread and Spring MVC dispatches the request back to the Servlet container.
The DispatcherServlet is invoked again and processing resumes with the asynchronously produced return value.
1》将Controller返回的DeferredResult
值保存到内存队列或集合当中以便存取
2》SpringMVC调用HttpServletRequest
的startAsync()
方法,异步处理
3》同时,DispatcherServlet和所有已配置的Filter都退出请求处理线程,但响应仍保持打开状态,此时方法的响应对象仍未返回。
4》应用程序从某个线程设置DeferredResult,Spring MVC将请求调度回Servlet容器,恢复处理
5》再次调用DispatcherServlet,并使用异步生成的返回值继续处理
源码分析:
当一个请求被DispatcherServlet
处理时,会试着获取一个WebAsyncManager
对象
protected void doDispatch(HttpServletRequest request, HttpServletResponse response) throws Exception {
HttpServletRequest processedRequest = request;
HandlerExecutionChain mappedHandler = null;
boolean multipartRequestParsed = false; // 获取WebAsyncManager
WebAsyncManager asyncManager = WebAsyncUtils.getAsyncManager(request);
try {
// ......省略部分代码
// 执行子控制器的方法
mv = ha.handle(processedRequest, response, mappedHandler.getHandler());
//如果当前的请求需要异步处理,则终止当前请求,但是响应是打开的
if (asyncManager.isConcurrentHandlingStarted()) {
return;
}
//....省略部分代码
}
//....省略部分代码
}
对于每一个子控制器的方法返回值,都是HandlerMethodReturnValueHandler接口处理的,其中有一个实现类是DeferredResultMethodReturnValueHandler,关键代码如下:
package org.springframework.web.servlet.mvc.method.annotation; import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.CompletionStage;
import java.util.function.BiFunction; import org.springframework.core.MethodParameter;
import org.springframework.lang.UsesJava8;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;
import org.springframework.util.concurrent.ListenableFuture;
import org.springframework.util.concurrent.ListenableFutureCallback;
import org.springframework.web.context.request.NativeWebRequest;
import org.springframework.web.context.request.async.DeferredResult;
import org.springframework.web.context.request.async.WebAsyncUtils;
import org.springframework.web.method.support.AsyncHandlerMethodReturnValueHandler;
import org.springframework.web.method.support.ModelAndViewContainer; /**
* Handler for return values of type {@link DeferredResult}, {@link ListenableFuture},
* {@link CompletionStage} and any other async type with a {@link #getAdapterMap()
* registered adapter}.
*
* @author Rossen Stoyanchev
* @since 3.2
*/
@SuppressWarnings("deprecation")
public class DeferredResultMethodReturnValueHandler implements AsyncHandlerMethodReturnValueHandler { //存放DeferredResult的适配集合
private final Map<Class<?>, DeferredResultAdapter> adapterMap; public DeferredResultMethodReturnValueHandler() {
this.adapterMap = new HashMap<Class<?>, DeferredResultAdapter>(5);
this.adapterMap.put(DeferredResult.class, new SimpleDeferredResultAdapter());
this.adapterMap.put(ListenableFuture.class, new ListenableFutureAdapter());
if (ClassUtils.isPresent("java.util.concurrent.CompletionStage", getClass().getClassLoader())) {
this.adapterMap.put(CompletionStage.class, new CompletionStageAdapter());
}
} /**
* Return the map with {@code DeferredResult} adapters.
* <p>By default the map contains adapters for {@code DeferredResult}, which
* simply downcasts, {@link ListenableFuture}, and {@link CompletionStage}.
* @return the map of adapters
* @deprecated in 4.3.8, see comments on {@link DeferredResultAdapter}
*/
@Deprecated
public Map<Class<?>, DeferredResultAdapter> getAdapterMap() {
return this.adapterMap;
} private DeferredResultAdapter getAdapterFor(Class<?> type) {
for (Class<?> adapteeType : getAdapterMap().keySet()) {
if (adapteeType.isAssignableFrom(type)) {
return getAdapterMap().get(adapteeType);
}
}
return null;
} @Override
public boolean supportsReturnType(MethodParameter returnType) {
return (getAdapterFor(returnType.getParameterType()) != null);
} @Override
public boolean isAsyncReturnValue(Object returnValue, MethodParameter returnType) {
return (returnValue != null && (getAdapterFor(returnValue.getClass()) != null));
} @Override
public void handleReturnValue(Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception { if (returnValue == null) {
mavContainer.setRequestHandled(true);
return;
}
//根据返回值的类型获取对应的DeferredResult适配器
DeferredResultAdapter adapter = getAdapterFor(returnValue.getClass());
if (adapter == null) {
throw new IllegalStateException(
"Could not find DeferredResultAdapter for return value type: " + returnValue.getClass());
}
DeferredResult<?> result = adapter.adaptToDeferredResult(returnValue);
//开启异步请求
WebAsyncUtils.getAsyncManager(webRequest).startDeferredResultProcessing(result, mavContainer);
} }
在这里我们重点关注handleReturnValue
的方法,在经过适配包装后获取DeferredResult
开启了异步之旅
紧接着查看handleReturnValue方法中调用的WebAsyncManager的startDeferredResultProcessing方法
public void startDeferredResultProcessing(
final DeferredResult<?> deferredResult, Object... processingContext) throws Exception { Assert.notNull(deferredResult, "DeferredResult must not be null");
Assert.state(this.asyncWebRequest != null, "AsyncWebRequest must not be null");
//设置超时时间
Long timeout = deferredResult.getTimeoutValue();
if (timeout != null) {
this.asyncWebRequest.setTimeout(timeout);
} //获取所有的延迟结果拦截器
List<DeferredResultProcessingInterceptor> interceptors = new ArrayList<DeferredResultProcessingInterceptor>();
interceptors.add(deferredResult.getInterceptor());
interceptors.addAll(this.deferredResultInterceptors.values());
interceptors.add(timeoutDeferredResultInterceptor); final DeferredResultInterceptorChain interceptorChain = new DeferredResultInterceptorChain(interceptors); this.asyncWebRequest.addTimeoutHandler(new Runnable() {
@Override
public void run() {
try {
interceptorChain.triggerAfterTimeout(asyncWebRequest, deferredResult);
}
catch (Throwable ex) {
setConcurrentResultAndDispatch(ex);
}
}
}); this.asyncWebRequest.addCompletionHandler(new Runnable() {
@Override
public void run() {
interceptorChain.triggerAfterCompletion(asyncWebRequest, deferredResult);
}
}); interceptorChain.applyBeforeConcurrentHandling(this.asyncWebRequest, deferredResult);
//开始异步处理
startAsyncProcessing(processingContext); try {
interceptorChain.applyPreProcess(this.asyncWebRequest, deferredResult);
deferredResult.setResultHandler(new DeferredResultHandler() {
@Override
public void handleResult(Object result) {
result = interceptorChain.applyPostProcess(asyncWebRequest, deferredResult, result);
//设置结果并转发
setConcurrentResultAndDispatch(result);
}
});
}
catch (Throwable ex) {
setConcurrentResultAndDispatch(ex);
}
} private void startAsyncProcessing(Object[] processingContext) {
clearConcurrentResult();
this.concurrentResultContext = processingContext;
//实际上是执行的是HttpServletRequest对应方法
this.asyncWebRequest.startAsync(); if (logger.isDebugEnabled()) {
HttpServletRequest request = this.asyncWebRequest.getNativeRequest(HttpServletRequest.class);
String requestUri = urlPathHelper.getRequestUri(request);
logger.debug("Concurrent handling starting for " + request.getMethod() + " [" + requestUri + "]");
}
}
在这里首先收集所有配置好的DeferredResultProcessingInterceptor
,然后设置asyncRequest的超时处理,完成时的处理等,同时会分阶段执行拦截器中的各个方法。最后我们关注一下如下代码:
deferredResult.setResultHandler(result -> {
result = interceptorChain.applyPostProcess(this.asyncWebRequest, deferredResult, result);
//设置结果并转发
setConcurrentResultAndDispatch(result);
});
查看setConcurrentResultAndDispatch内实现:其最终还是要调用AsyncWebRequest接口中的dispatch方法进行转发,让DispatcherServlet重新处理异步结果:this.asyncWebRequest.dispatch();
其实在这里都是封装自HttpServletRequest
的异步操作,我们可以看一下StandardServletAsyncWebRequest
的类结构图:
可以在其父类ServletRequestAttributes
里找到对应的实现:
private final HttpServletRequest request;
/**
* Exposes the native {@link HttpServletRequest} that we're wrapping.
*/
public final HttpServletRequest getRequest() {
return this.request;
}
StandardServletAsyncWebRequest
代码,方便理解整个异步是怎么执行:
//java.servlet.AsnycContext
private AsyncContext asyncContext; @Override
public void startAsync() {
Assert.state(getRequest().isAsyncSupported(),
"Async support must be enabled on a servlet and for all filters involved " +
"in async request processing. This is done in Java code using the Servlet API " +
"or by adding \"<async-supported>true</async-supported>\" to servlet and " +
"filter declarations in web.xml.");
Assert.state(!isAsyncComplete(), "Async processing has already completed"); if (isAsyncStarted()) {
return;
}
this.asyncContext = getRequest().startAsync(getRequest(), getResponse());
this.asyncContext.addListener(this);
if (this.timeout != null) {
this.asyncContext.setTimeout(this.timeout);
}
} @Override
public void dispatch() {
Assert.notNull(this.asyncContext, "Cannot dispatch without an AsyncContext");
this.asyncContext.dispatch();
}
4、异步结果的异常处理:
如果Callable在执行过程中抛出异常 与一般的控制器异常一样,会被正常的异常处理流程捕获处理
如果返回方法是一个DeferredResult对象,可以选择
deferredResult.setErrorResult()
5、拦截异步请求
处理连接器HandlerInterceptor可以实现AsyncHandlerInterceptor接口拦截异步请求,因为在异步请求的开始时,被调用的回调方法是该接口的afterConcurrentHandlingStarted方法,而不是一般的postHandle 和 afterCompletion方法。如果需要与异步请求处理的生命流程有更深入的集成,比如需要处理timeout的事件等,则HandlerInterceptor需要注册CallableProcessingInterceptor或DeferredResultProcessingInterceptor拦截器,更多细节需要参考AsyncHandlerInterceptor类的Java文档。
DeferredResult类还提供了onTimeout(Runnable)和onCompletion(Runnable)等方法可以参考DeferredResult的java文档
Callable需要请求过期(timeout)和完成后的拦截时,可以把他包装在一个WebAsyncTask实例中,后者提供了相关技术支持。
3.3、异步拦截器
1)、原生API的AsyncListener
2)、SpringMVC:实现AsyncHandlerInterceptor;
四、使用
4.1、Callable使用
@RestController
public class WebCallableAsyncController {
Logger log=LoggerFactory.getLogger(WebCallableAsyncController.class);
@GetMapping("/callable")
public Callable<String> testCallable() throws Exception {
log.info("主线程开始!"); Callable<String> result = new Callable<String>() {
@Override
public String call() throws Exception {
log.info("副线程开始1!");
Thread.sleep(3000);
log.info("副线程结束1!");
return "SUCCESS1";
}
};
log.info("主线程结束!");
return result;
}
}
请求地址查看
-- ::00.197 INFO --- [nio--exec-] c.g.b.g.d.w.WebCallableAsyncController : 主线程开始!
-- ::00.197 INFO --- [nio--exec-] c.g.b.g.d.w.WebCallableAsyncController : 主线程结束!
-- ::00.197 INFO --- [ MvcAsync2] c.g.b.g.d.w.WebCallableAsyncController : 副线程开始1!
-- ::03.200 INFO --- [ MvcAsync2] c.g.b.g.d.w.WebCallableAsyncController : 副线程结束1!
返回Callable意味着Spring MVC将调用在不同的线程中执行定义的任务。Spring将使用TaskExecutor来管理线程。在等待完成的长期任务之前,servlet线程将被释放。
在长时间运行的任务执行完毕之前就已经从servlet返回了。这并不意味着客户端收到了一个响应。与客户端的通信仍然是开放的等待结果,但接收到的请求的线程已被释放,并可以服务于另一个客户的请求。
4.2、DeferredResult使用
一旦在Servlet容器中启用了异步请求处理功能,控制器方法就可以使用DeferredResult包装任何支持的控制器方法返回值,
DeferredResult这个类代表延迟结果,我们先看一看spring的API文档给我们的解释:
{@code DeferredResult} provides an alternative to using a {@link Callable} for asynchronous request processing.
While a {@code Callable} is executed concurrently on behalf of the application,
with a {@code DeferredResult} the application can produce the result from a thread of its choice.
根据文档说明DeferredResult
可以替代Callable
来进行异步的请求处理。只不过这个类可以从其他线程里拿到对应的结果。当使用DeferredResult
,我们可以将DefferedResult的类型并将其保存到可以获取到该对象的地方,比如说队列或者集合当中,这样方便其它线程能够取到并设置DefferedResult
的值。
@RestController
public class WebDeferredResultAsyncController {
Logger log = LoggerFactory.getLogger(WebDeferredResultAsyncController.class);
//接收队列
private BlockingQueue<DeferredResult<String>> blockingQueue = new ArrayBlockingQueue(1024);
//接收队列 或者ConcurrentLinkedQueue
private static Queue<DeferredResult<String>> queue = new ConcurrentLinkedQueue<DeferredResult<String>>(); /**
* 返回值是DeferredResult类型,如果没有结果请求阻塞
*
* @return
*/
@GetMapping("/quotes")
public DeferredResult<String> quotes() {
//指定超时时间,及出错时返回的值
DeferredResult<String> result = new DeferredResult(3000L, "error");
blockingQueue.add(result);
// queue.add(result);
return result;
} /**
* 另外一个请求(新的线程)设置值
*
* @throws InterruptedException
*/ @GetMapping("take")
public void take() throws InterruptedException {
DeferredResult<String> result = blockingQueue.take();
result.setResult("route");
// DeferredResult<String> poll = queue.poll();
// poll.setResult("OK");
}
}
控制器可以从不同的线程异步生成返回值,例如响应外部事件(JMS消息)、计划任务等,那么在这里我先使用另外一个请求来模拟这个过程
此时我们启动tomcat,先访问地址http://localhost:8080/quotes ,此时我们会看到发送的请求由于等待响应遭到了阻塞:
当在规定时间内访问http://localhost:8080/take 时,则能成功显示结果:
如果有另一个线程给DeferredResult赋值后,DeferredResult在感知到自己的对象被赋值后就返回页面成功;
一个独立的示例
/**
* 一个独立的示例
* @return
*/
@RequestMapping(value = "/deferred", method = RequestMethod.GET)
public DeferredResult<String> executeSlowTask() {
log.info("Request received");
DeferredResult<String> deferredResult = new DeferredResult<>();
CompletableFuture.supplyAsync(()->{
try {
Thread.sleep(5000);
log.info("Slow task executed");
return "Task finished";
} catch (InterruptedException e) {
e.printStackTrace();
return "Task exception";
}
}).whenCompleteAsync((result, throwable) -> deferredResult.setResult(result));
log.info("Servlet thread released"); return deferredResult;
}
返回DeferredResult和返回Callable有什么区别?不同的是返回DeferredResult的线程是由我们管理。创建一个线程并将结果set到DeferredResult是由我们自己来做的。
用completablefuture创建一个异步任务。这将创建一个新的线程,在那里我们的长时间运行的任务将被执行。也就是在这个线程中,我们将set结果到DeferredResult并返回。
是在哪个线程池中我们取回这个新的线程?默认情况下,在completablefuture的supplyasync方法将在forkjoin池运行任务。如果你想使用一个不同的线程池,你可以通过传一个executor到supplyasync方法:
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
注意:Callable和Deferredresult做的是同样的事情——释放容器线程,在另一个线程上异步运行长时间的任务。不同的是谁管理执行任务的线程。
4.3、 WebAsyncTask
1》常规调用
查看WebAsyncTask,有说明:Holder for a {@link Callable}, a timeout value, and a task executor.其实是一个Callable。
示例
@RequestMapping(value="/longtimetask", method = RequestMethod.GET)
public WebAsyncTask longTimeTask(){
System.out.println("/longtimetask被调用 thread id is : " + Thread.currentThread().getId());
Callable<String> callable = new Callable<String>() {
public String call() throws Exception {
Thread.sleep(3000); //假设是一些长时间任务
System.out.println("执行成功 thread id is : " + Thread.currentThread().getId());
return "ok";
}
};
return new WebAsyncTask(callable);
}
事实上,直接返回Callable<String>都是可以的,但这里包装了一层,以便做后面提到的“超时处理”。和前一个方案的差别在于这个Callable的call方法并不是我们直接调用的,而是在longTimeTask返回后,由Spring MVC用一个工作线程来调用,执行,打印出来的结果:
/longtimetask被调用 thread id is : 24
执行成功 thread id is : 38
2》超时处理
如果“长时间处理任务”一直没返回,那也不应该让客户端无限等下去,需要服务端终结,即“超时”处理。如图:
“超时处理线程”和“回调处理线程”可能都是线程池中的某个线程,我为了清晰点把它们分开画而已。
@RequestMapping(value="/longtimetaskTimeout", method = RequestMethod.GET)
public WebAsyncTask longtimetaskTimeout(){
System.out.println("/longtimetask被调用 thread id is : " + Thread.currentThread().getId());
Callable<String> callable = new Callable<String>() {
public String call() throws Exception {
Thread.sleep(3000); //假设是一些长时间任务
System.out.println("执行成功 thread id is : " + Thread.currentThread().getId());
return "ok";
}
};
WebAsyncTask webAsyncTask = new WebAsyncTask(2000,callable);
webAsyncTask.onTimeout(()->{
System.out.println("执行超时 thread id is :" + Thread.currentThread().getId());
return "执行超时";
});
return webAsyncTask;
}
这就是前面提到的为什么Callable还要外包一层的缘故,给WebAsyncTask设置一个超时回调,即可实现超时处理,在这个例子中,正常处理需要3秒钟,而超时设置为2秒,所以肯定会出现超时
返回值
/longtimetask被调用 thread id is : 23
执行超时 thread id is :24
3》综合示例
自定义线程池
@Configuration
public class TaskConfiguration {
@Bean("taskExecutor")
public ThreadPoolTaskExecutor taskExecutor() {
ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();
taskExecutor.setCorePoolSize(5);
taskExecutor.setMaxPoolSize(10);
taskExecutor.setQueueCapacity(10);
taskExecutor.setThreadNamePrefix("asyncTask");
return taskExecutor;
}
}
使用
@GetMapping("/threadPool")
public WebAsyncTask<String> asyncTaskThreadPool() {
return new WebAsyncTask<>(10 * 1000L, executor,
() -> {
out.println(format("异步工作线程:%s", currentThread().getName()));
return asyncService.generateUUID();
});
}
超时、异常综合示例
@RestController
public class WebAsyncTaskController {
private final WebAsyncService asyncService;
private final static String ERROR_MESSAGE = "Task error";
private final static String TIME_MESSAGE = "Task timeout";
@Autowired
@Qualifier("taskExecutor")
private ThreadPoolTaskExecutor executor; @Autowired
public WebAsyncTaskController(WebAsyncService asyncService) {
this.asyncService = asyncService;
}
@GetMapping("/completion")
public WebAsyncTask<String> asyncTaskCompletion() {
// 打印处理线程名
out.println(format("请求处理线程:%s", currentThread().getName())); // 模拟开启一个异步任务,超时时间为10s
WebAsyncTask<String> asyncTask = new WebAsyncTask<>(10 * 1000L, () -> {
out.println(format("异步工作线程:%s", currentThread().getName()));
// 任务处理时间5s,不超时
sleep(5 * 1000L);
return asyncService.generateUUID();
}); // 任务执行完成时调用该方法
asyncTask.onCompletion(() -> out.println("任务执行完成"));
out.println("继续处理其他事情");
return asyncTask;
} @GetMapping("/exception")
public WebAsyncTask<String> asyncTaskException() {
// 打印处理线程名
out.println(format("请求处理线程:%s", currentThread().getName())); // 模拟开启一个异步任务,超时时间为10s
WebAsyncTask<String> asyncTask = new WebAsyncTask<>(10 * 1000L, () -> {
out.println(format("异步工作线程:%s", currentThread().getName()));
// 任务处理时间5s,不超时
sleep(5 * 1000L);
throw new Exception(ERROR_MESSAGE);
}); // 任务执行完成时调用该方法
asyncTask.onCompletion(() -> out.println("任务执行完成"));
asyncTask.onError(() -> {
out.println("任务执行异常");
return ERROR_MESSAGE;
}); out.println("继续处理其他事情");
return asyncTask;
} @GetMapping("/timeout")
public WebAsyncTask<String> asyncTaskTimeout() {
// 打印处理线程名
out.println(format("请求处理线程:%s", currentThread().getName())); // 模拟开启一个异步任务,超时时间为10s
WebAsyncTask<String> asyncTask = new WebAsyncTask<>(10 * 1000L, () -> {
out.println(format("异步工作线程:%s", currentThread().getName()));
// 任务处理时间5s,不超时
sleep(15 * 1000L);
return TIME_MESSAGE;
}); // 任务执行完成时调用该方法
asyncTask.onCompletion(() -> out.println("任务执行完成"));
asyncTask.onTimeout(() -> {
out.println("任务执行超时");
return TIME_MESSAGE;
}); out.println("继续处理其他事情");
return asyncTask;
}
}
4.4、@Async
参看:https://www.cnblogs.com/bjlhx/p/10364385.html
五、Callable、DeferredResult、WebAsyncTask、Async对比、
Callable | WebAsyncTask | DeferredResult | Async | |
针对问题点 | 异步请求处理 | 异步请求处理 | 异步请求处理 | 异步方法 |
目标 | 释放容器线程 | 释放容器线程 | 释放容器线程 | 服务线程内多线程执行 |
拦截器 | CallableProcessingInterceptor | DeferredResultProcessingInterceptor | ||
超时拦截器 | TimeoutCallableProcessingInterceptor | TimeoutDeferredResultProcessingInterceptor |
常用类:
NoSupportAsyncWebRequest.java
不支持异步处理模式的web请求
DeferredResultProcessingInterceptor.java
DeferredResult处理过程拦截器
在start async前,超时后/异步处理完成后/网络超时后触发拦截
DeferredResultProcessingInterceptorAdapter.java
抽象类实现DeferredResultProcessingInterceptor,做空实现
DeferredResultInterceptorChain.java
调用DeferredResultProcessingInterceptor的辅助类
DeferredResult.java
递延结果,在两个线程中传递的对象结果
实现Comparable接口以保证加入PriorityQueue队列的正确顺序
CallableProcessingInterceptor.java
Callable拦截器
CallableProcessingInterceptorAdapter.java
抽象类实现CallableProcessingInterceptor接口,空实现
CallableInterceptorChain.java
调用CallableProcessingInterceptor的辅助类
TimeoutCallableProcessingInterceptor.java
继承CallableProcessingInterceptorAdapter
实现超时处理方法
TimeoutDeferredResultProcessingInterceptor.java
继承DeferredResultProcessingInterceptorAdapter
实现超时处理方法
WebAsyncTask.java
web异步任务
包含一个Callable类,一个超时时间,一个任务执行着或名字
WebAsyncUtils.java
实现getAsyncManager
实现createAsyncWebRequest
WebAsyncManager.java
对Callables和DeferredResults启动的管理,包括拦截器的注入,Excutor的注入等
异步处理的入口类
007-优化web请求三-异步调用【WebAsyncTask】的更多相关文章
- SpringBoot中异步请求和异步调用(看这一篇就够了)
原创不易,如需转载,请注明出处https://www.cnblogs.com/baixianlong/p/10661591.html,否则将追究法律责任!!! 一.SpringBoot中异步请求的使用 ...
- Spring Boot 异步请求和异步调用,一文搞定
一.Spring Boot中异步请求的使用 1.异步请求与同步请求 特点: 可以先释放容器分配给请求的线程与相关资源,减轻系统负担,释放了容器所分配线程的请求,其响应将被延后,可以在耗时处理完成(例如 ...
- ANTS Performance Profiler 8:支持对Web请求、异步代码和WinRT的性能剖析
下载与激活:http://download.csdn.net/detail/lone112/6734291 离线激活 位于英国的Red Gate Software有限公司最近发布了ANTS Per ...
- Web开发——前后台异步调用
做web开发,最头疼的.最核心的部分或许就应该是前后台交互了,之前一直没弄明白,每次都不知道该如何去做.最近由于开发需要,加上有些朋友问起这个问题,不得不再次摸索前后台交互的方法.功夫不负有心人,总算 ...
- 006-优化web请求二-应用缓存、异步调用【Future、ListenableFuture、CompletableFuture】、ETag、WebSocket【SockJS、Stomp】
四.应用缓存 使用spring应用缓存.使用方式:使用@EnableCache注解激活Spring的缓存功能,需要创建一个CacheManager来处理缓存.如使用一个内存缓存示例 package c ...
- SpringBoot 异步调用方法并接收返回值
项目中肯定会遇到异步调用其他方法的场景,比如有个计算过程,需要计算很多个指标的值,但是每个指标计算的效率快慢不同,如果采用同步执行的方式,运行这一个过程的时间是计算所有指标的时间之和.比如: 方法A: ...
- 从Nginx的Web请求处理机制中剖析多进程、多线程、异步IO
Nginx服务器web请求处理机制 从设计架构来说,Nginx服务器是与众不同的.不同之处一方面体现在它的模块化设计,另一方面,也是最重要的一方面,体现在它对客户端请求的处理机制上. Web服务器和客 ...
- C# 委托的三种调用示例(同步调用 异步调用 异步回调)
首先,通过代码定义一个委托和下面三个示例将要调用的方法: 复制代码 代码如下: public delegate int AddHandler(int a,int b); public class ...
- tornado异步web请求
1.为什么要使用异步web服务使用异步非阻塞请求,并发处理更高效. 2.同步与异步请求比较同步请求时,web服务器进程是阻塞的,也就是说当一个请求被处理时,服务器进程会被挂起直至请求完成. 异步请求时 ...
随机推荐
- [Android Studio] Using NDK to call OpenCV
NDK才是Android开发通向超高薪之路.(这句话,似乎四年前有云) 难点在于常用的non-free module (sift and surf) unsw@unsw-UX303UB$ pwd /h ...
- [原]openstack-kilo--issue(十六) instance can't get ip 虚拟机不能得到ip(1)
=====问题点:vm instance不能正常获取ip地址(此时用户是:admin) =======不一样的点:如果使用用户demo用户,启动一个vm,同样的image这个时候就能正确获取ip == ...
- B - 考试排名
C++编程考试使用的实时提交系统,具有即时获得成绩排名的特点.它的功能是怎么实现的呢? 我们做好了题目的解答,提交之后,要么"AC",要么错误,不管怎样错法,总是给你记上一笔,表明 ...
- thinkphp 无限极 评论
郑创 今天用啦一天的时间用了各种方法终于把评论成无限极了,随便评论,有判断自己不能评论自己,下面先说前台源代码! 要实现的视图 前台源代码html模板 <div class="wen_ ...
- Shell----简单整理
------------------------------------------------------------------Shell脚本--------------------------- ...
- SQL 2017 远程连接被拒绝
1.防火墙端口 2.数据库要能帐号登录 可是还是不行 打开:SQL Server 2017 配置管理器->SQL Server 服务 ->SQLServer(你的实例名)-> 右键- ...
- js callback 和 js 混淆
function test(a,callback){ a+=100; callback(a) } function abc(a){ a+=100; alert(a); } test(5,abc) js ...
- PHP一个小函数
// function makeTemp($fileName="index",$ftype=0) { $tempPath="xx/xxxx/{$fileName}.htm ...
- centos 安装教程 服务器配置教程 服务器中安装python 服务器中安装Django 安装MySQL 配置MySQL
一 .解决python编译安装所需的软件依赖 yum install gcc patch libffi-devel python-devel zlib-devel bzip2-devel opens ...
- eclipse 注销和取消注销
单行注释: CTRL + / 当行取消注释(一样的): CTRL + / 多行注释: CTRL + SHIFT + / 多行取消注释(斜杠换成反斜杠): CTRL + SHIFT + \