servlet的异步处理机制
Java servlet是大家公认的服务器端web技术的标准,包括jsp,jsf,和大量的web框架,soap,RESTful web service api ,还有新闻供应。servlet运行在这些技术下面,以保证这些技术可以运行在任何java web服务器上。所以servlet的任何变化都会对所有与服务器有关的技术产生影响。
已经在2009年1月通过公众审核的servlet3.0,是一个包含很多重要特性的最新版本,它的出现将会给java web开发者带来更好的体验。下面就是servlet3.0所包含的新的特性。
1,支持异步处理。
2,配置简单。
3,可插入性。
4,增强既存api。
支持异步处理是servlet3.0的最重要的特征,它将更便捷服务器端ajax application的开发,以下将重点讲述servlet在支持异步处理上所做的处理,主要是连接和线程消耗。以及当今应用异步处理的实现,例如:comet和reverse ajax。最后还将介绍有关servlet3.0的其他特性。
异步支持:背景概念
web2.0迅速地改变了服务器与客户端的通信方式,servlet3.0的支持异步处理正是基于这种变化的回应。为了彻底地理解异步处理,首先我们先回顾一下http通信的进化史。
http1.0和http1.1
http1.1标准的一个重要的改善部分是能够建立持续的链接,在http1.0时代,服务器与客户端的通信在一次请求和回应后就会被关闭,但是在http1.1标准下,在多次请求的过程中,链接能够持续保持和可用。持续的连接减少了网络连接缓慢的可见状态,因为客户端不必在每次请求后都重新建立tcp连接。
基于连接的线程
解决怎样使服务器能更具有扩展性是现在许多研究人员正在进行的课题,基于http1.1持续连接的http连接线程,是研究人员们所采用的一个通用的解决方案,在这个策略下,每一个客户端与服务器端的连接都与一个服务器端的线程有联系,线程由一个服务器控制的线程池来管理,一旦一个连接关闭,负责这个连接的线程将会被收回,并重新放回线程池以备其他连接使用。依靠硬件配置,这种方式可以处理大量的并行连接,有试验表明,服务器端的内存消耗会随着连接数量的增加成比例的增长,原因很简单,线程与内存使用是成比例的。所以配置了固定数量线程的服务器会产生线程死锁现象,因此一旦服务器线程池里的线程全部被占用,来自客户端的请求就会被拒绝。另外,有很多的站点,用户请求服务器页面的机会很少,服务器连接线程在很长时间都处于闲置状态,这也是一种资源的浪费。
基于请求的线程
由于在java4的nio中新的i/o api中出现的无阻塞(non-blocking i/o)i/o处理能力,把原来的一个连接需要附加一个线程的处理方式变成只有当该请求被处理的时候才被分派线程的处理方式。当连接在请求的过程中处于闲置状态时,它会被放置到一个中央选择处理集合里,来观测新的请求而不是消耗一个独立的线程。这种模式被称为基于请求的进程。潜在的允许服务器在固定线程的基础上处理不断增长的用户连接。在相同硬件条件下,这种方式比基于连接进程方式扩展的更好。 现在很多web服务器,如:tomcat,jetty,glassfish,weglogic,websphere都通过java NIO应用这种方式。对于应用程序开发人员,这些都是以隐藏的方式进行的,不会通过servlet api暴露出来。
迎接ajax挑战
为了提供更丰富的用户体验和交互,越来越多的web application应用了ajax,应用ajax应用的用户会获得更多的与服务器的交互,不同于以往请求的是,ajax用户请求可以更频繁的被发送到服务器端,另外,客户端和脚本也在促使着服务器技术的更新,越来越多的同步请求 导致大量的线程被消耗,这使得基于请求线程方式的优点几乎销蚀殆尽。
运行缓慢,有限的资源
有些运行缓慢的后台常规工作更加恶化这个形势,例如:有些请求可能因为已经废弃的jdbc连接池或者低通量服务器端的原因而被阻塞,等待请求的线程可能被阻止很长时间直到资源再次可用。最好的办法是把请求放到一个中央集权的队列中等待可用资源,并回收不可用的线程。这种方式有效地协调了大量请求和运行缓慢的后台程序。它同时也暗示了在某种程度上放在队列里的请求是没有任何线程与之对应的。servlet3.0的支持异步通信正是为了在一个广泛的可移植的方式上(不管是否应用ajax)实现这个目标。下面的代码将阐述它是怎样实现的:
@WebServlet(name="myServlet", urlPatterns={"/slowprocess"}, asyncSupported=true)
public class MyServlet extends HttpServlet {
public void doGet(HttpServletRequest request, HttpServletResponse response) {
AsyncContext aCtx = request.startAsync(request, response);
ServletContext appScope = request.getServletContext();
((Queue<AsyncContext>)appScope.getAttribute("slowWebServiceJobQueue")).add(aCtx);
}
}
@WebServletContextListener
public class SlowWebService implements ServletContextListener {
public void contextInitialized(ServletContextEvent sce) {
Queue<AsyncContext> jobQueue = new ConcurrentLinkedQueue<AsyncContext>();
sce.getServletContext().setAttribute("slowWebServiceJobQueue", jobQueue);
// pool size matching Web services capacity
Executor executor = Executors.newFixedThreadPool(10);
while(true)
{
if(!jobQueue.isEmpty())
{
final AsyncContext aCtx = jobQueue.poll();
executor.execute(new Runnable(){
public void run() {
ServletRequest request = aCtx.getRequest();
// get parameteres
// invoke a Web service endpoint
// set results
aCtx.forward("/result.jsp");
}
});
}
}
}
public void contextDestroyed(ServletContextEvent sce) {
}
}
在这段代码,当asyncSupported 属性被设置为true时,response对象还没有被赋给exit方法。调用startAsync方法返回一个AsyncContext对象,它用来缓存request和response对象,该AsyncContext对象被存放在一个application域队列里,紧接着,doGet方法执行并返回,原始的request线程被回收,在ServletContextListener对象,在application开始监视上述队列并随时回收request处理过程,在request对象被处理后,你有选择调用ServletResponse.getWriter.print()方法然后调用complete方法来提交Response,或者是调用forward方法来导向一个jsp来显示结果,注意这些jsp页面是带有被设置为false的asyncSupported属性的servlets。
另外,在servlet3.0中的AsyncEvent和AsyncListener类可以给开发者对于异步生命周期事件精心的控制,你可以通过ServletRequest.addAsyncListener方法来注册一个AsyncListener类,在startAsync方法被调用后,当异步处理结束或者过期后,一个AsyncEvent被立即发送给上述已注册的AsyncListener类。AsyncEvent也像AsyncContext对象一样包含一个request和response对象。
服务器端推技术
servlet3.0异步特征的一个更重要,更有趣的应用案例就是服务器端推技术。Gtalk(一种可以使Gmail用户在线聊天的小工具)就是使用服务器端推技术的案例,Gtalk不经常访问服务器去核对是否有可以显示的新信息。相反,它等待服务器向回弹推新信息,这种方式有两种显著的优点:不用频繁发送request的低滞后通信,不浪费服务器资源和网络带宽。
ajax技术允许用户可以在处理很多请求的同时通过页面与服务器进行交互,一个普遍的用途就是用ajax来访问服务器来获得更新的状态而不打断用户操作,但是高频率访问会浪费服务器资源和网络带宽,如果服务器可以动态向浏览器弹推数据,换句话说,通过事件向客户端传送异步信息(状态改变)。ajax application将会表现的更优雅并且节省珍贵的服务器资源和网络带宽。
http协议是一个请求回复的协议,一个客户端发送请求信息到服务器端,服务器通过一个回复信息回复客户端。服务器不能初始化客户端或者发送一个非期望的信息给客户端。http协议的这个方面使得服务器弹推信息看起来不可能,但是很多有创意的技术可以绕开这个限制。
service streaming技术,允许一个服务器通过事件触发而不是从客户端发送过来的请求来向客户端发送消息,实际的实现方法是:客户端通过一个请求来初始化一个连接,回复信息会在服务器端事件发生的时候被一点一点地返回,理论上,回复信息永久持续,这些信息片可以被客户端浏览器接收,解析并显示在浏览器上。
long polling技术,也叫异步访问,是一种纯server push and client pull技术,这种技术基于Bayeux协议,像service streaming 一样,客户端通过请求与服务器建立一个连接,服务器保持该请求直到一个事件的发生,一旦事件发生,一个完整的回复信息被返回给客户端。一接收到回复信息,客户端立即发送一个新的信息,这样服务器端几乎一直持有一个通过服务器端事件触发回复消息的请求。long polling技术比service streaming 更容易在浏览器上实现。
passive piggyback技术,当服务器有更新信息需要发送给客户端时,它等待来自客户端的下一个请求,一旦接收到下一个请求,它会把该更新信息连同客户端期望的回复信息一并发送给客户端。
service streaming和long polling 技术通过ajax实现,以Comet 或者reverse ajax最为著名。
ajax改善了单用户响应性,像comet一样的服务器端推技术使得应用程序响应更具有协作性,多用户形式,而且不占用正常访问的消耗。
客户端方的技术,如:隐藏的iframe,xmlhttprequest,dojo,jquery类库不在本文讨论范围内,我们要探讨的是servlet3.0怎样帮助实现服务器端推技术。
有效线程利用:一个更广泛的解决方案。
comet一直占用一个等待在服务器端的管道返回状态更新信息,这个管道不可以再被客户端用来发送原始请求,所以comet实际上是在一个客户身上建立了两个连接,如果应用程序保持在基于请求的线程方式,每个连接都会被关联一个特殊线程,这就导致了线程数大于实际用户数。应用基于请求线程的comet应用只能在有限的线程消耗范围内扩展。