设为首页收藏本站

LUPA开源社区

 找回密码
 注册
文章 帖子 博客
LUPA开源社区 首页 IT综合资讯 查看内容

使用异步Servlet改进应用性能

2013-11-18 11:10| 发布者: joejoe0332| 查看: 2824| 评论: 0|原作者: 张龙|来自: InfoQ

摘要:   Nikita Salnikov Tarnovski是plumbr的高级开发者,也是一位应用性能调优的专家,他拥有多年的性能调优经验。近日,Tarnovski撰文谈到了如何通过异步Servlet来改进常见的Java Web应用的性能问题。   众所周知, ...


  下面我们来看看另外一种实现,利用Servlet API 3.0的异步支持:


@WebServlet(asyncSupported = true, value = "/AsyncServlet")
public class AsyncServlet extends HttpServlet {
  private static final long serialVersionUID = 1L;

  protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
    Work.add(request.startAsync());
  }
}
public class Work implements ServletContextListener {
  private static final BlockingQueue queue = new LinkedBlockingQueue();

  private volatile Thread thread;

  public static void add(AsyncContext c) {
    queue.add(c);
  }

  @Override
  public void contextInitialized(ServletContextEvent servletContextEvent) {
    thread = new Thread(new Runnable() {
      @Override
      public void run() {
        while (true) {
          try {
            Thread.sleep(2000);
            AsyncContext context;
            while ((context = queue.poll()) != null) {
              try {
                ServletResponse response = context.getResponse();
                response.setContentType("text/plain");
                PrintWriter out = response.getWriter();
                out.printf("Thread %s completed the task", Thread.currentThread().getName());
                out.flush();
              } catch (Exception e) {
                throw new RuntimeException(e.getMessage(), e);
              } finally {
                context.complete();
              }
            }
          } catch (InterruptedException e) {
            return;
          }
        }
      }
    });
    thread.start();
  }

  @Override
  public void contextDestroyed(ServletContextEvent servletContextEvent) {
    thread.interrupt();
  }
}


  上面的代码看起来有点复杂,因此在开始分析这个解决方案的细节信息之前,我先来概述一下这个方案:速度上提升了75倍,吞吐量提升了20倍。看到这个结果,你肯定迫不及待地想知道这个示例是如何做到的吧。


  这个Servlet本身是非常简单的。需要注意两点,首先是声明Servlet支持异步方法调用:


@WebServlet(asyncSupported = true, value = "/AsyncServlet")


  其次,重要的部分实际上是隐藏在下面这行代码调用中的。

Work.add(request.startAsync());


  整个请求处理都被委托给了Work类。请求上下文是通过AsyncContext实例来保存的,它持有容器提供的请求与响应对象。


  现在来看看第2个,也是更加复杂的类,Work类实现了ServletContextListener接口。进来的请求会在该实现中排队等待通知,通知可能是上面提到的拍卖中的竞标价,或是所有请求都在等待的群组聊天中的下一条消息。


  当通知到达时,我们这里依然是通过Thread.sleep()让线程睡眠2,000ms,队列中所有被阻塞的任务都是由一个工作线程来处理的,该 线程负责编辑与发送响应。相对于阻塞成百上千个线程以等待外部通知,我们通过一种更加简单且干净的方式达成所愿,通过批处理在单独的线程中处理请求。


  还是让结果来说话吧,测试配置与方才的示例一样,依然使用Tomcat 7.0.24的默认配置,测试结果如下所示:

  • 平均响应时间:265ms
  • 最快响应时间:6ms
  • 最慢响应时间:2,058ms
  • 吞吐量:1,965个请求/秒


  虽然说这个示例很简单,不过对于实际项目来说通过这种方式依然能获得类似的结果。


  在将所有的Servlet改写为异步Servlet前,请容许我多说几句。该解决方案非常适合于某些应用场景,比如说群组通知与拍卖价格通知等。不 过,对于等待数据库查询完成的请求来说,这种方式就没有什么必要了。像往常一样,我必须得重申一下——请通过实验进行度量,而不是瞎猜。


  对于那些不适合于这种解决方案的场景来说,我还是要说一下这种方式的好处。除了在吞吐量与延迟方面带来的显而易见的改进外,这种方式还可以在大负载的情况下优雅地避免可能出现的线程饥饿问题。


  另一个重要的方面,这种异步处理请求的方式已经是标准化的了。它不依赖于你所使用的Servlet API 3.0,兼容于各种应用服务器,如Tomcat 7、JBoss 6或是Jetty 8等,在这些服务器上这种方式都可以正常使用。你不必再面对各种不同的Comet实现或是依赖于平台的解决方案了,比如说Weblogic FutureResponseServlet。


  就如本文一开始所提的那样,现在的Java Web项目很少会直接使用Servlet API进行开发了,不过诸多的Web MVC框架都是基于Servlet与JSP标准实现的,那么在你的日常开发中,是否使用过出现多年的Servlet API 3.0,使用了它的哪些特性与API呢?


酷毙

雷人

鲜花

鸡蛋

漂亮
  • 快毕业了,没工作经验,
    找份工作好难啊?
    赶紧去人才芯片公司磨练吧!!

最新评论

关于LUPA|人才芯片工程|人才招聘|LUPA认证|LUPA教育|LUPA开源社区 ( 浙B2-20090187 浙公网安备 33010602006705号   

返回顶部