异步Servlet都不懂,谈何WebFlux?
我们日常使用的SpringMVC,基本上都不是异步Servlet,而学习WebFlux,异步Servlet是基础,因此还是花点时间来和大家聊一聊什么是异步Servlet,这有助于大家理解我们为什么需要WebFlux。1。什么是异步Servlet
先来说说什么是非异步Servlet。
在Servlet3。0之前,Servlet采用ThreadPerRequest的方式处理Http请求,即每一次请求都是由某一个线程从头到尾负责处理。
如果一个请求需要进行IO操作,比如访问数据库、调用第三方服务接口等,那么其所对应的线程将同步地等待IO操作完成,而IO操作是非常慢的,所以此时的线程并不能及时地释放回线程池以供后续使用,如果并发量很大的话,那肯定会造性能问题。
传统的MVC框架如SpringMVC也无法摆脱Servlet的桎梏,原因很简单,他们都是基于Servlet来实现的。如SpringMVC中大家所熟知的DispatcherServlet(如果大家对于SpringMVC的原理不太理解,可以查看松哥之前的系列文章SpringMVC源码解读系列,20篇干货完美收官!)。
为了解决这一问题,Servlet3。0中引入了异步Servlet,然后在Servlet3。1中又引入了非阻塞IO来进一步增强异步处理的性能。
在正式开整WebFlux之前,我们先来了解下异步Servlet的一些基本玩法。2。版本关系
我们要先看看Servlet和Tomcat之间的对应关系,毕竟异步Servlet这种事,用错了Tomcat版本可能就不支持了。
下图来自Tomcat官网(http:tomcat。apache。orgwhichversion。html):
从上图我们可以看出,Servlet3。0对应的Tomcat版本是7。0。x,Servlet3。1对应的Tomcat版本是8。0。x。
换句话说,如果我们要使用异步Servlet,Tomcat至少要7。0以上的版本;如果你还想体验一把非阻塞IO,那么Tomcat至少要8。0以上。
接下来的案例小伙伴们记得选好自己本地的Tomcat版本。3。基本玩法
先来看一个大家熟悉的同步Servlet:WebServlet(urlPatternssync)publicclassSyncServletextendsHttpServlet{OverrideprotectedvoiddoPost(HttpServletRequestrequest,HttpServletResponseresponse)throwsServletException,IOException{doGet(request,response);}OverrideprotectedvoiddoGet(HttpServletRequestrequest,HttpServletResponseresponse)throwsServletException,IOException{longstartSystem。currentTimeMillis();printLog(request,response);System。out。println(总耗时:(System。currentTimeMillis()start));}privatevoidprintLog(HttpServletRequestrequest,HttpServletResponseresponse)throwsIOException{try{Thread。sleep(3000);}catch(InterruptedExceptione){e。printStackTrace();}response。getWriter()。write(ok);}}
这个Servlet大家再熟悉不过了。
前端请求到达后,我们调用printLog方法做一些处理,同时把doGet方法执行耗时打印出来。
在printLog中,我们先休息3s,然后给前端返回一个字符串给前端。
前端发送请求,最终doGet方法中耗时3001毫秒。
这是我们大家熟知的同步Servlet。在整个请求处理过程中,请求会一直占用Servlet线程,直到一个请求处理完毕这个线程才会被释放。
接下来我们对其稍微进行改造,使之变为一个异步Servlet。
有人可能会说,异步有何难?直接把printLog方法扔到子线程里边去执行不就行了?但是这样会有另外一个问题,子线程里边没有办法通过HttpServletResponse直接返回数据,所以我们一定需要Servlet的异步支持,有了异步支持,才可以在子线程中返回数据。
我们来看改造后的代码:WebServlet(urlPatternsasync,asyncSupportedtrue)publicclassAsyncServletextendsHttpServlet{OverrideprotectedvoiddoPost(HttpServletRequestrequest,HttpServletResponseresponse)throwsServletException,IOException{doGet(request,response);}OverrideprotectedvoiddoGet(HttpServletRequestrequest,HttpServletResponseresponse)throwsServletException,IOException{longstartSystem。currentTimeMillis();AsyncContextasyncContextrequest。startAsync();CompletableFuture。runAsync(()printLog(asyncContext,asyncContext。getRequest(),asyncContext。getResponse()));System。out。println(总耗时:(System。currentTimeMillis()start));}privatevoidprintLog(AsyncContextasyncContext,ServletRequestrequest,ServletResponseresponse){try{Thread。sleep(3000);response。getWriter()。write(ok);asyncContext。complete();}catch(InterruptedExceptionIOExceptione){e。printStackTrace();}}}
这里的改造主要有如下几方面:WebServlet注解上添加asyncSupported属性,开启异步支持。调用request。startAsync();方法开启异步上下文。通过JDK8中的CompletableFuture。runAsync方法来启动一个子线程(当然也可以自己new一个子线程)。调用printLog方法时的request和response重新构造,直接从asyncContext中获取,注意,这点是【关键】。在printLog方法中,方法执行完成后,调用asyncContext。complete()方法通知异步上下文请求处理完毕。
经过上面的改造之后,现在的控制台打印出来的总耗时几乎可以忽略不计了。
也就是说,有了异步Servlet之后,后台Servlet的线程会被及时释放,释放之后又可以去接收新的请求,进而提高应用的并发能力。
第一次接触异步Servlet的小伙伴可能会有一个误解,以为用了异步Servlet后,前端的响应就会加快。这个怎么说呢?后台的并发能力提高了,前端的响应速度自然会提高,但是我们一两个简单的请求是很难看出这种提高的。4。小结
好啦,今天就和大家分享一下异步Servlet,作为WebFlux的一个前奏。至此,我们的WebFlux前奏已经更新了五篇了,即将进入WebFlux的殿堂。
原文链接:https:mp。weixin。qq。comszgrPg9DM9OkPMs8XIPiMA