前言
关于
web开发
的相关知识点,后续有补充时再开续写了。比如webService
服务、发邮件
等,这些一般上觉得不完全属于web开发
方面的,而且目前webService
作为一个接口来提供服务的机会应该比较小了吧。所以本章节开始,开始讲解关于异步开发过程中会使用到的一些知识点。本章节就来讲解下异步请求相关知识点。
一点知识
何为异步请求
在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三个新组件。
对于其发展前景还是拭目以待吧。有时间也该去了解下Spring5
了。
原生异步请求API说明
在编写实际代码之前,我们来了解下一些关于异步请求的api的调用说明。
- 获取AsyncContext:根据
HttpServletRequest
对象获取。
1 | AsyncContext asyncContext = request.startAsync(); |
- 设置监听器:可设置其开始、完成、异常、超时等事件的回调处理
其监听器的接口代码:1
2
3
4
5
6public 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;
}
说明:
- onStartAsync:异步线程开始时调用
- onError:异步线程出错时调用
- onTimeout:异步线程执行超时调用
- onComplete:异步执行完毕时调用
一般上,我们在超时或者异常时,会返回给前端相应的提示,比如说超时了,请再次请求等等,根据各业务进行自定义返回。同时,在异步调用完成时,一般需要执行一些清理工作或者其他相关操作。
需要注意的是只有在调用request.startAsync
前将监听器添加到AsyncContext
,监听器的onStartAsync
方法才会起作用,而调用startAsync
前AsyncContext
还不存在,所以第一次调用startAsync
是不会被监听器中的onStartAsync
方法捕获的,只有在超时后又重新开始的情况下onStartAsync
方法才会起作用。
- 设置超时:通过
setTimeout
方法设置,单位:毫秒。
一定要设置超时时间,不能无限等待下去,不然和正常的请求就一样了。。
Servlet方式实现异步请求
前面已经提到,可通过HttpServletRequest
对象中获得一个AsyncContext
对象,该对象构成了异步处理的上下文。所以,我们来实际操作下。
0.编写一个简单控制层
1 | /** |
注意:异步请求时,可以利用ThreadPoolExecutor
自定义个线程池。
1.启动下应用,查看控制台输出就可以获悉是否在同一个线程里面了。同时,可设置下等待时间,之后就会调用超时回调方法了。大家可自己试试。
1 | 2018-08-15 23:03:04.082 INFO 6732 --- [nio-8080-exec-1] c.l.l.s.controller.ServletController : 线程:http-nio-8080-exec-1 |
使用过滤器时,需要加入asyncSupported
为true
配置,开启异步请求支持。
1 | "/okong", asyncSupported = true ) (urlPatterns = |
题外话:其实我们可以利用在未执行asyncContext.complete()
方法时请求未结束这特性,可以做个简单的文件上传进度条之类的功能。但注意请求是会超时的,需要设置超时的时间下。
Spring方式实现异步请求
在
Spring
中,有多种方式实现异步请求,比如callable
、DeferredResult
或者WebAsyncTask
。每个的用法略有不同,可根据不同的业务场景选择不同的方式。以下主要介绍一些常用的用法
Callable
使用很简单,直接返回的参数包裹一层
callable
即可。
用法
1 | "/callable") ( |
控制台输出:1
22018-08-15 23:32:22.317 INFO 15740 --- [nio-8080-exec-2] c.l.l.s.controller.SpringController : 外部线程:http-nio-8080-exec-2
2018-08-15 23:32:22.323 INFO 15740 --- [ MvcAsync1] c.l.l.s.controller.SpringController : 内部线程:MvcAsync1
超时、自定义线程设置
从控制台可以看见,异步响应的线程使用的是名为:MvcAsync1
的线程。第一次再访问时,就是MvcAsync2
了。若采用默认设置,会无限的创建新线程去处理异步请求,所以正常都需要配置一个线程池及超时时间。
编写一个配置类:CustomAsyncPool.java
1 |
|
自定义一个超时异常处理类:CustomAsyncRequestTimeoutException.java
1 | /** |
同时,在统一异常处理加入对CustomAsyncRequestTimeoutException
类的处理即可,这样就有个统一的配置了。
之后,再运行就可以看见使用了自定义的线程池了,超时的可以自行模拟下:1
22018-08-15 23:48:29.022 INFO 16060 --- [nio-8080-exec-1] c.l.l.s.controller.SpringController : 外部线程:http-nio-8080-exec-1
2018-08-15 23:48:29.032 INFO 16060 --- [ oKong-1] c.l.l.s.controller.SpringController : 内部线程:oKong-1
DeferredResult
相比于
callable
,DeferredResult
可以处理一些相对复杂一些的业务逻辑,最主要还是可以在另一个线程里面进行业务处理及返回,即可在两个完全不相干的线程间的通信。
1 | /** |
控制台输出:1
2
32018-08-15 23:52:27.841 INFO 12984 --- [nio-8080-exec-2] c.l.l.s.controller.SpringController : 外部线程:http-nio-8080-exec-2
2018-08-15 23:52:27.843 INFO 12984 --- [pool-1-thread-1] c.l.l.s.controller.SpringController : 内部线程:pool-1-thread-1
2018-08-15 23:52:27.872 INFO 12984 --- [nio-8080-exec-2] c.l.l.s.controller.SpringController : 调用完成
注意:返回结果时记得调用下setResult
方法。
题外话:利用DeferredResult
可实现一些长连接的功能,比如当某个操作是异步时,我们可以保存这个DeferredResult
对象,当异步通知回来时,我们在找回这个DeferredResult
对象,之后在setResult
会结果即可。提高性能。
WebAsyncTask
使用方法都类似,只是
WebAsyncTask
是直接返回了。觉得就是写法不同而已,更多细节希望大神解答!
1 | "/webAsyncTask") ( |
控制台输出:1
2
32018-08-15 23:55:02.568 INFO 2864 --- [nio-8080-exec-1] c.l.l.s.controller.SpringController : 外部线程:http-nio-8080-exec-1
2018-08-15 23:55:02.587 INFO 2864 --- [ oKong-1] c.l.l.s.controller.SpringController : 内部线程:oKong-1
2018-08-15 23:55:02.615 INFO 2864 --- [nio-8080-exec-2] c.l.l.s.controller.SpringController : WebAsyncTask执行结束
参考资料
- https://blog.csdn.net/paincupid/article/details/52266905
- https://docs.spring.io/spring/docs/4.3.18.RELEASE/spring-framework-reference/htmlsingle/#mvc-ann-async
总结
本章节主要是讲解了
异步请求
的使用及相关配置,如超时,异常等处理。设置异步请求时,记得不要忘记设置超时时间。异步请求
只是提高了服务的吞吐量,提高单位时间内处理的请求数,并不会加快处理效率的,这点需要注意。。下一章节,讲讲使用@Async
进行异步调用相关知识。
最后
目前互联网上很多大佬都有
SpringBoot
系列教程,如有雷同,请多多包涵了。本文是作者在电脑前一字一句敲的,每一步都是自己实践的。若文中有所错误之处,还望提出,谢谢。
老生常谈
- 个人QQ:
499452441
- 微信公众号:
lqdevOps
个人博客:http://blog.lqdev.cn
完整示例:https://github.com/xie19900123/spring-boot-learning/tree/master/chapter-20