Servlet 3.0 异步处理详解

Github地址html

相关系列文章:java

Servlet 3.0 开始提供了AsyncContext用来支持异步处理请求,那么异步处理请求到底可以带来哪些好处?git

Web容器通常来讲处理请求的方式是:为每一个request分配一个thread。咱们都知道thread的建立不是没有代价的,Web容器的thread pool都是有上限的。
那么一个很容易预见的问题就是,在高负载状况下,thread pool都被占着了,那么后续的request就只能等待,若是运气很差客户端会报等待超时的错误。
在AsyncContext出现以前,解决这个问题的惟一办法就是扩充Web容器的thread pool。github

可是这样依然有一个问题,考虑如下场景:web

有一个web容器,线程池大小200。有一个web app,它有两个servlet,Servlet-A处理单个请求的时间是10s,Servlet-B处理单个请求的时间是1s。
如今遇到了高负载,有超过200个request到Servlet-A,若是这个时候请求Servlet-B就会等待,由于全部HTTP thread都已经被Servlet-A占用了。
这个时候工程师发现了问题,扩展了线程池大小到400,可是负载依然持续走高,如今有400个request到Servlet-A,Servlet-B依然没法响应。segmentfault

看到问题了没有,由于HTTP thread和Worker thread耦合在了一块儿(就是同一个thread),因此致使了当大量request到一个耗时操做时,就会将HTTP thread占满,致使整个Web容器就会没法响应。oracle

可是若是使用AsyncContext,咱们就能够将耗时的操做交给另外一个thread去作,这样HTTP thread就被释放出来了,能够去处理其余请求了。app

注意,只有使用AsyncContext才可以达到上面所讲的效果,若是直接new Thread()或者相似的方式的,HTTP thread并不会归还到容器。

下面是一个官方的例子:异步

@WebServlet(urlPatterns={"/asyncservlet"}, asyncSupported=true)
public class AsyncServlet extends HttpServlet {
   /* ... Same variables and init method as in SyncServlet ... */

   @Override
   public void doGet(HttpServletRequest request, 
                     HttpServletResponse response) {
      response.setContentType("text/html;charset=UTF-8");
      final AsyncContext acontext = request.startAsync();
      acontext.start(new Runnable() {
         public void run() {
            String param = acontext.getRequest().getParameter("param");
            String result = resource.process(param);
            HttpServletResponse response = acontext.getResponse();
            /* ... print to the response ... */
            acontext.complete();
            }
      });
   }
}

陷阱

在这个官方例子里,每一个HTTP thread都会开启另外一个Worker thread来处理请求,而后把HTTP thread就归还给Web容器。可是看AsyncContext.start()方法的javadoc:async

Causes the container to dispatch a thread, possibly from a managed thread pool, to run the specified Runnable.

实际上这里并无规定Worker thread到底从哪里来,也许是HTTP thread pool以外的另外一个thread pool?仍是说就是HTTP thread pool?

The Limited Usefulness of AsyncContext.start()文章里写道:不一样的Web容器对此有不一样的实现,不过Tomcat其实是利用HTTP thread pool来处理AsyncContext.start()的。

这也就是说,咱们本来是想释放HTTP thread的,但实际上并无,由于有HTTP thread依然被用做Worker thread,只不过这个thread和接收请求的HTTP thread不是同一个而已。

这个结论咱们也能够经过AsyncServlet1SyncServlet的Jmeter benchmark看出来,二者的throughput结果差很少。启动方法:启动Main,而后利用Jmeter启动benchmark.jmx(Tomcat默认配置下HTTP thread pool=200)。

使用ExecutorService

前面看到了Tomcat并无单独维护Worker thread pool,那么咱们就得本身想办法搞一个,见AsyncServlet2,它使用了一个带Thread pool的ExecutorService来处理AsyncContext。

其余方式

因此对于AsyncContext的使用并无固定的方式,你能够根据实际须要去采用不一样的方式来处理,为此你须要一点Java concurrent programming的知识。

对于性能的误解

AsyncContext的目的并非为了提升性能,也并不直接提供性能提高,它提供了把HTTP thread和Worker thread解藕的机制,从而提升Web容器的响应能力

不过AsyncContext在某些时候的确可以提升性能,但这个取决于你的代码是怎么写的。
好比:Web容器的HTTP thread pool数量200,某个Servlet使用一个300的Worker thread pool来处理AsyncContext。
相比Sync方式Worker thread pool=HTTP thread pool=200,在这种状况下咱们有了300的Worker thread pool,因此确定可以带来一些性能上的提高(毕竟干活的人多了)。

相反,若是当Worker thread的数量<=HTTP thread数量的时候,那么就不会获得性能提高,由于此时处理请求的瓶颈在Worker thread。
你能够修改AsyncServlet2的线程池大小,把它和SyncServlet比较benchmark结果来验证这一结论。

必定不要认为Worker thread pool必须比HTTP thread pool大,理由以下:

  1. 二者职责不一样,一个是Web容器用来接收外来请求,一个是处理业务逻辑
  2. thread的建立是有代价的,若是HTTP thread pool已经很大了再搞一个更大的Worker thread pool反而会形成过多的Context switch和内存开销
  3. AsyncContext的目的是将HTTP thread释放出来,避免被操做长期占用进而致使Web容器没法响应

因此在更多时候,Worker thread pool不会很大,并且会根据不一样业务构建不一样的Worker thread pool。
好比:Web容器thread pool大小200,一个慢速Servlet的Worker thread pool大小10,这样一来,不管有多少请求到慢速操做,它都不会将HTTP thread占满致使其余请求没法处理。

相关资料

相关文章
相关标签/搜索