jetty的 continuations 有什么优点

如题所述

jetty在6.0以后引入了continuations, 那么什么是continuations?他有什么优点?
原始的servlet是一个连接对应一个线程,在短连接情况下都没什么问题,但是现在有越来越多的运用是基于长连接的,这样用户并发量上去的话,后台的线 程数将会以正比数上升。一个java线程一般占用64K内存,包括其他的,资源会占用很多,况且线程调度,线程锁机制等都很耗CPU。
后来出现一个请求对应一个线程。得益于NIO,连接能够在请求被处理时分配一个线程。当连接空闲时,那个线程将会被放到线程池中,而连接再次添加到NIO Select Set中去检测新的请求。这种一个请求对应一个线程的模式能应对更多的用户连接。对于ajax运用来说,轮循请求去取数据不太好,因为要不断建立连接,而 且一般连接都没取到数据,浪费很多连接,所以比较好的办法是服务器保持住那个请求,直到服务器有数据就往客户端转送或超时为止。这是一个好技术,但是它同 时也背离了一个请求一个线程的初衷,因为每个客户端在服务端都有一个请求线程保持着,因此服务端需要为每个客户端生成一个或多个线程。在大量并发用户下仍 然有问题。
jetty的continuations正是解决上面的问题而产生的。它的原理是:使用SelectChannelConnector来处理请求,这个类 是基于NIO API上的。因此使它能够不用消耗每个连接的线程就可以持有开放的连接。使用continuations suspend时,调用的实际是SelectChannelConnector.RetryContinuation.suspend,此时会抛出一个异 常RetryRequest,该异常将传播到 servlet 以外并通过过滤器链传回,并由 SelectChannelConnector 捕获。 但是发生该异常之后并没有将响应发送给客户机,请求被放到处于等待状态的 Continuation 队列中,而 HTTP 连接仍然保持打开状态。此时,为该请求提供服务的线程将返回 ThreadPool,用于为其他请求提供服务。 暂停的请求将一直保持在等待状态的 Continuation 队列,直到超出指定的时限,或者当对 resume() 方法的 Continuation 调用 resume() 时,出现上述任意一种条件时,请求将被重新提交到 servlet(通过过滤器链)。事实上,整个请求被重新进行处理,直到首次调用 suspend()。当执行第二次发生 suspend() 调用时,RetryRequest 异常不会被抛出,执行照常进行.判断的依据是:if (!_pending && !resumed && timeout >= 0) ..throw _retry;其中_pending是指是不是第二次调用了suspend....这样子,jetty中的thread pool就会有空闲线程来处理其他的请求。
continuations的引入能用很少的线程处理大量的请求。。实验见下:

public class App {
public static void main(String[] args) throws Exception {
Server server = new Server();
Connector conn = new SelectChannelConnector();
conn.setPort(8080);
server.setConnectors(new Connector[]{conn});
WebAppContext ctx = new WebAppContext();
ctx.setContextPath("/");
ctx.setWar("./webapp");
server.setHandler(ctx);
QueuedThreadPool threadPool = new QueuedThreadPool();
threadPool.setMaxThreads(100);#设置jetty的thread pool最大为100个线程。
server.setThreadPool(threadPool);
DaemonService.bind(server);
server.start();
}
}
DaemonService是一个ServletContextListener,代码为:
public void contextInitialized(ServletContextEvent sce) {
new Thread(new Runnable() {

public void run() {
while (true) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
#每次打印出当前线程池的线程大小及空闲线程
System.out.println("thread:" + server.getThreadPool().getThreads() + "#idle:" + server.getThreadPool().getIdleThreads());
}
}
}).start();
}
写一个TestServlet,对应的mapping是/test
public class TestServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
resp.getWriter().write("Test");
resp.getWriter().flush();
}
}
使用ab -c 200 -n 1000 http://localhost:8080/test 测试后结果如下:
thread:2#idle:1
thread:2#idle:1
thread:89#idle:0
thread:100#idle:0
大概还有10多个的thread:100#idle:0
thread:100#idle:0
thread:100#idle:0
thread:99#idle:19
thread:99#idle:88
thread:99#idle:98
thread:99#idle:98
thread:99#idle:98
说明了在并发200个时,jetty线程池里几乎没有空闲线程来处理其他的请求。
ab测试结果为:
Requests per second: 44.54 [#/sec] (mean)
Time per request: 4490.625 [ms] (mean)

当把TestServlet代码改为Continuation时,同样是停顿2秒
public class TestServlet extends HttpServlet {
protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
Continuation continuation = ContinuationSupport.getContinuation(req, null);
continuation.suspend(2000);
resp.getWriter().write("Test");
resp.getWriter().flush();
}
}
使用ab -c 200 -n 1000 http://localhost:8080/test 测试后结果如下:
thread:2#idle:1
thread:7#idle:6
thread:7#idle:6
thread:39#idle:26
thread:42#idle:41
thread:42#idle:36
thread:65#idle:64
thread:65#idle:46
thread:91#idle:90
thread:91#idle:90
thread:91#idle:89
thread:91#idle:90
thread:91#idle:83
说明在并发200个时,jetty线程池中一直都有很多空闲线程
ab测试结果为:
Requests per second: 79.21 [#/sec] (mean)
Time per request: 2525.000 [ms] (mean)

结论:jetty的continuations在短连接上不见得有很大的优势,但是在长连接或者在后台访问IO资源(数据库,网络等)造成的 servlet响应慢问题上,会有很大的优势,他不会让一个线程傻傻地在等待数据库访问完成,而是会放入线程池去处理其他请求。等数据库访问完后,再过来 处理。

servlet 3.0规范已经引入了异步servlet功能,写法为:
请求/url/A
AsyncContext ac = request.startAsync();
//...这边处理具体的资源操作,如db.getConnection()或其他的操作
ac.dispatch();
温馨提示:答案为网友推荐,仅供参考