Jetty6 Continuation

译注:项目中用到Jetty6的Continuation机制,翻译一篇专业文章用于学习此机制
http://docs.codehaus.org/display/JETTY/Continuations

背景资料
   对于当前的很多网络应用,并行访问的用户数会远远超过服务器上的连接数。这是由于在用户与服务器会话中,当用户在做阅读内容或是填写表格等动作时,连接可能会因为这些暂停被关闭。所以成千上万的用户可以由数百个连接来提供服务。

   但是基于AJAX的应用与传统网络应用相比有不同的业务模型(Traffic profiles)。当一个用户在填写表格的时,AJAX会请求服务器获取输入数据验证和提示信息;当一个用户在读网页内容时,AJAX请求可能会去异步获取新闻或是更新网页内容。如此一个AJAX应用,它需要与服务器不间断地连接,这样前面所说的“并行访问用户数可能会远远超过服务器并发TCP/IP连接数”的情况就不会存在了。

   这样,如果有几千位用户,你就需要几千个连接;如果有数以万计的用户,你也需要数以万计的并发连接。这对Java WEB容器来说是个巨大挑战,它必须可以处理大量的并发连接。同时,对于你的应用系统,从操作系统到JVM都需要不断地被关注着。

为每个连接分配一个线程(Thread-per-Connect)
   构建一个可扩展的Servlet服务器的最主要问题之一是如果处理线程(Thread)和连接(Connection)。传统的Java IO模型为每个TCP/IP连接关联一个线程。如果你有少量非常活跃的线程,这个模型可以扩展到每秒处理大量请求。

   然而在很多WEB应用中有这样的业务模型,当用户在阅读网页或是查找链接的时候,很多HTTP长连接几乎是空闲的。在这样的模型中,“每个连接分配一个线程”的模型在大规模部署时很难让几千线程同时支持几千用户请求。

为每个请求分配一个线程(Thread-per-Request)
   采用异步IO的NIO包可以解决这个问题,它在服务器处理请求时为每个连接分配一个线程。当在两次请求之间连接空闲的时候,线程就会返回到线程池中,并且这个连接会被放入一个NIO选择组(Select set)中去侦听新的请求。“每个请求分配一个线程”的模型对服务器来说,在损失每秒最大请求数的同时可以允许更多的连接。

AJAX轮询问题
   还有一个新的问题。AJAX作为一个WEB应用模型的出现大大改变了服务器端的业务模型。由于AJAX服务器不能将事件异步地通知到AJAX客户端,所以AJAX客户端必须去服务器端轮询等待AJAX服务器端事件。为了避免频繁轮询,AJAX服务器常常持有这次轮询请求,当超时或是有新事件时才释放它。这样AJAX服务器为了当新事件到来的时候发送请求到客户端,就应当持有客户端的一个请求,而这时客户端是空闲的。这种技术思路挺好,但破坏了“每个请求分配一个线程”的模型,因为对于每个客户端在服务器端都有一个未完成的请求。这样每个服务器为每个客户端需要一个或多个线程,在成千上万个用户时就会存在问题。
Jetty6 Continuation

Jetty6 Continuation
   上述问题的解决方法就是Jetty6中引入的新特性--Continuation。当一个Java filter或是Servlet处理一个AJAX请求,可以使用Continuation对象有效地中断(suspend)请求并释放当前的线程。当中断超时或是在Continuation对象上调用resume方法时,这个请求会被唤醒(resume)。Jett6的聊天室Demo中,有下面的这些代码用于处理AJAX对服务器事件的轮询:

private void doPoll(HttpServletRequest request, AjaxResponse response)
{
    HttpSession session = request.getSession(true);

    synchronized (mutex)
    {
        Member member = (Member)chatroom.get(session.getId());

        // Is there any chat events ready to send?
        if (!member.hasEvents())
        {
            // No - so prepare a continuation
            Continuation continuation = ContinuationSupport.getContinuation(request, mutex);
            member.setContinuation(continuation);

            // wait for an event or timeout
            continuation.suspend(timeoutMS);
        }
        member.setContinuation(null);

        // send any events
        member.sendEvents(response);
    }
}


   所以正在处理的请求被中断用于等待可以接受的聊天事件。当其它用户在聊天室中说话的时候,这种事件会由另外一个线程通过调用下面方式来发送到每个接收方。
class Member
    {
        // ...
        public void addEvent(Event event)
        {
            synchronized (mutex)
            {
                _events.add(event);
                if (getContinuation()!=null)
                    getContinuation().resume();
            }
        }
        // ...
    }



Continuation如何工作
   Java中没有中断一个线程并随后唤醒它的机制,所以Jetty在后台同Java与Servlet规范一道完成这样的功能。首先当请求处理器(Request Hander)调用continuation.suspend(timeoutMS)方法时,一个RetryRequest运行时异常被抛出。这个异常会跳过所有的请求处理代码,由Jetty捕获并对它做特殊处理。Jetty不会为这种情况产生错误响应,而是将这个请求(Request)放入一个超时队列(Timeout Queue)并将这个请求的执行线程释放回线程池中。

   当超时期到了或是另外一个线程调用continuation.resume()方法时,那个请求就被取回。这时如果再调用continuation.suspend(timeoutMS)方法,这个方法要么返回null要么返回RetryRequest事件,请求处理器会按正常的流程产生对客户端的响应。

   这个机制使用HTTP请求处理中的无状态性来模拟一个中断(suspend)和唤醒(resume)动作。那种运行时异常(RetryRequest异常)可以让线程合理地退出请求处理器(Request Handler),任何上游的filter或是servlet都可以添加一些相关的安全上下文(Security Context)。请求在取回时,会重新进入filter/servlet链和一些安全上下文并在continuation的点上重新执行正常处理。

   另外,Continuation的API是简明的。如果它运行在一个非Jetty服务器上,它可以在getEvent时使用简单的wait/notify去中断请求。如果Continuation是按我想的这样在工作,我计划在Servlet3.0中支持它。

你可能感兴趣的:(Ajax,Web,应用服务器,servlet,网络应用)