青青草网站在线:Jetty 服务器的并发处理

来源:百度文库 编辑:九乡新闻网 时间:2024/05/09 19:28:05
为了使用 Continuatins,Jetty 必须配置为使用它的 SelectChannelConnector 处理请求。这个 connector 构建在 java.nio API 之上,允许它维持每个连接开放而不用消耗一个线程。当使用 SelectChannelConnector 时,ContinuationSupport.getContinuation() 提供一个 SelectChannelConnector.RetryContinuation 实例(但是,您必须针对 Continuation 接口编程)。当在 RetryContinuation 上调用 suspend() 时,它抛出一个特殊的运行时异常 -- RetryRequest,该异常传播到 servlet 外并且回溯到 filter 链,最后被 SelectChannelConnector 捕获。但是不会发送一个异常响应给客户端,而是将请求维持在未决 Continuations 队列里,则 HTTP 连接保持开放。这样,用来服务请求的线程返回给 ThreadPool,然后又可以用来服务其他请求。暂停的请求停留在未决 Continuations 队列里直到指定的过期时间,或者在它的 Continuation 上调用 resume() 方法。当任何一个条件触发时,请求会重新提交给 servlet(通过 filter 链)。这样,整个请求被"重播"直到 RetryRequest 异常不再抛出,然后继续按正常情况执行。此,在 BlockingServlet 和 ContinuationServlet 两种情况中,请求被放入队列中以访问单个 servlet 线程。然而,虽然 servlet 线程执行期间 BlockingServlet 发生两秒暂停,SelectChannelConnector 中的 ContinuationServlet 的暂停发生在 servlet 之外。ContinuationServlet 的总吞吐量更高一些,因为 servlet 线程没有将大部分时间用在 sleep() 调用中。使 Continuations 变得有用现在您已经了解到 Continuations 能够不消耗线程就可以暂停 servlet 请求,我需要进一步解释 Continuations API 以向您展示如何在实际应用中使用。resume() 方法生成一对 suspend()。可以将它们视为标准的 Object wait()/notify() 机制的 Continuations 等价体。就是说,suspend() 使 Continuation(因此也包括当前方法的执行)处于暂停状态,直到超出时限,或者另一个线程调用 resume()。suspend()/resume() 对于实现真正使用 Continuations 的 Comet 风格的服务非常关键。其基本模式是:从当前请求获得 Continuation,调用 suspend(),等待异步事件的到来。然后调用 resume() 并生成一个响应。然而,与 Scheme 这种语言中真正的语言级别的 continuations 或者是 Java 语言的 wait()/notify() 范例不同的是,对 Jetty Continuation 调用 resume() 并不意味着代码会从中断的地方继续执行。正如您刚刚看到的,实际上和 Continuation 相关的请求被重新处理。这会产生两个问题:重新执行 清单 4 中的 ContinuationServlet 代码,以及丢失状态:即调用 suspend() 时丢失作用域内所有内容。第一个问题的解决方法是使用 isPending() 方法。如果 isPending() 返回值为 true,这意味着之前已经调用过一次 suspend(),而重新执行请求时还没有发生第二次 suspend() 调用。换言之,根据 isPending() 条件在执行 suspend() 调用之前运行代码,这样将确保对每个请求只执行一次。在 suspend() 调用具有等幂性之前,最好先对应用程序进行设计,这样即使调用两次也不会出现问题,但是某些情况下无法使用 isPending() 方法。Continuation 也提供了一种简单的机制来保持状态:putObject(Object) 和 getObject() 方法。在 Continuation 发生暂停时,使用这两种方法可以保持上下文对象以及需要保存的状态。您还可以使用这种机制作为在线程之间传递事件数据的方式,稍后将演示这种方法。DWR 2 最新引入了 Reverse Ajax 概念。这种机制可以将服务器端事件 “推入” 到客户机。客户端 DWR 代码透明地处理已建立的连接并解析响应,因此从开发人员的角度来看,事件是从服务器端 Java 代码轻松地发布到客户机中。DWR 经过配置之后可以使用 Reverse Ajax 的三种不同机制。第一种就是较为熟悉的轮询方法。第二种称为 piggyback,这种机制并不创建任何到服务器的连接,相反,将一直等待直至发生另一个 DWR 服务,piggybacks 使事件等待该请求的响应。这使它具有较高的效率,但也意味着客户机事件通知被延迟到直到发生另一个不相关的客户机调用。最后一种机制使用长期的、 Comet 风格的连接。最妙的是,当运行在 Jetty 下时,DWR 能够自动检测并切换为使用 Contiuations,实现非阻塞 Comet。public void onCoord(GpsCoord gpsCoord) {  // Generate JavaScript code to call client-side  // function with coord data  ScriptBuffer script = new ScriptBuffer();  script.appendScript("updateCoordinate(")    .appendData(gpsCoord)    .appendScript(");");  // Push script out to clients viewing the page  Collection sessions =             sctx.getScriptSessionsByPage(pageUrl);              for (ScriptSession session : sessions) {    session.addScript(script);  }   public void onCoord(GpsCoord gpsCoord) {  // Generate JavaScript code to call client-side  // function with coord data  ScriptBuffer script = new ScriptBuffer();  script.appendScript("updateCoordinate(")    .appendData(gpsCoord)    .appendScript(");");  // Push script out to clients viewing the page  Collection sessions =             sctx.getScriptSessionsByPage(pageUrl);              for (ScriptSession session : sessions) {    session.addScript(script);  }   window.onload = function() {  dwr.engine.setActiveReverseAjax(true);}function updateCoordinate(coord) {  if (coord) {    var li = document.createElement("li");    li.appendChild(document.createTextNode(            coord.longitude + ", " + coord.latitude)    );    document.getElementById("coords").appendChild(li);  }}不使用 JavaScript 更新页面如果希望最小化应用程序中使用的 JavaScript 代码的数量,可以使用 ScriptSession 编写 JavaScript 回调:将 ScriptSession 实例封装在 DWR Util 对象中。该类将提供直接操作浏览器 DOM 的简单 Java 方法,并在后台自动生成所需的脚本。 tomcat5:客户端连接到达 -> 传统的SeverSocket.accept接收连接 ->  从线程池取出一个线程 -> 在该线程读取文本并且解析HTTP协议 -> 在该线程生成ServletRequest、ServletResponse,取出请求的Servlet -> 在该线程执行这个Servlet -> 在该线程把ServletResponse的内容发送到客户端连接 -> 关闭连接。    我以前理解的使用nio后的tomcat6:客户端连接到达 -> nio接收连接 -> nio使用轮询方式读取文本并且解析HTTP协议(单线程) -> 生成ServletRequest、ServletResponse,取出请求的Servlet -> 直接在本线程执行这个Servlet -> 把ServletResponse的内容发送到客户端连接 -> 关闭连接。    实际的tomcat6:客户端连接到达 -> nio接收连接 -> nio使用轮询方式读取文本并且解析HTTP协议(单线程) -> 生成ServletRequest、ServletResponse,取出请求的Servlet -> 从线程池取出线程,并在该线程执行这个Servlet -> 把ServletResponse的内容发送到客户端连接 -> 关闭连接。        从上图可以看出,BIO与NIO的不同,也导致进入客户端处理线程的时刻有所不同:tomcat5在接受连接后马上进入客户端线程,在客户端线程里解析HTTP协议,而tomcat6则是解析完HTTP协议后才进入多线程,另外,tomcat6也比5早脱离客户端线程的环境。    实际的tomcat6与我之前猜想的差别主要集中在如何处理servlet的问题上。实际上即使抛开ThreadLocal的问题,我之前理解tomcat6只使用一个线程处理的想法其实是行不同的。大家都有经验:servlet是基于BIO的,执行期间会存在堵塞的,例如读取文件、数据库操作等等。tomcat6使用了nio,但不可能要求servlet里面要使用nio,而一旦存在堵塞,效率自然会锐降。