Continuation 异步化机制(转)

Jetty的Continuation机制

讨论Jetty的Continuation机制,首先需要提到Ajax技术,Ajax技术是当前开发Web应用的非常热门的技术,也是Web2.0的一个重要的组成部分。Ajax技术中的一个核心对象是XMLHttpRequest对象,这个对象支持异步请求,所谓异步请求即是指当客户端发送一个请求到服务器的时候,客户端不必一直等待服务器的响应。这样就不会造成整个页面的刷新,给用户带来更好的体验。而当服务器端响应返回时,客户端利用一个Javascript函数对返回值进行处理,以更新页面上的部分元素的值。但很多时候这种异步事件只是在很小一部分的情况下才会发生,那么怎么保证一旦服务器端有了响应之后客户端马上就知道呢,我们有两种方法来解决这个问题,一是让浏览器每隔几秒请求服务器来获得更改,我们称之为轮询。二是服务器维持与浏览器的长时间的连接来传递数据,长连接的技术称之为Comet。

大家很容易就能发现轮询方式的主要缺点是产生了大量的传输浪费。因为可能大部分向服务器的请求是无效的,也就是说客户端等待发生的事件没有发生,如果有大量的客户端的话,那么这种网络传输的浪费是非常厉害的。特别是对于服务器端很久才更新的应用程序来讲,比如邮件程序,这种浪费就更是巨大了。并且对Server端处理请求的能力也相应提高了要求。如果很长时间才向Server端发送一次请求的话,那么客户端就不能的得到及时的响应。

如果使用Comet技术的话,客户端和服务器端必须保持一个长连接,一般情况下,服务器端每一个Servlet都会独占一个线程,这样就会使得服务器端有很多线程同时存在,这在客户端非常多的情况下也会对服务器端的处理能力带来很大的挑战。

Jetty利用Java语言的非堵塞I/O技术来处理并发的大量连接。Jetty有一个处理长连接的机制:一个被称为Continuations的特性。利用Continuation机制,Jetty可以使得一个线程能够用来同时处理多个从客户端发送过来的异步请求,下面我们通过一个简化的聊天程序的服务器端的代码来演示不使用Continuation机制和使用Continuation的差别。

Continuation机制

[java]viewplaincopy在CODE上查看代码片派生到我的代码片

publicclassChatContinuationextendsHttpServlet{

publicvoiddoPost(HttpServletRequestrequest,HttpServletResponseresponse){

postMessage(request,response);

}

privatevoidpostMessage(HttpServletRequestrequest,HttpServletResponseresponse)

{

HttpSessionsession=request.getSession(true);

Peoplepeople=(People)session.getAttribute(session.getId());

if(!people.hasEvent())

{

Continuationcontinuation=

ContinuationSupport.getContinuation(request,this);

people.setContinuation(continuation);

continuation.suspend(1000);

}

people.setContinuation(null);

people.sendEvent(response);

}

}

大家注意到,首先获取一个Continuation对象,然后把它挂起1秒钟,直到超时或者中间被resume函数唤醒位置,这里需要解释的是,在调用完suspend函数之后,这个线程就可处理其他的请求了,这也就大大提高了程序的并发性,使得长连接能够获得非常好的扩展性。

如果我们不使用Continuation机制,那么程序就如清单3所示:

不使用Continuation机制

[java]viewplaincopy在CODE上查看代码片派生到我的代码片

publicclassChatextendsHttpServlet{

publicvoiddoPost(HttpServletRequestrequest,HttpServletResponseresponse){

postMessage(request,response);

}

privatevoidpostMessage(HttpServletRequestrequest,HttpServletResponseresponse)

{

HttpSessionsession=request.getSession(true);

Peoplepeople=(People)session.getAttribute(session.getId());

while(!people.hasEvent())

{

try{

Thread.sleep(1000);

}catch(InterruptedExceptione){

e.printStackTrace();

}

}

people.setContinuation(null);

people.sendEvent(response);

}

}

大家注意到在等待事件发生的时间里,线程被挂起,直到所等待的事件发生为止,但在等待过程中,这个线程不能处理其他请求,这也就造成了在客户端非常多的情况下服务器的处理能力跟不上的情况。下面我们解释一下Jetty的Continuation的机制是如何工作的。

为了使用Continuatins,Jetty必须配置为使用它的SelectChannelConnector处理请求。这个connector构建在java.nioAPI之上,允许它维持每个连接开放而不用消耗一个线程。当使用SelectChannelConnector时,ContinuationSupport.getContinuation()提供一个SelectChannelConnector.RetryContinuation实例(但是,您必须针对Continuation接口编程)。当在RetryContinuation上调用suspend()时,它抛出一个特殊的运行时异常--RetryRequest,该异常传播到servlet外并且回溯到filter链,最后被SelectChannelConnector捕获。但是不会发送一个异常响应给客户端,而是将请求维持在未决Continuations队列里,则HTTP连接保持开放。这样,用来服务请求的线程返回给ThreadPool,然后又可以用来服务其他请求。暂停的请求停留在未决Continuations队列里直到指定的过期时间,或者在它的Continuation上调用resume()方法。当任何一个条件触发时,请求会重新提交给servlet(通过filter链)。这样,整个请求被"重播"直到RetryRequest异常不再抛出,然后继续按正常情况执行。

相关推荐