该篇文章翻译自:http://developerlife.com/tutorials/?p=1437

  一、简介

  Servlet API 3.0 之前,需要使用类似Comet的方式来实现创建异步的Servlet。然而,Tomcat7 与 Servlet API 3.0 支持同步与异步方式。在同步Servlet中,一个处理客户端HTTP请求的线程将在整个请求的过程中被占用。对于运时较长的任务,服务器主要在等待一个应答,这导致了线程的饥渴,并且负载加重。这是由于即使服务器只是等待,服务端的线程还是被请求占光。

  异步Servlet,使用在其他线程中执行耗时(等待)的操作,而允许Tomcat的线程返回线程池的方式来解决问题。当任务完成并且结果就绪,Servlet容器需要得到通知,然后另外一个线程将被分配用于处理将结果返回给客户端。客户端完全感受不到服务器的差别,并且不需要任何改变。为了允许异步魔法,新的Servlet需要使用一种回调机制(有App Server提供),告知app server结果就绪。另外,需要告知Servlet容器(app server)何时可以释放当前正在处理的请求(随后该任务将在后台的线程中得到真正的处理)。

  二、简要的伪代码

  1、客户端请求通过HTTP请求,然后被分配到某个Servlet。

  2、Servlet.service方法在Servlet容器的某个线程中执行。

  3、Servlet.service方法创建一个AsyncContext对象(使用sartAsync())。

  4、Servlet.service方法后将创建AsyncContext对象传递给另外的线程执行。
  5、Servlet.service方法随后返回并结束执行。

  然后,客户端照样请求服务器,并且之前的那个连接被挂起等待,直到某时触发了如下事件:

  1、后台的线程处理完AsyncContext任务,并且结果已经就绪,将通知AsyncContext处理已经完成。它将向HttpResponse中写入返回的数据,并且调用AsyncContext的Complete方法。这将通知Servlet容器将结果返回给客户端。

  三、异常情况

  假如某些异常在后台线程处理期间发生,客户端应用将得到某些网络异常。然而,若后台线程处理任务时有错误,有一种方式处理这种情况。当创建AsyncContext时,可以指定两件事情:

  1、设置后台线程处理得到结果的最大时间,超过时产生一个超时异常。

  2、可以在超时事件上设置监听器来处理这种情况。

  因此,假如在后台线程出现了某些错误,经过一个你指定的合适时间后,监听器将被触发并且告知超时条件被触发。超时处理函数将被执行,可以向客户端发送一些错误信息。若后台线程在这之后试图向HttpResponse写入数据,将会触发一个异常。之后就需要将之前执行过程中产生的结果丢弃。

  四、简单实现

  将提供两组异步Servlet,一个简单实现一个一个复杂的。简单实现只是介绍异步Servlet的理念以及部分web.xml。另一个将展示耗时操作超时与触发异常,以便看到如何处理他们。

  1. @javax.servlet.annotation.WebServlet(
  2. // servlet name
  3. name = "simple",
  4. // servlet url pattern
  5. value = {"/simple"},
  6. // async support needed
  7. asyncSupported = true
  8. )
  9. public class SimpleAsyncServlet extends HttpServlet {
  10.  
  11. /**
  12. * Simply spawn a new thread (from the app server's pool) for every new async request.
  13. * Will consume a lot more threads for many concurrent requests.
  14. */
  15. public void service(ServletRequest req, final ServletResponse res)
  16. throws ServletException, IOException {
  17.  
  18. // create the async context, otherwise getAsyncContext() will be null
  19. final AsyncContext ctx = req.startAsync();
  20.  
  21. // set the timeout
  22. ctx.setTimeout(30000);
  23.  
  24. // attach listener to respond to lifecycle events of this AsyncContext
  25. ctx.addListener(new AsyncListener() {
  26. public void onComplete(AsyncEvent event) throws IOException {
  27. log("onComplete called");
  28. }
  29. public void onTimeout(AsyncEvent event) throws IOException {
  30. log("onTimeout called");
  31. }
  32. public void onError(AsyncEvent event) throws IOException {
  33. log("onError called");
  34. }
  35. public void onStartAsync(AsyncEvent event) throws IOException {
  36. log("onStartAsync called");
  37. }
  38. });
  39.  
  40. // spawn some task in a background thread
  41. ctx.start(new Runnable() {
  42. public void run() {
  43.  
  44. try {
  45. ctx.getResponse().getWriter().write(
  46. MessageFormat.format("<h1>Processing task in bgt_id:[{0}]</h1>",
  47. Thread.currentThread().getId()));
  48. }
  49. catch (IOException e) {
  50. log("Problem processing task", e);
  51. }
  52.  
  53. ctx.complete();
  54. }
  55. });
  56.  
  57. }
  58.  
  59. }

服务端简单示例

  代码说明:

  1、可以直接命名Servlet并且提供一个特殊的url模式,以避免弄乱web.xml条目。

  2、需要传递asyncSupported=true告知app server这个servlet需要采用异步模式。

  3、在service方法中,超时时间设置为30秒,因此只要后台线程执行时间小于30秒就不会触发超时错误。

  4、runnbale对象实际上传递给了app server在另外一个线程中执行。

  5、AsyncContext监听器没有执行有价值的操作,只是打印一行日志。

  1. public class LoadTester {
  2.  
  3. public static final AtomicInteger counter = new AtomicInteger(0);
  4. public static final int maxThreadCount = 100;
  5.  
  6. public static void main(String[] args) throws InterruptedException {
  7.  
  8. new LoadTester();
  9.  
  10. }
  11.  
  12. public LoadTester() throws InterruptedException {
  13.  
  14. // call simple servlet
  15.  
  16. ExecutorService exec1 = Executors.newCachedThreadPool();
  17.  
  18. for (int i = 0; i < maxThreadCount; i++) {
  19.  
  20. exec1.submit(new UrlReaderTask("http://localhost:8080/test/simple"));
  21.  
  22. }
  23.  
  24. exec1.shutdown();
  25.  
  26. Thread.currentThread().sleep(5000);
  27.  
  28. System.out.println("....NEXT....");
  29.  
  30. // call complex servlet
  31.  
  32. counter.set(0);
  33.  
  34. ExecutorService exec2 = Executors.newCachedThreadPool();
  35.  
  36. for (int i = 0; i < maxThreadCount; i++) {
  37.  
  38. exec2.submit(new UrlReaderTask("http://localhost:8080/test/complex"));
  39.  
  40. }
  41.  
  42. exec2.awaitTermination(1, TimeUnit.DAYS);
  43.  
  44. }
  45.  
  46. public class UrlReaderTask implements Runnable {
  47.  
  48. private String endpoint;
  49. public UrlReaderTask(String s) {
  50. endpoint = s;
  51. }
  52. public void run() {
  53.  
  54. try {
  55. actuallyrun();
  56. }
  57. catch (Exception e) {
  58. System.err.println(e.toString());
  59. }
  60.  
  61. }
  62.  
  63. public void actuallyrun() throws Exception {
  64.  
  65. int count = counter.addAndGet(1);
  66.  
  67. BufferedReader in = new BufferedReader(
  68. new InputStreamReader(
  69. new URL(endpoint).openStream()));
  70.  
  71. String inputLine;
  72.  
  73. while ((inputLine = in.readLine()) != null) {
  74. System.out.println(MessageFormat.format("thread[{0}] : {1} : {2}",
  75. count, inputLine, endpoint));
  76. }
  77.  
  78. in.close();
  79.  
  80. }
  81.  
  82. }
  83.  
  84. }//end class ComplexLoadTester

客户端代码示例

  代码说明:

  1、这个简单的控台app只是产生100个线程并且同时执行GET请求,到简单与复杂的异步Servlet。

  2、简单异步Servlet运行没问题,并且返回所有的回复。需要注意app server中的线程ID将会有很大的差异,这些线程来自由tomcat 7管理的线程池。另外在下面的复杂示例中你将看到比当前示例少得多的线程id。

  五、复杂实现

  在这个复杂实现中,有如下主要的改变,

  1、这个Servlet管理自己固定大小的线程池,大小通过初试参数设置。这里设置成3。

  2、头四个请求都在处理过程中发生异常,只是为了说明在service函数中未处理的异常发生时的情况。

  3、耗时任务将被执行一个最大值为5秒的随机值,给AsyncContext设置的超时未60秒。这个结果在客户端请求的后20个将导致超时,因为只有3个服务器线程处理所有的100个并发请求。(100个并发的请求只有3个线程处理,每个任务1~5秒,排在后面的任务会在60秒之后才会得到执行)。

  4、当tomcat7检测到超时,并且监听器被触发后,监听器需要调用AsyncContext.complete()函数。

  5、一旦超时条件触发,tomcat7将会使AsyncContext包含的HttpRequest、HttpResponse对象无效。这是给耗时任务的一个信号,AsyncContext已经无效。这是为什么需要在往HttpResponse写入数据时检查Http对象是否为null。当一个超时发生时,耗时任务将不知道,并且必须检查request、response是否为null。如果为null,意味着需要停止处理,因为应答消息可以已经通过监听器或者tomcat7回复。

  

  1. @javax.servlet.annotation.WebServlet(
  2. // servlet name
  3. name = "complex",
  4. // servlet url pattern
  5. value = {"/complex"},
  6. // async support needed
  7. asyncSupported = true,
  8. // servlet init params
  9. initParams = {
  10. @WebInitParam(name = "threadpoolsize", value = "3")
  11. }
  12. )
  13. public class ComplexAsyncServlet extends HttpServlet {
  14.  
  15. public static final AtomicInteger counter = new AtomicInteger(0);
  16. public static final int CALLBACK_TIMEOUT = 60000;
  17. public static final int MAX_SIMULATED_TASK_LENGTH_MS = 5000;
  18.  
  19. /** executor svc */
  20. private ExecutorService exec;
  21.  
  22. /** create the executor */
  23. public void init() throws ServletException {
  24.  
  25. int size = Integer.parseInt(
  26. getInitParameter("threadpoolsize"));
  27. exec = Executors.newFixedThreadPool(size);
  28.  
  29. }
  30.  
  31. /** destroy the executor */
  32. public void destroy() {
  33.  
  34. exec.shutdown();
  35.  
  36. }
  37.  
  38. /**
  39. * Spawn the task on the provided {@link #exec} object.
  40. * This limits the max number of threads in the
  41. * pool that can be spawned and puts a ceiling on
  42. * the max number of threads that can be used to
  43. * the init param "threadpoolsize".
  44. */
  45. public void service(final ServletRequest req, final ServletResponse res)
  46. throws ServletException, IOException {
  47.  
  48. // create the async context, otherwise getAsyncContext() will be null
  49. final AsyncContext ctx = req.startAsync();
  50.  
  51. // set the timeout
  52. ctx.setTimeout(CALLBACK_TIMEOUT);
  53.  
  54. // attach listener to respond to lifecycle events of this AsyncContext
  55. ctx.addListener(new AsyncListener() {
  56. /** complete() has already been called on the async context, nothing to do */
  57. public void onComplete(AsyncEvent event) throws IOException { }
  58. /** timeout has occured in async task... handle it */
  59. public void onTimeout(AsyncEvent event) throws IOException {
  60. log("onTimeout called");
  61. log(event.toString());
  62. ctx.getResponse().getWriter().write("TIMEOUT");
  63. ctx.complete();
  64. }
  65. /** THIS NEVER GETS CALLED - error has occured in async task... handle it */
  66. public void onError(AsyncEvent event) throws IOException {
  67. log("onError called");
  68. log(event.toString());
  69. ctx.getResponse().getWriter().write("ERROR");
  70. ctx.complete();
  71. }
  72. /** async context has started, nothing to do */
  73. public void onStartAsync(AsyncEvent event) throws IOException { }
  74. });
  75.  
  76. // simulate error - this does not cause onError - causes network error on client side
  77. if (counter.addAndGet(1) < 5) {
  78. throw new IndexOutOfBoundsException("Simulated error");
  79. }
  80. else {
  81. // spawn some task to be run in executor
  82. enqueLongRunningTask(ctx);
  83. }
  84.  
  85. }
  86.  
  87. /**
  88. * if something goes wrong in the task, it simply causes timeout condition that causes
  89. * the async context listener to be invoked (after the fact)
  90. * <p/>
  91. * if the {@link AsyncContext#getResponse()} is null, that means this context has
  92. * already timedout (and context listener has been invoked).
  93. */
  94. private void enqueLongRunningTask(final AsyncContext ctx) {
  95.  
  96. exec.execute(new Runnable() {
  97. public void run() {
  98.  
  99. try {
  100.  
  101. // simulate random delay
  102. int delay = new Random().nextInt(MAX_SIMULATED_TASK_LENGTH_MS);
  103. Thread.currentThread().sleep(delay);
  104.  
  105. // response is null if the context has already timedout
  106. // (at this point the app server has called the listener already)
  107. ServletResponse response = ctx.getResponse();
  108. if (response != null) {
  109. response.getWriter().write(
  110. MessageFormat.format("<h1>Processing task in bgt_id:[{0}], delay:{1}</h1>",
  111. Thread.currentThread().getId(), delay)
  112. );
  113. ctx.complete();
  114. }
  115. else {
  116. throw new IllegalStateException("Response object from context is null!");
  117. }
  118. }
  119. catch (Exception e) {
  120. log("Problem processing task", e);
  121. e.printStackTrace();
  122. }
  123.  
  124. }
  125. });
  126. }
  127.  
  128. }

复杂的服务器示例

  六、异步还是同步

  综上所述,API使用很直观,假设从一开始你就熟悉异步处理。然而假如你不熟悉异步处理,这种callback的方式会带来困惑与恐惧。另外Tomcat7与Servlet API 3.0更加容易配置servlet,在这个教程中都没有涉及,如符合语法规则的加载Servlet。

 

使用tomcat7创建异步servlet的更多相关文章

  1. Servlet 3特性:异步Servlet

    解异步Servlet之前,让我们试着理解为什么需要它.假设我们有一个Servlet需要很多的时间来处理,类似下面的内容: LongRunningServlet.java package com.jou ...

  2. 关于servlet3.0中的异步servlet

    刚看了一下维基百科上的介绍,servlet3.0是2009年随着JavaEE6.0发布的: 到现在已经有六七年的时间了,在我第一次接触java的时候(2011年),servlet3.0就已经出现很久了 ...

  3. WebFlux01 webflux概念、异步servlet、WebFlux意义

    1 概念 待更新...... 2 异步servlet 2.1 同步servlet servlet容器(如tomcat)里面,每处理一个请求会占用一个线程,同步servlet里面,业务代码处理多久,se ...

  4. 异步servlet的原理探究

    异步servlet是servlet3.0开始支持的,对于单次访问来讲,同步的servlet相比异步的servlet在响应时长上并不会带来变化(这也是常见的误区之一),但对于高并发的服务而言异步serv ...

  5. EasyUI创建异步树形菜单和动态添加标签页tab

    创建异步树形菜单 创建树形菜单的ul标签 <ul class="easyui-tree" id="treeMenu"> </ul> 写j ...

  6. Filter 快速开始 异步Servlet 异步请求 AsyncContext 异步线程 异步派发 过滤器拦截

    [web.xml] <filter> <filter-name>normalFilter</filter-name> <filter-class>net ...

  7. 雷林鹏分享:jQuery EasyUI 树形菜单 - 创建异步树形菜单

    jQuery EasyUI 树形菜单 - 创建异步树形菜单 为了创建异步的树形菜单(Tree),每一个树节点必须要有一个 'id' 属性,这个将提交回服务器去检索子节点数据. 创建树形菜单(Tree) ...

  8. 雷林鹏分享:jQuery EasyUI 表单 - 创建异步提交表单

    jQuery EasyUI 表单 - 创建异步提交表单 本教程向您展示如何通过 easyui 提交一个表单(Form).我们创建一个带有 name.email 和 phone 字段的表单.通过使用 e ...

  9. 异步Servlet和异步过虑器

    异步处理功能可以节约容器线程.此功能的作用是释放正在等待完成的线程,是该线程能够被另一请求所使用. 要编写支持异步处理的 Servlet 或者过虑器,需要设置 asyncSupported 属性为 t ...

随机推荐

  1. WPF数据双向绑定

    设置双向绑定,首先控件要绑定的对象要先继承一个接口: INotifyPropertyChanged 然后对应被绑定的属性增加代码如下: 意思就是当Age这个属性变化时,要通知监听它变化的人. 即:Pr ...

  2. prettyprint

    <!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml"> <head> <m ...

  3. Android UI效果实现——Activity滑动退出效果

    更新说明: 1.在QQ网友北京-旭的提醒下,在SlideFrame的initilize方法中添加了focusable.focusableInTouch.clickable的状态设置,否则会导致部分情况 ...

  4. Iframe 自适应高度的方法!

    第一种方法:代码简单,兼容性还可以,大家可以先测试下. function SetWinHeight(obj) { var win=obj; if (document.getElementById) { ...

  5. jquery绑定事件失效的情况(转)

    原文地址:http://www.thinksaas.cn/group/topic/348453/ jQuery中文api地址:http://www.jquery123.com/api/ jQuery官 ...

  6. OpenStack: OVS安装

    > OVS安装:1. Install the Open vSwitch plug-in and its dependencies:# apt-get install \neutron-plugi ...

  7. C++函数传指针和传引用的区别

    从概念上讲.指针从本质上讲就是存放变量地址的一个变量,在逻辑上是独立的,它可以被改变,包括其所指向的地址的改变和其指向的地址中所存放的数据的改变. 而引用是一个别名,它在逻辑上不是独立的,它的存在具有 ...

  8. UIButton之Block回调

    本文主要介绍了两种改写UIButton的点击事件方法——继承UIButton跟给UIButton添加一个分类.附代码 方法一:继承UIButton UIButtonBlock.h文件 如下 #impo ...

  9. SQL-Server数据库学习笔记-表

    1. 表及其属性 表(Table):也称实体,是存储同类型数据的集合. 列(Field):也称字段.域或属性,它构成表的架构,具体表示为一条信息中的一个属性. 行(Row):也称元组(Tuple),存 ...

  10. [转]ubuntu 14.04 系统设置不见了

    [转]ubuntu 14.04 系统设置不见了 http://blog.sina.com.cn/s/blog_6c9d65a10101i0i7.html 不知道删除什么了,系统设置不见了! 我在终端运 ...