zoukankan      html  css  js  c++  java
  • 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;
            }
        }
    View Code

      对比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调用HttpServletRequeststartAsync()方法,异步处理

      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);
        }
    
    }
    View Code

      在这里我们重点关注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 + "]");
            }
        }
    View Code

       在这里首先收集所有配置好的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;
        }
    }

    请求地址查看

    2019-02-27 14:25:00.197  INFO 13815 --- [nio-8080-exec-3] c.g.b.g.d.w.WebCallableAsyncController   : 主线程开始!
    2019-02-27 14:25:00.197  INFO 13815 --- [nio-8080-exec-3] c.g.b.g.d.w.WebCallableAsyncController   : 主线程结束!
    2019-02-27 14:25:00.197  INFO 13815 --- [      MvcAsync2] c.g.b.g.d.w.WebCallableAsyncController   : 副线程开始1!
    2019-02-27 14:25:03.200  INFO 13815 --- [      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;
        }
    }
    View Code

    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的注入等
      异步处理的入口类

  • 相关阅读:
    Luogu P1004 方格取数
    Luogu P1896 [SCOI2005]互不侵犯
    Luogu P1879 [USACO06NOV]玉米田Corn Fields 【状压dp模板】
    高精度模板(结构体)
    【模板】快读
    vue input框type=number 保留两位小数自定义组件
    elementui表格表头合并
    将excle表中得数据生成insert语句插入到数据库中
    数据库基本语法
    ztree 数组和树结构互转算法
  • 原文地址:https://www.cnblogs.com/bjlhx/p/10444814.html
Copyright © 2011-2022 走看看