使用异步 Servlet 处理挂起线程
页面: 1, 2, 3

Future Servlet to the Rescue

WebLogic Server 6.1中已经有了未来servlet的身影,不过此功能一直都是“隐藏”在WebLogic Server中的。到WebLogic Server 9.1发行时,BEA才最终公开了这一模型。因此,我们将了解一下这个未来servlet模型究竟有何帮助。要使用该servlet,需要实现 weblogic.servlet.FutureResponseServlet 类。

我们不再需要实现doRequest()、doResponse()和doTimeout()方法,惟一要做的就是定义一个处理servlet请求的服务:

 public void  
                         service(HttpServletRequest req, FutureServletResponse rsp)
                      

如果决定提供即时应答,只需要对FutureServletResponse实例调用send方法。但是,如果希望在以后延缓响应,则需要提供一个 TimerTask 类的扩展。下面给出了示例代码:

     
Timer timer = new Timer();     
ScheduledTask mt = new ScheduledTask(rsp, timer);
myTimer.schedule(mt, 100);
 
......
 
public class ScheduledTask extends TimerTask {   
  private FutureServletResponse rsp;   
  Timer timer;                                          
  
  ScheduledTask(FutureServletResponse rsp, Timer timer){      
    this.rsp = rsp;      
    this.timer = timer;    
  }    
  
  public void run() {
   try {        
      PrintWriter out = rsp.getWriter();        
      out.println("This is a delayed answer");        
      rsp.send();        
      timer.cancel();      
   } catch(IOException e) {
      e.printStackTrace();      
   }    
  }

但是,如何才能在实际用例中利用这个类呢?我们再次回到Ajax范型。当新请求传入时,如果遵循传统模型,则应该向客户机提供一个即时响应并继续等待新请求。但是此处就是关键:如果将客户机请求存入缓冲区,并立即回复客户机,那么将在不同的线程中异步处理请求,且仅当后台进程终止时才发送响应。

换句话说,我们不再需要客户机-服务器轮询,而只处理服务器请求并在处理终止时通知客户机。解除请求与响应之间的耦合可以防止执行过于频繁,从而使应用程序更易于伸缩。还可以用于避免被Ajax请求淹没

但是,客户如何才能知道服务器-客户机何时终止呢?当我们处理事件驱动型编程时,需要使用观察者模式(Observer Pattern),如图4所示。

如果对于此模式还不太熟悉,我们给出了其简短描述。该模式需要依赖三个角色:Subject、Observer和Concrete Observer。

Observer pattern 

图4. 观察者模式

  • Subject将提供一个接口,用于附加和分离Observer。
  • Observer将为所有定义一个更新接口,用于从Subject处接收更新通知。
  • Concrete Observer将维护与Subject的一个引用,这样便可在接收到通知时接收Subject的状态。最终将执行它所包含的函数。

以下是该方法的应用示例:

public class FutureServlet extends FutureResponseServlet {

 long time = 100;
 public void service(HttpServletRequest req, FutureServletResponse rsp)    
                                       throws IOException, ServletException {
   PrintWriter pw=null;
   try {
     pw = rsp.getWriter();
   } catch (IOException e) {
     e.printStackTrace();
   }
   pw.println("Request arrived");
   pw.flush();
   new Subject(req,rsp);
 }  
}

class Subject extends Observable {
 private static Stack <FutureServletResponse> 
                 stackResponses = new Stack <FutureServletResponse>();

 public Subject (HttpServletRequest req, FutureServletResponse rsp){

  PrintWriter pw=null;
  try {
    pw = rsp.getWriter();
  } catch (IOException e) {
    e.printStackTrace();
  }
  pw.println("Request sent to server");
  pw.flush();

  addObserver(new ConcreteObserver());
  stackResponses.push(rsp);
  startListening();
 }

 public void startListening() {
  Thread t = new Thread() {
   public void run() {
    while (true) {
     try {
      Thread.sleep(1000);
     } catch (InterruptedException e) {
      e.printStackTrace();
     }
     if (stackResponses.isEmpty()) {
      continue;
     }
     FutureServletResponse rsp =  stackResponses.pop(); 

     setChanged();
                    // Notify event to Observer attached 
     notifyObservers(rsp); 
    }
   }
  };
  t.start();
 }

 class ConcreteObserver implements Observer {
  public ConcreteObserver() {}

  public void update(Observable o, Object arg) {
   FutureServletResponse rsp = (FutureServletResponse) arg; 
   PrintWriter pw=null;
   try {
     pw = rsp.getWriter();
     pw.println("Response sent at :" + new Date() + "
                        

 ");
     pw.flush();
     rsp.send(); 
     pw.close();
   } catch (IOException e) {
    e.printStackTrace();
   }
  }
 }
}
                      

如您所见,当接收到新请求时,请求和响应将存储在一个栈中。然后,Subject类将注册为ConcreteObserver类的Observer,用于保存需要在发生通知时执行业务函数。

通过这种简单而有效的方法,我们可以使Ajax应用程序更具伸缩性,从而降低执行线程的影响。该线程现在已经可以同时处理较多的请求了。

据BEA称,“此模型可为响应的处理方式提供全面的控制,并且可在线程处理方面提供更多的控制。但是,使用该类来避免挂起线程需要开发人员自己提供大部分代码。”他们建议在大多数情况下使用Abstract Asynchronous Servlet。

结束语

在理想的测试条件下,并不会出现网络超时并且处理超时的工作几乎可以忽略不计。但是,当到达服务的请求开始增加时,设计较差的Web应用程序可能会增加服务的负担并最终造成网络客户无限制阻塞。

考虑到这些原因,防止挂起线程的扩散是非常有必要的。Abstract Asynchronous Servlet可以解除响应与传入请求和超时无响应请求之间的耦合。

但是,如果需要将响应调整到未来的特定时间,并且需要全面控制线程处理,那么可以采用Future Response Servlet接口。

参考资料

Francesco Marchioni 于1997年加入Java社区,是一名经过认证的Sun企业架构师。他是Pride SpA的雇员,为BEA客户设计和开发了许多在Weblogic Platform上使用的J2EE应用程序。