WebAsyncTask异步超时的原理
写在前面
我是从今年9月初报名,9月中旬开始学习,刚开始报名的时候也有些忐忑,主要是怕学不到东西,又或者自己到底能不能按照课程计划去进行。幸运的是,我坚持下来了,在这里遇到了各位前辈,并从老师身上学到的不少东西,譬如,他们的想法、思路,在碰到一个问题的时候该如何思考,从哪里下手等等。
首先感谢的奇点的老师,他是第一个引路人,很喜欢听他讲课,深入浅出,我那时候就在想,哇,什么时候我才能够拥有这种水平,并且可以积累这么多、这么深厚的知识啊!从他的身上,我学会了怎么一步步的查看源码,我以前是很抗拒看源码的,可能是突然开窍了,又或者是喜欢这个老师,亦或者老师上课带得好,我开始一步步地步入源码的世界,突然发现,原来世界这么精彩!因为通过源码,其实自己是可以得到很多问题的答案的,也不用每次都上一些博客、论坛去找答案,因为有时候别人的不一定对的。尽管,在不懂的情况下,查看别人写的博客确实是个好选择,帮助自己本身理解问题,但能学会查看源码也是必不可少的一份技能啊。
概述
最近接触了springMVC的异步模式,总结下来有两个优点:
- 第一当然是节约tomcat容器的线程
- 可以利用异步超时,起到一定的超时降级保护
注意:在Controller中使用时,一定要注意做好接口的线程池隔离,让慢的接口使用固定数量的线程池, 否则从tomcat减少的线程会转移到应用里,导致拥塞,在部分接口下游异常的情况的情况下,会出现影响正常接口的服务
springMVC默认使用的是一个线程池,建议根据接口指定不同的线程池
原理
异步模式处理步骤概述如下:
-
当Controller返回值是WebAsyncTask的时候
-
Spring就会将Callable交给TaskExecutor去处理(一个隔离的线程池)
-
与此同时将
DispatcherServlet
里的拦截器、Filter等等都马上退出主线程,但是response仍然保持打开的状态 -
Callable线程处理完成后,Spring MVC将请求重新派发给容器**(注意这里的重新派发,和后面讲的拦截器密切相关)**
-
根据Callabel返回结果,继续处理(比如参数绑定、视图解析等等就和之前一样了)~~~~
Spring MVC异步模式中使用Filter和HandlerInterceptor
看到上面的异步访问,不免我们会新生怀疑,若是普通的拦截器HandlerInterceptor
,还生效吗?若生效,效果是怎么样的?
经过验证,如果我们使用的是普通的Spring MVC的拦截器,preHandler会执行两次。所以我们在书写preHandler的时候,一定要特别的注意,要让preHandler即使执行多次,也不要受到影响(幂等)
除此之外,Spring MVC给提供了异步拦截器,能让我们更深入的参与进去异步request的生命周期里面去。其中最为常用的为:AsyncHandlerInterceptor
疑问:WebAsyncTask是如何实现异步超时
WebAsyncTask有一个特性,可以设置异步处理线程的超时时间,一旦超时以后,就释放连接,中断线程,以保护系统资源,那么这究竟是如何实现的呢?这个问题的核心就是解决如何在线程池里,中断指定的异步线程(正在执行) 。
关键代码
每一个处理请求的线程会调用startCallableProcessing方法,在该方法内,向线程池提交task, 并将获得的future对象 ,设置到一个new CallableInterceporChain()对象中,用于超时中断该线程。通过对asyncWebRequest设置超时回调(传入匿名内部类对象),将CallableInterceporChaing关联到请求中来,最后在超时回调中执行future.cancel()
@Override
public void handleReturnValue(@Nullable Object returnValue, MethodParameter returnType,
ModelAndViewContainer mavContainer, NativeWebRequest webRequest) throws Exception {
if (returnValue == null) {
mavContainer.setRequestHandled(true);
return;
}
WebAsyncTask<?> webAsyncTask = (WebAsyncTask<?>) returnValue;
if (this