引言
Jetty的NIO可以有效地减少请求的线程数,但是对于servlet而言,依然是每个servlet需要一个线程,Continuation的出现有效地解决了servlet中耗时操作占用请求线程的境况。
Continuation用于处理异步请求,可以在请求处理的上下文交给应用线程去处理并挂起当前请求,这样减少了请求线程的数量,最后当应用线程处理完成后恢复该请求。比如使用该功能可以解决ajax长轮询服务端的境况,异步长连接的功能可见一斑。
Jetty中的Continunation官方文档:http://wiki.eclipse.org/Jetty/Feature/Continuations
Servlet3中规范:http://docs.oracle.com/javaee/6/api/javax/servlet/AsyncContext.html
而Jetty8已经将Continuation集成到了整个框架中,虽然目前并没有很大的应用场景,但是了解该功能亦能无障碍地阅读Jetty的源码。
Continunation结构
1、Continunation的类图
究其名字就是异步长连接的含义,首先了解下其体系结构
蓝色的留作拓展;红色的是恢复请求的基础;黄色的持有Continunation,拥有每个阶段的状态,因此可以有效地控制请求的流转。
2、Continunation的状态
private static final int __IDLE=0; // Idle request private static final int __DISPATCHED=1; // Request dispatched to filter/servlet private static final int __ASYNCSTARTED=2; // Suspend called, but not yet returned to container private static final int __REDISPATCHING=3;// resumed while dispatched private static final int __ASYNCWAIT=4; // Suspended and parked private static final int __REDISPATCH=5; // Has been scheduled private static final int __REDISPATCHED=6; // Request redispatched to filter/servlet private static final int __COMPLETING=7; // complete while dispatched private static final int __UNCOMPLETED=8; // Request is completable private static final int __COMPLETED=9; // Request is complete
图中不同颜色对应这请求不同的流程,理解该图对于本节的总结至关重要。
1)蓝色的表示正常请求流程
2)红色的表示请求被挂起,并交出线程
3)橙色的表示请求先是被挂起,交出线程前,又被resume恢复了,因此会重复被server去handle。
4)黑色的表示被挂起的请求在调用resume或者超时的情况下被重新恢复。
5)绿色有两层涵义:线程挂起后交出线程前和交出线程之后。可以看到,无论哪种情况走的流程都是一样的。但是区别于resume,complete正如其含义:“完成,结束”,请求是不会再次被递交给server去执行的,因此只适用于交出线程前需要提前结束的时候调用(交出线程之后就算超时了被再次调用也不会被server去执行,因此没有意义)
6)为了简要,图中状态图并不全,但是对于理解Continunation已经足够了。
Jetty中的Continunation
对于Jetty中Continunation的理解其实只要理解到挂起的Request如何管理和Continunation在Jetty请求的处理流程中的地位即可。
1、AbstractHttpConnection
protected void handleRequest() throws IOException { boolean error = false; String threadName=null; Throwable async_exception=null; try { if (LOG.isDebugEnabled()) { threadName=Thread.currentThread().getName(); Thread.currentThread().setName(threadName+" - "+_uri); } final Server server=_server; //标志服务器处理请求了,修改状态位 boolean handling=_request._async.handling() && server!=null && server.isRunning(); while (handling) { _request.setHandled(false); String info=null; try { _uri.getPort(); String path = null; try { path = _uri.getDecodedPath(); } catch (Exception e) { LOG.warn("Failed UTF-8 decode for request path, trying ISO-8859-1"); LOG.ignore(e); path = _uri.getDecodedPath(StringUtil.__ISO_8859_1); } info=URIUtil.canonicalPath(path); if (info==null && !_request.getMethod().equals(HttpMethods.CONNECT)) { if (_uri.getScheme()!=null && _uri.getHost()!=null) info="/"; else throw new HttpException(400); } _request.setPathInfo(info); if (_out!=null) _out.reopen(); if (_request._async.isInitial()) { _request.setDispatcherType(DispatcherType.REQUEST); _connector.customize(_endp, _request); //应用的入口 server.handle(this); } else { _request.setDispatcherType(DispatcherType.ASYNC); server.handleAsync(this); } } catch (ContinuationThrowable e) { LOG.ignore(e); } catch (EofException e) { async_exception=e; LOG.debug(e); error=true; _request.setHandled(true); if (!_response.isCommitted()) _generator.sendError(500, null, null, true); } catch (RuntimeIOException e) { async_exception=e; LOG.debug(e); error=true; _request.setHandled(true); } catch (HttpException e) { LOG.debug(e); error=true; _request.setHandled(true); _response.sendError(e.getStatus(), e.getReason()); } catch (Throwable e) { async_exception=e; LOG.warn(String.valueOf(_uri),e); error=true; _request.setHandled(true); _generator.sendError(info==null?400:500, null, null, true); } finally { //标志服务器已经处理完请求了,修改状态位并判断是不是需要再次被Server处理一次,主要适用于一次请求线程暂时被挂起又被resume了。 handling = !_request._async.unhandle() && server.isRunning() && _server!=null; } } } finally { if (threadName!=null) Thread.currentThread().setName(threadName); if (_request._async.isUncompleted()) { //请求处理的最后一步,主要是记录一些错误信息 _request._async.doComplete(async_exception); if (_expect100Continue) { LOG.debug("100 continues not sent"); _expect100Continue = false; if (!_response.isCommitted()) _generator.setPersistent(false); } if(_endp.isOpen()) { if (error) { _endp.shutdownOutput(); _generator.setPersistent(false); if (!_generator.isComplete()) _response.complete(); } else { if (!_response.isCommitted() && !_request.isHandled()) _response.sendError(HttpServletResponse.SC_NOT_FOUND); _response.complete(); if (_generator.isPersistent()) _connector.persist(_endp); } } else { _response.complete(); } _request.setHandled(true); } } }
2、在请求处理完了之后服务器会判断是不是有被挂起的标记,即ASYNCSTARTED,如果有,则加入超时队列中。
protected boolean unhandle() { synchronized (this) { switch(_state) { case __ASYNCSTARTED: _initial=false; _state=__ASYNCWAIT; scheduleTimeout(); // could block and change state. if (_state==__ASYNCWAIT) return true; else if (_state==__COMPLETING) { _state=__UNCOMPLETED; return true; } _initial=false; _state=__REDISPATCHED; return false; } } }
随后注册到SelectSet的TimeOut队列中,在每次doSelect的时候被检查。
SelectSet在进行doSelect时的检查逻辑:
now=System.currentTimeMillis(); _timeout.setNow(now); Task task = _timeout.expired(); while (task!=null) { if (task instanceof Runnable) dispatch((Runnable)task); task = _timeout.expired(); }