jetty6 Continuation

http://langyu.iteye.com/blog/707713

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

   但是基于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对服务器事件的轮询:

Java代码   收藏代码
  1. private void doPoll(HttpServletRequest request, AjaxResponse response)  
  2. {  
  3.     HttpSession session = request.getSession(true);  
  4.   
  5.     synchronized (mutex)  
  6.     {  
  7.         Member member = (Member)chatroom.get(session.getId());  
  8.   
  9.         // Is there any chat events ready to send?  
  10.         if (!member.hasEvents())  
  11.         {  
  12.             // No - so prepare a continuation  
  13.             Continuation continuation = ContinuationSupport.getContinuation(request, mutex);  
  14.             member.setContinuation(continuation);  
  15.   
  16.             // wait for an event or timeout  
  17.             continuation.suspend(timeoutMS);  
  18.         }  
  19.         member.setContinuation(null);  
  20.   
  21.         // send any events  
  22.         member.sendEvents(response);  
  23.     }  
  24. }  



   所以正在处理的请求被中断用于等待可以接受的聊天事件。当其它用户在聊天室中说话的时候,这种事件会由另外一个线程通过调用下面方式来发送到每个接收方。

Java代码   收藏代码
  1. class Member  
  2.     {  
  3.         // ...  
  4.         public void addEvent(Event event)  
  5.         {  
  6.             synchronized (mutex)  
  7.             {  
  8.                 _events.add(event);  
  9.                 if (getContinuation()!=null)  
  10.                     getContinuation().resume();  
  11.             }  
  12.         }  
  13.         // ...  
  14.     }  




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中支持它。

你可能感兴趣的:(jetty)