88407013 2015-01-28
Servlet3 比 Servlet2.4最大的进步应该是异步支持了,不再像过去那样,对于每个请求,只有一个线程在处理,必须等待这个线程阻塞处理完毕后,才能给 client 返回响应。
使用异步处理,新开一条线程处理耗时的 task,这样线程不会一直堵塞,可以接着处理其他的client请求。
此处有一误区,需要澄清的是,Servlet3的异步处理,不是指Http的异步,而是指服务器端的异步处理Http请求,由以往的一个线程阻塞处理,变成了接到请求后,把费时的IO或与DB打交道的工作,交给新的线程处理,而servlet的线程不会一直阻塞在这里。而Http客户端,仍是同步的等待服务器端的处理结果。 Servlet容器(比如Tomcat)一般会分配一个线程来处理一个来自客户端的Http请求,在这个线程发送回Http响应之前,这个线程只能属于这个HTTP请求,而不能离开这个请求,去处理其他请求。
采用Servlet3.0之后,当前的线程可以开启异步处理,开启异步处理的时候会得到一个异步处理上下文对象,之后当前的线程就可以不生成HTTP响应而直接退出去处理其它的HTTP请求,其它线程可以在之后通过异步处理上下文来生成和发送那个HTTP响应。可见所谓的异步HTTP其实只是一种可以让当前的处理线程在不生成响应前就离开,而在之后再处理这个HTTP请求的机制。
使用异步servlet主要原因就是因为,在service方法中业务逻辑如果碰到io操作时间比较长的操作,这样这个service方法就会长时间占用tomcat容器线程池中的线程,这样是不利于其他请求的处理的,当线程池中的线程处理任务时,任务由于长时间io操作,肯定会阻塞线程处理其他任务,引入异步servlet的目的就是将容器线程池和业务线程池分离开。在处理大io的业务操作的时候,把这个操作移动到业务线程池中进行,释放容器线程,使得容器线程处理其他任务,在业务逻辑执行完毕之后,然后在通知tomcat容器线程池来继续后面的操作,这个操作应该是把处理结果commit到客户端或者是dispatch到其他servlet上。
从客户端看来,不论是哪种方式,浏览器都在发送完HTTP请求之后,都必须同步的等待服务器端的响应。假如浏览器发送完HTTP请求之后,可以在得到服务器处理结果之前转而处理其它事情,而在未来的某个时刻,当服务器处理完请求后,不需要客户端再发送请求,就可以发响应发回给浏览器,也许那才是真的异步HTTP了。但是这是违反HTTP的有请求才响应,无请求不响应的基本原则的。 HTTP长连接可以让客户端和服务器在同一个TCP连接中做多次请求响应,但是并不能改变客户端和服务器之间的同步请求响应模式。
尽管Servlet3.0的异步功能不能改变HTTP的协议,在本质上让浏览器和服务器之间异步的交互,但是这一功能还是有非常大的意义的。假设接受请求和发送响应的时间分别为 Req和 Resp,每个请求都要执行一个耗时P的操作O,并且O操作会让调用者阻塞,当在P时间内有n个请求发送过来时,用传统的处理方式,由于P时间内每个线程都不能处理完,servlet容器要分配F(n) = n个线程处理请求,如下图所示:
而用Servlet3.0的异步处理时,处理线程可以开启单一线程去做那个耗时P的操作,而把当前请求的异步处理上下文放入一个等待队列中,自己则接着处理其它的请求,假设这个开启异步,加入异步处理上下文的操作需要时间A,那么需要开启F(n) = n*(A+Req+Resp)/(P + Req + Resp) + 1个线程就可以在P时间内处理完所有请求。如下图所示:
假如执行操作O可以不阻塞,耗时C就返回, 那么n客户端每获得一次资源,需要发送f(n) = n*P / (Req + C + Resp) 次请求,而用异步处理的时候,只需要n次请求。可见当A足够小于P时,O阻塞访问时,异步可以用更少的线程处理更多的请求;O非阻塞访问时,异步可以减少请求次数。
以Web QQ为例来看看。用户发送消息时,假设服务器分发消息耗时P远远大于开启异步和把消息放到待分发队列的耗时A,那么采用异步处理发送消息,可以用更少的线程处理更多的发送消息请求。用户接受消息,假设平均P时间内用户才有新消息到达,而检查一次新消息的耗时远小于P,那么采用异步则可以减少很多客户端请求。