jetty continuation基本原理及实现

来源:互联网 发布:杭州旅游 知乎 编辑:程序博客网 时间:2024/05/18 20:52

背景

在io密集型的web 应用,如何能更好地提升后台性能,jetty continuation是一个选择

现在特别流行的说法就是事件驱动,看看node.js以及redis, 

jetty continuation也不例外


Jetty7发布了,Jetty7支持servlet 2.5,且对Jetty6做了很大的重构,使之更合理更高效。
Jetty的Http异步处理模式,包括Jetty HttpClient(异步的HttpClient),Jetty Continuation(异步的Http Request/Respoinse),都是很吸引人的技术,有很多很好的应用,比如在线聊天室,实时股票行情表,异步Ajax代理等等,都可以用Jetty的异步处理模式来实现。

趁Jetty7的到来,赶紧享受一下这道技术美味。

Jetty Continuation 实际上是一种异步Http技术,他能让Http连接挂起,直到超时或者异步事件发生时,Http连接可以恢复。

Jetty Continuation 的技术应用起来不复杂,有几个关键的API,和两种设计模式:

API: 
(1) 得到Continuation
Continuation continuation = ContinuationSupport.getContinuation(request);

(2) 挂起Http请求
void doGet(HttpServletRequest request, HttpServletResponse response)
{
    ...
    continuation.setTimeout(long);  // 可选:设置continuation 超时
    continuation.suspend();
    ...
}

(3) 恢复Http连接,一旦异步事件发生了,可以通过异步事件的回调函数来恢复Http连接
void myAsyncCallback(Object results)
{
    continuation.setAttribute("results", results);
    continuation.resume();
}

(4) 完成Http连接,通常用在异步事件回调函数里返回Http Response时:
void myAsyncCallback(Object results)
{
    writeResults(continuation.getServletResponse(), results); // 将异步事件结果result,通过Response返回客户端
    continuation.complete();
}

(5)监听continuation事件
void doGet(HttpServletRequest request, HttpServletResponse response)
{
    ...
    Continuation continuation = ContinuationSupport.getContinuation(request);
    
continuation.addContinuationListener(new ContinuationListener()
    {
      public void onTimeout(Continuation continuation) { ... } // 超时事件
      public void onComplete(Continuation continuation) { ... } // 完成事件
    });
 
    continuation.suspend();
    ...
}



一个例子

[java] view plaincopy
  1. package org.kaka.web;  
  2.   
  3. import java.io.IOException;  
  4. import java.io.PrintWriter;  
  5.   
  6. import javax.servlet.ServletException;  
  7. import javax.servlet.http.HttpServlet;  
  8. import javax.servlet.http.HttpServletRequest;  
  9. import javax.servlet.http.HttpServletResponse;  
  10.   
  11. import org.eclipse.jetty.continuation.Continuation;  
  12. import org.eclipse.jetty.continuation.ContinuationSupport;  
  13.   
  14. public class SimpleSuspendResumeServlet extends HttpServlet {  
  15.   
  16.     /** 
  17.      *  
  18.      */  
  19.     private static final long serialVersionUID = 6112996063962978130L;  
  20.   
  21.     private MyAsyncHandler myAsyncHandler;  
  22.   
  23.     public void init() throws ServletException {  
  24.   
  25.         myAsyncHandler = new MyAsyncHandler() {  
  26.             public void register(final MyHandler myHandler) {  
  27.                 new Thread(new Runnable() {  
  28.                     public void run() {  
  29.                         try {  
  30.                             Thread.sleep(10000);  
  31.                             myHandler.onMyEvent("complete!");  
  32.                         } catch (InterruptedException e) {  
  33.                             // TODO Auto-generated catch block  
  34.                             e.printStackTrace();  
  35.                         }  
  36.   
  37.                     }  
  38.                 }).start();  
  39.             }  
  40.         };  
  41.   
  42.     }  
  43.   
  44.     public void doGet(HttpServletRequest request, HttpServletResponse response)  
  45.             throws ServletException, IOException {  
  46.         // if we need to get asynchronous results  
  47.         //Object results = request.getAttribute("results");  
  48.         final PrintWriter writer = response.getWriter();  
  49.         final Continuation continuation = ContinuationSupport  
  50.                 .getContinuation(request);  
  51.         //if (results == null) {  
  52.         if (continuation.isInitial()) {  
  53.               
  54.             //request.setAttribute("results","null");  
  55.             sendMyFirstResponse(response);  
  56.             // suspend the request  
  57.             continuation.suspend(); // always suspend before registration  
  58.   
  59.             // register with async service. The code here will depend on the  
  60.             // the service used (see Jetty HttpClient for example)  
  61.             myAsyncHandler.register(new MyHandler() {  
  62.                 public void onMyEvent(Object result) {  
  63.                     continuation.setAttribute("results", result);  
  64.                       
  65.                     continuation.resume();  
  66.                 }  
  67.             });  
  68.             return// or continuation.undispatch();  
  69.         }  
  70.   
  71.         if (continuation.isExpired()) {  
  72.             sendMyTimeoutResponse(response);  
  73.             return;  
  74.         }  
  75.          //Send the results  
  76.         Object results = request.getAttribute("results");  
  77.         if(results==null){  
  78.             response.getWriter().write("why reach here??");  
  79.             continuation.resume();  
  80.             return;  
  81.         }  
  82.         sendMyResultResponse(response, results);  
  83.     }  
  84.   
  85.     private interface MyAsyncHandler {  
  86.         public void register(MyHandler myHandler);  
  87.     }  
  88.   
  89.     private interface MyHandler {  
  90.         public void onMyEvent(Object result);  
  91.     }  
  92.       
  93.     private void sendMyFirstResponse(HttpServletResponse response) throws IOException {  
  94.         //必须加上这一行,否者flush也没用,为什么?  
  95.         response.setContentType("text/html");  
  96.         response.getWriter().write("start");  
  97.         response.getWriter().flush();  
  98.   
  99.     }  
  100.   
  101.     private void sendMyResultResponse(HttpServletResponse response,  
  102.             Object results) throws IOException {  
  103.         //response.setContentType("text/html");  
  104.         response.getWriter().write("results:" + results);  
  105.         response.getWriter().flush();  
  106.   
  107.     }  
  108.   
  109.     private void sendMyTimeoutResponse(HttpServletResponse response)  
  110.             throws IOException {  
  111.         response.getWriter().write("timeout");  
  112.   
  113.     }  
  114.   
  115. }  


这个例子到底干了什么?


出于演示的目的,这个例子格外简单。模拟了在web请求中如何处理一个耗时阻塞10s的io操作


  • 常规的程序遇到这种情况(假设没有设置超时),通常会等待10s,线程会白白浪费在哪,一旦请求一多,线程池及等待队列会满掉,从而导致网站无法服务
  • 稍微好点的程序恐怕会使用futuretask来解决问题,但无论怎样,futuretask带有轮询的性质,或多或少会带有阻塞
  • jetty continuation更进了一步, 采用事件驱动的方式来通知请求完成,丝毫不浪费一点io时间,一旦遇到阻塞,当前worker线程会结束,这样就可以服务其他请求,等耗时操作处理完毕通知jetty,此时jetty会再动用一个新的worker线程再次处理请求

常见流程

  • 具体流程1,耗时操作在AsyncContinuation默认超时(30s,可以设置)之内完成
  1. 当请求到来,被selector线程感知
    •  selector线程,会实例化org.eclipse.jetty.server.Request以及org.eclipse.jetty.server.AsyncContinuation
    • AsyncContinuation实例此时状态为__IDLE
  2. worker线程A会获取到selector线程派发的这个请求
    • 流程进入org.eclipse.jetty.server.AsyncHttpConnection(AbstractHttpConnection).handleRequest()
    • 调用_request._async.handling(),AsyncContinuation实例的状态__IDLE-->__DISPATCHED
    • 调用真正的业务逻辑server.handle(this);业务逻辑中调用AsyncContinuation.suspend(ServletContext, ServletRequest, ServletResponse) ,会将AsyncContinuation实例的状态__DISPATCHED-->__ASYNCSTARTED
    • 最后调用_request._async.unhandle(),AsyncContinuation实例的状态__ASYNCSTARTED-->__ASYNCWAIT
      • 会将suspend的那个请求任务放入selector线程的超时队列(和redis 事件框架的做法非常类似)
    • 为了简单期间,假设在上诉过程中,耗时操作还没完成,此时worker线程A被回收将会服务其他请求,虽然当前请求并未完成
  3. 匿名应用线程(在jetty woker线程另外启动的线程)完成耗时操作
    • 调用AsyncContinuation.resume() ,此时AsyncContinuation实例的状态__ASYNCWAIT-->__REDISPATCH
    • 然后会往worker线程队列或者selector线程队列中添加一个请求task,以此来触发jetty再次完成未完成的请求
  4. worker线程B再次处理那个请求
    • 流程进入org.eclipse.jetty.server.AsyncHttpConnection(AbstractHttpConnection).handleRequest()
    • 调用_request._async.handling(),AsyncContinuation实例的状态__REDISPATCH-->__REDISPATCHED
    • 调用server.handleAsync(this);再次进入业务逻辑,此时耗时操作已完成,可以输出最后的结果
    • 调用_request._async.unhandle()   AsyncContinuation实例的状态__REDISPATCHED-->__UNCOMPLETED
    • 调用_request._async.doComplete(),AsyncContinuation实例的状态__UNCOMPLETED-->__COMPLETED 
    • 请求结束

  • 具体流程2,耗时操作超过了AsyncContinuation默认超时时间
  1. 同上
  2. 同上
  3. selector线程轮询中感知到耗时任务超时
    • 此时在轮询中能感知并获取这个task,见org.eclipse.jetty.io.nio.SelectorManager$SelectSet.doSelect()
    • 将此task丢入worker线程队列
  4. worker线程C处理超时任务
    • 调用AsyncContinuation.expired() ,此时AsyncContinuation实例的状态__ASYNCWAIT-->__REDISPATCH
    • 然后会往worker线程队列或者selector线程队列中添加一个请求task,以此来触发jetty再次完成未完成的请求
  5. 同上面的4

  • 具体流程3,耗时操作非常快
  1. 同上
  2. worker线程A会获取到selector线程派发的这个请求
    • 流程进入org.eclipse.jetty.server.AsyncHttpConnection(AbstractHttpConnection).handleRequest()
    • 调用_request._async.handling(),AsyncContinuation实例的状态__IDLE-->__DISPATCHED
    • 调用真正的业务逻辑server.handle(this);业务逻辑中调用AsyncContinuation.suspend(ServletContext, ServletRequest, ServletResponse) ,会将AsyncContinuation实例的状态__DISPATCHED-->__ASYNCSTARTED​
  3. 匿名应用线程(在jetty woker线程另外启动的线程)完成耗时操作,在2)中调用_request._async.unhandle()之前完成
    • 调用AsyncContinuation.resume() ,此时AsyncContinuation实例的状态__ASYNCSTARTED-->__REDISPATCHING
  4. 还是那个work线程A,注意此时3已经完成
    • 调用_request._async.unhandle(),,AsyncContinuation实例的状态__REDISPATCHING-->__REDISPATCHED
    • 由于此时_request._async.unhandle()返回false,再次进入循环调用server.handleAsync(this);再次进入业务逻辑,此时耗时操作已完成,可以输出最后的结果
    • 调用_request._async.unhandle()   AsyncContinuation实例的状态__REDISPATCHED-->__UNCOMPLETED
    • 调用_request._async.doComplete(),AsyncContinuation实例的状态__UNCOMPLETED-->__COMPLETED
    • 请求结束 


小结


总体来讲

  • jetty continuation需要在应用中使用应用级的线程池来完成一些io任务,这个在普通的web编程并不常见
  • 在应用的第一次请求中需要
    • 调用AsyncContinuation.suspend(完成一个状态的转换,以及产生一个超时任务)
    • 将耗时任务派发给应用线程池
    • 完毕后,jetty回收该请求线程
  • io任务在应用线程中完成后,然后通过AsyncContinuation.resume或者complete等方法通知jetty任务完成
  • jetty然后会再次分配一个worker线程处理该请求(如果逻辑复杂,如并行的多次Io会分配多个worker线程)
  • 引入jetty continuation带来的负面作用是编程模型会变得复杂,需要仔细的切割各类io任务

此例子在jetty7.2和jetty7.6中测试通过,但在jetty8中运行失败(因为调用flush导致request中_header为空从而引发NPE[jetty8的bug?],但如果不掉用有达不大展示效果)
0 0
原创粉丝点击