何为异步请求

在Servlet 3.0之前,Servlet采用Thread-Per-Request的方式处理请求,即每一次Http请求都由某一个线程从头到尾负责处理。如果一个请求需要进行IO操作,比如访问数据库、调用第三方服务接口等,那么其所对应的线程将同步地等待****IO操作完成, 而IO操作是非常慢的,所以此时的线程并不能及时地释放回线程池以供后续使用,在并发量越来越大的情况下,这将带来严重的性能问题。其请求流程大致为:

而在Servlet3.0发布后,提供了一个新特性:异步处理请求。可以先释放容器分配给请求的线程与相关资源,减轻系统负担,释放了容器所分配线程的请求,其响应将被延后,可以在耗时处理完成(例如长时间的运算)时再对客户端进行响应。其请求流程为:

在Servlet 3.0后,我们可以从HttpServletRequest对象中获得一个**AsyncContext**对象,该对象构成了异步处理的上下文,Request和Response对象都可从中获取。AsyncContext可以从当前线程传给另外的线程,并在新的线程中完成对请求的处理并返回结果给客户端,初始线程便可以还回给容器线程池以处理更多的请求。如此,通过将请求从一个线程传给另一个线程处理的过程便构成了Servlet 3.0中的异步处理。

随着Spring5发布,提供了一个响应式Web框架:Spring WebFlux。之后可能就不需要Servlet容器的支持了。以下是其先后对比图:

左侧是传统的基于Servlet的Spring Web MVC框架,右侧是5.0版本新引入的基于Reactive Streams的Spring WebFlux框架,从上到下依次是Router Functions,WebFlux,Reactive Streams三个新组件。

原生异步请求API说明

在编写实际代码之前,我们来了解下一些关于异步请求的api的调用说明。

  • 获取AsyncContext:根据HttpServletRequest对象获取。

AsyncContext asyncContext = request.startAsync();

  • 设置监听器:可设置其开始、完成、异常、超时等事件的回调处理

其监听器的接口代码:

public interface AsyncListener extends EventListener {void onComplete(AsyncEvent event) throws IOException;void onTimeout(AsyncEvent event) throws IOException;void onError(AsyncEvent event) throws IOException;void onStartAsync(AsyncEvent event) throws IOException;
}

说明:

  1. onStartAsync:异步线程开始时调用
  2. onError:异步线程出错时调用
  3. onTimeout:异步线程执行超时调用
  4. onComplete:异步执行完毕时调用

一般上,我们在超时或者异常时,会返回给前端相应的提示,比如说超时了,请再次请求等等,根据各业务进行自定义返回。同时,在异步调用完成时,一般需要执行一些清理工作或者其他相关操作。

需要注意的是只有在调用request.startAsync前将监听器添加到AsyncContext,监听器的onStartAsync方法才会起作用,而调用startAsync前AsyncContext还不存在,所以第一次调用startAsync是不会被监听器中的onStartAsync方法捕获的,只有在超时后又重新开始的情况下onStartAsync方法才会起作用。

  • 设置超时:通过setTimeout方法设置,单位:毫秒。

一定要设置超时时间,不能无限等待下去,不然和正常的请求就一样了。。

Servlet方式实现异步请求

前面已经提到,可通过HttpServletRequest对象中获得一个**AsyncContext**对象,该对象构成了异步处理的上下文。所以,我们来实际操作下。

1、编写一个简单控制层

/*** 使用servlet方式进行异步请求**/
@Slf4j
@RestController
public class ServletController {@RequestMapping("/servlet/orig")public void todo(HttpServletRequest request,HttpServletResponse response) throws Exception {//这里来个休眠Thread.sleep(100);response.getWriter().println("这是【正常】的请求返回");}@RequestMapping("/servlet/async")public void todoAsync(HttpServletRequest request,HttpServletResponse response) {AsyncContext asyncContext = request.startAsync();asyncContext.addListener(new AsyncListener() {@Overridepublic void onTimeout(AsyncEvent event) throws IOException {log.info("超时了:");//做一些超时后的相关操作}@Overridepublic void onStartAsync(AsyncEvent event) throws IOException {// TODO Auto-generated method stublog.info("线程开始");}@Overridepublic void onError(AsyncEvent event) throws IOException {log.info("发生错误:",event.getThrowable());}@Overridepublic void onComplete(AsyncEvent event) throws IOException {log.info("执行完成");//这里可以做一些清理资源的操作}});//设置超时时间asyncContext.setTimeout(200);//也可以不使用start 进行异步调用
//        new Thread(new Runnable() {
//
//            @Override
//            public void run() {
//                编写业务逻辑
//
//            }
//        }).start();asyncContext.start(new Runnable() {            @Overridepublic void run() {try {Thread.sleep(100);log.info("内部线程:" + Thread.currentThread().getName());asyncContext.getResponse().setCharacterEncoding("utf-8");asyncContext.getResponse().setContentType("text/html;charset=UTF-8");asyncContext.getResponse().getWriter().println("这是【异步】的请求返回");} catch (Exception e) {log.error("异常:",e);}//异步请求完成通知//此时整个请求才完成//其实可以利用此特性 进行多条消息的推送 把连接挂起。。asyncContext.complete();}});//此时之类 request的线程连接已经释放了log.info("线程:" + Thread.currentThread().getName());}}

注意:异步请求时,可以利用ThreadPoolExecutor自定义个线程池。

1.启动下应用,查看控制台输出就可以获悉是否在同一个线程里面了。同时,可设置下等待时间,之后就会调用超时回调方法了。

使用过滤器时,需要加入asyncSupported为true配置,开启异步请求支持。

@WebServlet(urlPatterns = "/okong", asyncSupported = true )
public  class AsyncServlet extends HttpServlet ...

题外话:其实我们可以利用在未执行asyncContext.complete()方法时请求未结束这特性,可以做个简单的文件上传进度条之类的功能。但注意请求是会超时的,需要设置超时的时间下。

Spring方式实现异步请求

在Spring中,有多种方式实现异步请求,比如callable、DeferredResult或者WebAsyncTask。每个的用法略有不同,可根据不同的业务场景选择不同的方式。以下主要介绍一些常用的用法

Callable

使用很简单,直接返回的参数包裹一层callable即可。

用法

    @RequestMapping("/callable")public Callable<String> callable() {log.info("外部线程:" + Thread.currentThread().getName());return new Callable<String>() {@Overridepublic String call() throws Exception {log.info("内部线程:" + Thread.currentThread().getName());return "callable!";}};}

控制台输出:

超时、自定义线程设置

从控制台可以看见,异步响应的线程使用的是名为:MvcAsync1的线程。第一次再访问时,就是MvcAsync2了。若采用默认设置,会无限的创建新线程去处理异步请求,所以正常都需要配置一个线程池及超时时间。

编写一个配置类

@Configuration
public class JavaConfig {/*** 配置线程池* @return*/@Bean(name = "asyncPoolTaskExecutor")public ThreadPoolTaskExecutor getAsyncThreadPoolTaskExecutor() {ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor();//此方法返回可用处理器的虚拟机的最大数量; 不小于1int core = Runtime.getRuntime().availableProcessors();taskExecutor.setCorePoolSize(core);taskExecutor.setMaxPoolSize(core*2 + 1);taskExecutor.setQueueCapacity(25);taskExecutor.setKeepAliveSeconds(200);taskExecutor.setThreadNamePrefix("callable-");//线程名称前缀// 线程池对拒绝任务(无线程可用)的处理策略,目前只支持AbortPolicy、CallerRunsPolicy;默认为后者taskExecutor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());taskExecutor.initialize();return taskExecutor;}
}

DeferredResult

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

/*** 线程池*/
public static ExecutorService FIXED_THREAD_POOL = Executors.newFixedThreadPool(30);@RequestMapping("/deferredresult")
public DeferredResult<String> deferredResult(){log.info("外部线程:" + Thread.currentThread().getName());//设置超时时间DeferredResult<String> result = new DeferredResult<String>(60*1000L);//处理超时事件 采用委托机制result.onTimeout(new Runnable() {@Overridepublic void run() {log.error("DeferredResult超时");result.setResult("超时了!");}});result.onCompletion(new Runnable() {@Overridepublic void run() {//完成后log.info("调用完成");}});FIXED_THREAD_POOL.execute(new Runnable() {@Overridepublic void run() {//处理业务逻辑log.info("内部线程:" + Thread.currentThread().getName());//返回结果result.setResult("DeferredResult!!");}});return result;
}

注意:返回结果时记得调用下setResult方法。

题外话:利用DeferredResult可实现一些长连接的功能,比如当某个操作是异步时,我们可以保存这个DeferredResult对象,当异步通知回来时,我们在找回这个DeferredResult对象,之后在setResult会结果即可。提高性能。

WebAsyncTask

使用方法都类似,只是WebAsyncTask是直接返回了。

@RequestMapping("/webAsyncTask")public WebAsyncTask<String> webAsyncTask() {log.info("外部线程:" + Thread.currentThread().getName());WebAsyncTask<String> result = new WebAsyncTask<String>(60*1000L, new Callable<String>() {@Overridepublic String call() throws Exception {log.info("内部线程:" + Thread.currentThread().getName());return "WebAsyncTask!!!";}});result.onTimeout(new Callable<String>() {@Overridepublic String call() throws Exception {// TODO Auto-generated method stubreturn "WebAsyncTask超时!!!";}});result.onCompletion(new Runnable() {@Overridepublic void run() {//超时后 也会执行此方法log.info("WebAsyncTask执行结束");}});return result;}

SpringBoot异步请求相关推荐

  1. Spring及Springboot 异步请求

    1.java 异步请求 Spring在3.2的版本上就已经为我们提供的相应的机制,以应对Http Nio的场景. 官方文档中说DeferredResult和Callable都是为了异步生成返回值提供基 ...

  2. 面试官 | SpringBoot 中如何实现异步请求和异步调用?

    作者 | 会炼钢的小白龙 来源 | cnblogs.com/baixianlong/p/10661591.html 一.SpringBoot中异步请求的使用 1.异步请求与同步请求 特点: 可以先释放 ...

  3. springboot服务调用超时_Spring Boot 异步请求和异步调用,一文搞定

    一.Spring Boot中异步请求的使用 1.异步请求与同步请求 特点: 可以先释放容器分配给请求的线程与相关资源,减轻系统负担,释放了容器所分配线程的请求,其响应将被延后,可以在耗时处理完成(例如 ...

  4. SpringMvc异步请求的使用及部分原理

    最近隔壁项目组的项目又出问题了,一直被用户投诉太卡了,页面白屏的那种,打开源代码一看,全是非异步请求,类似于以下写法: @ResponseBody@RequestMapping(value = &qu ...

  5. 新手也能看懂的 SpringBoot 异步编程指南

    通过本文你可以了解到下面这些知识点: Future 模式介绍以及核心思想 核心线程数.最大线程数的区别,队列容量代表什么: ThreadPoolTaskExecutor饱和策略: SpringBoot ...

  6. httpservletrequest 设置请求头_大部分程序员不知道的 Servelt3 异步请求,原来这么简单?

    前言 当一个 HTTP 请求到达 Tomcat,Tomcat 将会从线程池中取出线程,然后按照如下流程处理请求: 将请求信息解析为 HttpServletRequest 分发到具体 Servlet 处 ...

  7. ajax异步请求数据库实现级联下拉菜单。

    ajax实现级联下拉菜单,使用Springboot实现. 思路很简单,使用ajax异步请求数据库数据即可. 文末有项目源码! 实现效果: 废话不多说,现在我们来开始实现! 第一步. 创建三张表:省.市 ...

  8. SpringBoot异步调用方法

    SpringBoot异步调用方法 一.spring boot--使用异步请求,提高系统的吞吐量 https://blog.csdn.net/liuchuanhong1/article/details/ ...

  9. Springboot异步多线程编程

    文章目录 一.基础知识 二.什么时候用同步&异步 三.什么时候需要使用多线程 四.springboot异步多线程编程实现 一.基础知识 同步:同步就是指一个进程在执行某个请求的时候,若该请求需 ...

最新文章

  1. php执行只读文件,php实现以只读方式打开文件的方法
  2. 小熊电器、九阳、苏泊尔们的“颜价比”被外卖小哥“打回原形”
  3. 利用 apache ab 测试服务器性能
  4. 数据保护伞—为MaxCompute平台数据安全保驾护航
  5. (转载)C中实参与形参的“值传递”只能“单向传送”(透彻的理解)
  6. python是什么 自学-自学python用什么系统好
  7. 【HDOJ】1261 字串数【组合数学--排列+代数】
  8. 使用k-近邻算法进行分类
  9. R语言ETL工程系列:检索(select)
  10. HDL4SE:软件工程师学习Verilog语言(四)
  11. 死磕算法!35篇算法设计实例+6本必读书打包送你
  12. 使用ApiPost按顺序传参及测试多个接口
  13. c语言数学函数库根号程序,用C 编写程序时根号肿么输入
  14. windows 下 tomcat 开机自启动
  15. 【自考】数据结构导论—二叉树计算题
  16. Mysql,姓名按笔画排序
  17. 转载:  Lodop、C-Lodop打印控件报错
  18. 如何使用JavaScript来判断用户设备类型
  19. 计算机模拟图像和数字,模拟与数字的区别
  20. Cotex-M3内核STM32F10XX系列时钟及其配置方法

热门文章

  1. [笔记分享] [GPIO] MSM8x39 GPIO 硬件部分小结
  2. jdk下载与安装_jdk下载与安装教程32位
  3. pandas学习笔记(三):数据的变换与数据的管理
  4. Autodesk Fusion 360安装指引
  5. WIZnet网络芯片的使用
  6. 嵌入式操作系统多任务调度原理分析与RUST参考实现
  7. ARM:ELF bin Hex axf
  8. mysql 主键删除数据库_【数据库】mysql如何删除主键?
  9. 恢复刚被误删QQ好友的方法
  10. Java串口通信学习(一)