面向 Java 开发人员的 Ajax: 使用 Jetty 和 Direct Web Remoting 编写可扩展的 Comet 应用程序
面向 Java 开发人员的 Ajax: 使用 Jetty 和 Direct Web Remoting 编写可扩展的 Comet 应用程序
使用 Continuations 和 Reverse Ajax 创建事件驱动 Web 应用程序
?
2007 年 8 月 02 日
受异步服务器端事件驱动的 Ajax 应用程序实现较为困难,并且难于扩展。Philip McCarthy 在其广受欢迎的 系列文章 中介绍了一种行之有效的方法:结合使用 Comet 模式(将数据推到客户机)和 Jetty 6 的 Continuations API(将 Comet 应用程序扩展到大量客户机中)。您可以方便地在 Direct Web Remoting(DWR) 2 中将 Comet 和 Continuations 与 Reverse Ajax 技术结合使用。<!-- START RESERVED FOR FUTURE USE INCLUDE FILES--><!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters --><!-- END RESERVED FOR FUTURE USE INCLUDE FILES-->
作为一种广泛使用的 Web 应用程序开发技术,Ajax 牢固确立了自己的地位,随之而来的是一些通用 Ajax 使用模式。例如,Ajax经常用于对用户输入作出响应,然后使用从服务器获得的新数据修改页面的部分内容。但是,有时 Web应用程序的用户界面需要进行更新以响应服务器端发生的异步事件,而不需要用户操作 —— 例如,显示到达 Ajax聊天应用程序的新消息,或者在文本编辑器中显示来自另一个用户的改变。由于只能由浏览器建立 Web 浏览器和服务器之间的 HTTP连接,服务器无法在改动发生时将变化 “推送” 给浏览器。
Ajax 应用程序可以使用两种基本的方法解决这一问题:一种方法是浏览器每隔若干秒时间向服务器发出轮询以进行更新,另一种方法是服务器始终打开与浏览器的连接并在数据可用时发送给浏览器。长期连接技术被称为 Comet(请参阅 参考资料)。本文将展示如何结合使用 Jetty servlet 引擎和 DWR 简捷有效地实现一个 Comet Web 应用程序。
?
现在可以观察到 servlet 响应一些同步请求的行为。清单 3 展示了控制台输出,五个使用 lynx 的并行请求。命令行启动五个 lynx 进程,将标识序号附加在请求 URL 的后面。
清单 3. 对 BlockingServlet 并发请求的输出
我提到过应该使用 Jetty 的 SelectChannelConnector 来启用 Continuations 功能。然而,Continuations API 仍然可用于传统的 SocketConnector,这种情况下 Jetty 将回退到不同的 Continuation 实现,该实现使用 wait()/notify() 方法。您的代码仍然可以编译和运行,但是却失去了非阻塞 Continuations 的优点。如果您希望继续使用非 Jetty 服务器,您应该考虑编写自己的 Continuation 包装器,在运行时期使用反射检查 Jetty Continuations 库是否可用。DWR 就使用了这种策略。
暂停的请求将一直保持在等待状态的 Continuation 队列,直到超出指定的时限,或者当对 resume() 方法的 Continuation 调用 resume() 时(稍后将详细介绍)。出现上述任意一种条件时,请求将被重新提交到 servlet(通过过滤器链)。事实上,整个请求被重新进行处理,直到首次调用 suspend()。当执行第二次发生 suspend() 调用时,RetryRequest 异常不会被抛出,执行照常进行。
现在应该可以解释 清单 5 中的输出了。每个请求依次进入 servlet 的 service() 方法后,将发送 start 消息进行响应,Continuation 的 suspend() 方法引发 servlet 异常,将释放线程使其处理下一个请求。所有五个请求快速通过 service() 方法的第一部分,并进入等待状态,并且所有 start 消息将在几毫秒内输出。两秒后,当超过 suspend() 的时限后,将从等待队列中检索第一个请求,并将其重新提交给 ContinuationServlet。第二次输出 start 消息,立即返回对 suspend() 的第二次调用,并且发送 end 消息进行响应。然后将在此执行 servlet 代码来处理队列中的下一个请求,以此类推。
因此,在 BlockingServlet 和 ContinuationServlet 两种情况中,请求被放入队列中以访问单个 servlet 线程。然而,虽然 servlet 线程执行期间 BlockingServlet 发生两秒暂停,SelectChannelConnector 中的 ContinuationServlet 的暂停发生在 servlet 之外。ContinuationServlet 的总吞吐量更高一些,因为 servlet 线程没有将大部分时间用在 sleep() 调用中。
?
?
?
?
首先,应用程序需要某种方法来生成坐标。这将由 RandomWalkGenerator 完成。从一对初始坐标对开始,每次调用它的私有 generateNextCoord() 方法时,将从该位置移动随机指定的距离,并将新的位置作为 GpsCoord 对象返回。初始化完成后,RandomWalkGenerator 将生成一个线程,该线程以随机的时间间隔调用 generateNextCoord() 方法并将生成的坐标发送给任何注册了 addListener() 的 CoordListener 实例。清单 6 展示了 RandomWalkGenerator 循环的逻辑:
清单 6. RandomWalkGenerator's run() 方法
?
create 元素的 javascript 属性指定了 DWR 用于将跟踪器公开为 JavaScript 对象的名字,在本例中,我的客户端代码没有使用该属性,而是将数据从跟踪器推入到其中。同样,还需对 web.xml 进行额外的配置,以针对 Reverse Ajax 配置 DWR,如 清单 12 所示:
清单 12. DwrServlet 的 web.xml 配置
?
如果希望最小化应用程序中使用的 JavaScript 代码的数量,可以使用
ScriptSession 编写 JavaScript 回调:将 ScriptSession 实例封装在 DWR Util 对象中。该类将提供直接操作浏览器 DOM 的简单 Java 方法,并在后台自动生成所需的脚本。现在我可以将浏览器指向跟踪器页面,DWR 将在生成坐标数据时把数据推入客户机。该实现输出生成坐标的列表,如 图 2 所示:
图 2. ReverseAjaxTracker 的输出
可以看到,使用 Reverse Ajax 创建事件驱动的 Ajax 应用程序非常简单。请记住,正是由于 DWR 使用了 Jetty Continuations,当客户机等待新事件到来时不会占用服务器上面的线程。
此时,集成来自 Yahoo! 或 Google 的地图部件非常简单。通过更改客户端回调,可轻松地将坐标传送到地图 API,而不是直接附加到页面中。图 3 展示了 DWR Reverse Ajax GPS 跟踪器在此类地图组件上标绘随机路线:
Figure 3. 具有地图 UI 的 ReverseAjaxTracker
?
?
?
?
结束语
通过本文,您了解了如何结合使用 Jetty Continuations 和 Comet 为事件驱动 Ajax应用程序提供高效的可扩展解决方案。我没有给出 Continuations可扩展性的具体数字,因为实际应用程序的性能取决于多种变化的因素。服务器硬件、所选择的操作系统、JVM 实现、Jetty配置以及应用程序的设计和通信量配置文件都会影响 Jetty Continuations 的性能。然而,Webtide 的 GregWilkins(主要的 Jetty 开发人员)曾经发布了一份关于 Jetty 6 的白皮书,对使用 Continuations 和没有使用Continuations 的 Comet 应用程序的性能进行了比较,该程序同时处理 10000 个并发请求(参阅 参考资料)。在 Greg 的测试中,使用 Continuations 能够减少线程消耗,并同时减少了超过 10 倍的栈内存消耗。
您还看到了使用 DWR 的 Reverse Ajax 技术实现事件驱动 Ajax 应用程序是多么简单。DWR不仅省去了大量客户端和服务器端编码,而且 Reverse Ajax 还从代码中将完整的服务器-推送机制抽象出来。通过更改 DWR的配置,您可以自由地在 Comet、轮询,甚至是 piggyback方法之间进行切换。您可以对此进行实验,并找到适合自己应用程序的最佳性能策略,同时不会影响到自己的代码。
如果希望对自己的 Reverse Ajax 应用程序进行实验,下载并研究 DWR 演示程序的代码—WR 源代码发行版的一部分,参阅 参考资源)将非常有帮助。如果希望亲自运行示例,还可获得本文使用的示例代码(参见 下载)。
?
?