欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

Java Servlet3.0异步处理问题

程序员文章站 2023-12-04 11:37:40
通过本篇文章主要给大家讲解了在java开发中servlet3.0异步处理遇到的问题以及处理办法,以下是具体内容: servlet 3.0 开始提供了asynccontex...

通过本篇文章主要给大家讲解了在java开发中servlet3.0异步处理遇到的问题以及处理办法,以下是具体内容:

servlet 3.0 开始提供了asynccontext用来支持异步处理请求,那么异步处理请求到底能够带来哪些好处?

web容器一般来说处理请求的方式是:为每个request分配一个thread。我们都知道thread的创建不是没有代价的,web容器的thread pool都是有上限的。
那么一个很容易预见的问题就是,在高负载情况下,thread pool都被占着了,那么后续的request就只能等待,如果运气不好客户端会报等待超时的错误。
在asynccontext出现之前,解决这个问题的唯一办法就是扩充web容器的thread pool。

但是这样依然有一个问题,考虑以下场景:

有一个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依然无法响应。

看到问题了没有,因为http thread和worker thread耦合在了一起,所以导致了当大量request到一个耗时操作时,就会将http thread占满,导致整个web容器就会无法响应。

但是如果使用asynccontext,我们就可以将耗时的操作交给另一个thread去做,这样http thread就被释放出来了,可以去处理其他请求了。

注意,只有使用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:

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不是同一个而已。

这个结论我们也可以通过asyncservlet1和syncservlet的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大,理由如下:

两者职责不同,一个是web容器用来接收外来请求一个是处理业务逻辑

thread的创建是有代价的,如果http thread pool已经很大了再搞一个更大的worker thread pool反而会造成过多的context switch和内存开销

asynccontext的目的是将http thread释放出来,避免被操作长期占用进而导致web容器无法响应

所以在更多时候,worker thread pool不会很大,而且会根据不同业务构建不同的worker thread pool。

比如:web容器thread pool大小200,一个慢速servlet的worker thread pool大小10,这样一来,无论有多少请求到慢速操作,它都不会将http thread占满导致其他请求无法处理。