当前位置 主页 > 服务器问题 > Linux/apache问题 >

    详解Tomcat是如何实现异步Servlet的

    栏目:Linux/apache问题 时间:2019-11-08 10:56

    前言

    通过我之前的Tomcat系列文章,相信看我博客的同学对Tomcat应该有一个比较清晰的了解了,在前几篇博客我们讨论了Tomcat在SpringBoot框架中是如何启动的,讨论了Tomcat的内部组件是如何设计以及请求是如何流转的,那么我们这边博客聊聊Tomcat的异步Servlet,Tomcat是如何实现异步Servlet的以及异步Servlet的使用场景。

    手撸一个异步的Servlet

    我们直接借助SpringBoot框架来实现一个Servlet,这里只展示Servlet代码:

    @WebServlet(urlPatterns = "/async",asyncSupported = true)
    @Slf4j
    public class AsyncServlet extends HttpServlet {
    
     ExecutorService executorService =Executors.newSingleThreadExecutor();
    
     @Override
      protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
      //开启异步,获取异步上下文
      final AsyncContext ctx = req.startAsync();
      // 提交线程池异步执行
      executorService.execute(new Runnable() {
    
    
       @Override
       public void run() {
        try {
         log.info("async Service 准备执行了");
         //模拟耗时任务
         Thread.sleep(10000L);
         ctx.getResponse().getWriter().print("async servlet");
         log.info("async Service 执行了");
        } catch (IOException e) {
         e.printStackTrace();
        } catch (InterruptedException e) {
         e.printStackTrace();
        }
        //最后执行完成后完成回调。
        ctx.complete();
       }
      });
     }
    

    上面的代码实现了一个异步的Servlet,实现了 doGet 方法注意在SpringBoot中使用需要再启动类加上 @ServletComponentScan 注解来扫描Servlet。既然代码写好了,我们来看看实际运行效果。

    我们发送一个请求后,看到页面有响应,同时,看到请求时间花费了10.05s,那么我们这个Servlet算是能正常运行啦。有同学肯定会问,这不是异步servlet吗?你的响应时间并没有加快,有什么用呢?对,我们的响应时间并不能加快,还是会取决于我们的业务逻辑,但是我们的异步servlet请求后,依赖于业务的异步执行,我们可以立即返回,也就是说,Tomcat的线程可以立即回收,默认情况下,Tomcat的核心线程是10,最大线程数是200,我们能及时回收线程,也就意味着我们能处理更多的请求,能够增加我们的吞吐量,这也是异步Servlet的主要作用。

    异步Servlet的内部原理

    了解完异步Servlet的作用后,我们来看看,Tomcat是如何是先异步Servlet的。其实上面的代码,主要核心逻辑就两部分, final AsyncContext ctx = req.startAsync();ctx.complete(); 那我们来看看他们究竟做了什么?

     public AsyncContext startAsync(ServletRequest request,
       ServletResponse response) {
      if (!isAsyncSupported()) {
       IllegalStateException ise =
         new IllegalStateException(sm.getString("request.asyncNotSupported"));
       log.warn(sm.getString("coyoteRequest.noAsync",
         StringUtils.join(getNonAsyncClassNames())), ise);
       throw ise;
      }
    
      if (asyncContext == null) {
       asyncContext = new AsyncContextImpl(this);
      }
    
      asyncContext.setStarted(getContext(), request, response,
        request==getRequest() && response==getResponse().getResponse());
      asyncContext.setTimeout(getConnector().getAsyncTimeout());
    
      return asyncContext;
     }