【SpringBoot】异步请求的使用和拓展


异步请求与异步调用的区别

  • 两者的使用场景不同,异步请求用来解决并发请求对服务器造成的压力,从而提高对请求的吞吐量;而异步调用是用来做一些非主线流程且不需要实时计算和响应的任务,比如同步日志到kafka中做日志分析等。
  • 异步请求是会一直等待response响应的,需要返回结果给客户端的;而异步调用我们往往会马上返回给客户端响应,完成这次整个的请求,至于异步调用的任务后台自己慢慢跑就行,客户端不会关心。

前言

同步请求
在这里插入图片描述
异步请求
在这里插入图片描述
特点:

  • 可以先释放容器分配给请求的线程与相关资源,减轻系统负担,释放了容器所分配线程的请求,其响应将被延后,可以在后台耗时处理完成(例如长时间的运算)时再对客户端进行响应。
  • 一句话:增加了服务器对客户端请求的吞吐量
  • 实际生产上我们用的比较少,如果并发请求量很大的情况下,我们会通过nginx把请求负载到集群服务的各个节点上来分摊请求压力,当然还可以通过消息队列来做请求的缓冲)。

异步请求的实现

1.Servlet方式实现异步请求

    @RequestMapping(value = "/email/servletReq", method = RequestMethod.GET)
    public void servletReq(HttpServletRequest request, HttpServletResponse response) {
    
    
        AsyncContext asyncContext = request.startAsync();
        //设置监听器:可设置其开始、完成、异常、超时等事件的回调处理
        asyncContext.addListener(new AsyncListener() {
    
    
            @Override
            public void onTimeout(AsyncEvent event) throws IOException {
    
    
                System.out.println("超时了...");
                //做一些超时后的相关操作...
            }

            @Override
            public void onStartAsync(AsyncEvent event) throws IOException {
    
    
                System.out.println("线程开始");
            }

            @Override
            public void onError(AsyncEvent event) throws IOException {
    
    
                System.out.println("发生错误:" + event.getThrowable());
            }

            @Override
            public void onComplete(AsyncEvent event) throws IOException {
    
    
                System.out.println("执行完成");
                //这里可以做一些清理资源的操作...
            }
        });
        
        //设置超时时间
        asyncContext.setTimeout(20000);
        asyncContext.start(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                try {
    
    
                    Thread.sleep(10000);
                    System.out.println("内部线程:" + Thread.currentThread().getName());
                    asyncContext.getResponse().setCharacterEncoding("utf-8");
                    asyncContext.getResponse().setContentType("text/html;charset=UTF-8");
                    asyncContext.getResponse().getWriter().println("这是异步的请求返回");
                } catch (Exception e) {
    
    
                    e.printStackTrace();
                    System.out.println("异常:" + e);
                }
                //异步请求完成通知
                //此时整个请求才完成
                asyncContext.complete();
            }
        });
        //此时之类 request的线程连接已经释放了
        System.out.println("主线程:" + Thread.currentThread().getName());
    }

2.Callable

将Controller返回的参数用Callable包裹即可

可以继承WebMvcConfigurerAdapter类/实现WebMvcConfigurer接口 (SpringBoot2.0推荐)来设置默认线程池和超时处理

接口使用Callable修饰返回参数

    @RequestMapping(value = "/email/callableReq", method = RequestMethod.GET)
    @ResponseBody
    public Callable<String> callableReq() {
    
    
        System.out.println("外部线程:" + Thread.currentThread().getName());
        return new Callable<String>() {
    
    
            @Override
            public String call() throws Exception {
    
    
                Thread.sleep(10000);
                System.out.println("内部线程:" + Thread.currentThread().getName());
                return "callable!";
            }
        };
    }

配置类设置默认线程池和超时处理

@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
    
    
    @Resource
    private ThreadPoolTaskExecutor myThreadPoolTaskExecutor;

    @Override
    public void configureAsyncSupport(final AsyncSupportConfigurer configurer) {
    
    
        //处理 callable超时
        configurer.setDefaultTimeout(60 * 1000);
        configurer.setTaskExecutor(myThreadPoolTaskExecutor);
        configurer.registerCallableInterceptors(timeoutCallableProcessingInterceptor());
    }

    @Bean
    public TimeoutCallableProcessingInterceptor timeoutCallableProcessingInterceptor() {
    
    
        return new TimeoutCallableProcessingInterceptor();
    }
}

3.WebAsyncTask

和Callable用法差不多,将Controller返回的参数用WebAsyncTask包裹即可,还可以给WebAsyncTask设置一个超时回调,即可实现超时处理

可以继承WebMvcConfigurerAdapter类/实现WebMvcConfigurer接口 (SpringBoot2.0推荐)来设置默认线程池和超时处理

    @RequestMapping(value = "/email/webAsyncReq", method = RequestMethod.GET)
    @ResponseBody
    public WebAsyncTask<String> webAsyncReq() {
    
    
        System.out.println("外部线程:" + Thread.currentThread().getName());
        Callable<String> result = () -> {
    
    
            System.out.println("内部线程开始:" + Thread.currentThread().getName());
            try {
    
    
                TimeUnit.SECONDS.sleep(4);
            } catch (Exception e) {
    
    
                    e.printStackTrace();
            }
            logger.info("副线程返回");
            System.out.println("内部线程返回:" + Thread.currentThread().getName());
            return "success";
        };
        
        
        WebAsyncTask<String> wat = new WebAsyncTask<String>(3000L, result);
        wat.onTimeout(new Callable<String>() {
    
    
            @Override
            public String call() throws Exception {
    
    
                return "超时";
            }
        });
        return wat;
    }

4.DeferredResult

DeferredResult可以处理一些相对复杂一些的业务逻辑,最主要还是可以在另一个线程里面进行业务处理及返回即可在两个完全不相干的线程间的通信

可以继承WebMvcConfigurerAdapter类/实现WebMvcConfigurer接口 (SpringBoot2.0推荐)来设置默认线程池和超时处理

@RequestMapping(value = "/email/deferredResultReq", method = RequestMethod.GET)
    @ResponseBody
    public DeferredResult<String> deferredResultReq() {
    
    
        System.out.println("外部线程:" + Thread.currentThread().getName());
        //设置超时时间
        DeferredResult<String> result = new DeferredResult<String>(60 * 1000L);
        //处理超时事件 采用委托机制
        result.onTimeout(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("DeferredResult超时");
                result.setResult("超时了!");
            }
        });
        result.onCompletion(new Runnable() {
    
    

            @Override
            public void run() {
    
    
                //完成后
                System.out.println("调用完成");
            }
        });

        myThreadPoolTaskExecutor.execute(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                //处理业务逻辑
                System.out.println("内部线程:" + Thread.currentThread().getName());
                //返回结果
                result.setResult("DeferredResult!!");
            }
        });
        return result;
    }

猜你喜欢

转载自blog.csdn.net/qq877728715/article/details/111556998