我们每天用的SpringMVC基本不是异步Servlet,而是学习WebFlux,异步Servlet是基础,所以宋哥还是抽空跟大家聊聊什么是异步Servlet,有助于大家理解我们为什么做你需要WebFlux。1、什么是异步Servlet我们先来说说什么是非异步Servlet。在Servlet3.0之前,Servlet采用Thread-Per-Request来处理Http请求,即每个请求从头到尾都由某个线程处理。如果一个请求需要IO操作,比如访问数据库,调用第三方服务接口等,相应的线程会同步等待IO操作完成,IO操作很慢,所以线程此时不能及时释放回线程池,以供后续使用。如果并发量很大,肯定会造成性能问题。SpringMVC等传统的MVC框架都摆脱不了Servlet的束缚,原因很简单,都是基于Servlet实现的。为了解决这个问题,在Servlet3.0中引入了异步Servlet,随后在Servlet3.1中引入了非阻塞IO,进一步提升了异步处理的性能。在正式推出WebFlux之前,我们先了解一下异步Servlet的一些基本玩法。2、版本关系我们首先要看一下Servlet和Tomcat的对应关系。毕竟,如果使用了错误版本的Tomcat,可能会不支持异步Servlet之类的东西。下图来自Tomcat官网(http://tomcat.apache.org/whichversion.html):从上图我们可以看出Servlet3.0对应的Tomcat版本是7.0.x,而TomcatServlet3.1对应的版本是8.0.x。也就是说,如果我们要使用异步Servlet,Tomcat至少要7.0以上版本;如果想体验非阻塞IO,那么Tomcat至少要8.0以上。接下来的情况小伙伴们记得选择自己本地的Tomcat版本。3.基本玩法先来看一个大家熟悉的同步Servlet:@WebServlet(urlPatterns="/sync")publicclassSyncServletextendsHttpServlet{@OverrideprotectedvoiddoPost(HttpServletRequestrequest,HttpServletResponseresponse)throwsServletException,IOException{doGet(request,response);}@OverrideprotectedvoiddoGet(HttpServletRequestrequest,HttpServletResponseresponse)throwsServletException,IOException{longstart=System.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返回数据,所以我们必须需要ServletAsynchronous的支持,有了异步的支持,才能在子线程返回数据。我们来看改造后的代码:@WebServlet(urlPatterns="/async",asyncSupported=true)publicclassAsyncServletextendsHttpServlet{@OverrideprotectedvoiddoPost(HttpServletRequestrequest,HttpServletResponseresponse)throwsServletException,IOException{doGet(request,response);}@OverrideprotectedvoiddoGet(HttpServletRequestrequest,HttpServletResponseresponse)throwsServletException,IOException{longstart=System.currentTimeMillis();AsyncContextasyncContext=request.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(InterruptedException|IOExceptione){e.printStackTrace();}}}这里的改造主要有以下几个方面:在@WebServlet注解中添加asyncSupported属性,开启异步支持。调用request.startAsync();启动异步上下文的方法。通过JDK8中的CompletableFuture.runAsync方法启动一个子线程(当然你也可以自己新建一个子线程)。request和response是调用printLog方法时重构的,直接从asyncContext中获取。请注意,这是[key]。在printLog方法中,方法执行后,调用asyncContext.complete()方法通知异步上下文请求已经处理完毕。经过以上改造,目前控制台打印出来的总耗时几乎可以忽略不计。也就是说,有了异步的Servlet,后台Servlet的线程会及时释放,释放后才能接收新的请求,从而提高应用的并发性。第一次接触异步servlet的朋友可能会有一个误区,认为使用了异步servlet后,前端的响应会加快。你怎么说这个?随着后台并发能力的提高,前端的响应速度自然会提高,但是我们很难通过一两个简单的请求看到这种提升。4.总结好了,今天给大家分享异步Servlet作为WebFlux的前奏。至此,我们的WebFlux序曲已经更新了五次,即将步入WebFlux的殿堂。本文转载自微信公众号“江南的一场小雨”,可通过以下二维码关注。转载本文请联系江南一点鱼公众号。
