Tomcat 7 异步处理

15

我想知道Tomcat 7如何实现异步处理。我知道请求线程立即返回,允许它立即监听新请求并对其做出响应。

'异步'请求是如何处理的?是否有一个独立的线程池来处理异步请求?我认为阻塞IO使用类似于java.nio.Selector的东西进行处理以获得更好的性能。那么对于在CPU计算上阻塞的线程呢?


如果你想在Tomcat 7上实现异步处理,你可能需要看一下这个链接:https://dev59.com/-msz5IYBdhLWcg3wpZny。但有一个小细节需要注意。 - Jérôme Verstrynge
1个回答

43
您混淆了不同的概念。您必须区分以下内容:
  1. Servlet 3.0中的异步请求处理:这是一种API,使您可以将传入的servlet请求与Web容器线程池解耦。它不会即时创建任何线程。用户需要实现适当的多线程解决方案。它与非阻塞IO无关。
  2. 线程池:提供一种机制来获取和管理Web容器中的线程。在涉及异步请求处理时,有两个选项。您可以定义自己的ExecutorService并使用它进一步处理请求,或者您可以创建一个新的Runnable并通过调用AsyncContext.start()将其提交给获得的AsyncContext。在Tomcat的情况下,后一种方法使用Tomcat在server.xml中定义的线程池。
  3. 非阻塞IO(NIO):虽然它也是异步的,但它是另一回事。它涉及非阻塞IO操作,如磁盘或网络IO。如果要为HTTP请求处理启用NIO,请查看Tomcat的文档

下面的示例概述了它可以的工作方式。它仅使用一个线程进行工作。如果您从两个不同的浏览器并行运行它,则输出如下所示(我使用自定义记录器):
   DATE                         THREAD_ID  LEVEL      MESSAGE
2011-09-03 11:51:22.198 +0200      26        I:     >doGet: chrome
2011-09-03 11:51:22.204 +0200      26        I:     <doGet: chrome
2011-09-03 11:51:22.204 +0200      28        I:     >run: chrome
2011-09-03 11:51:27.908 +0200      29        I:     >doGet: firefox
2011-09-03 11:51:27.908 +0200      29        I:     <doGet: firefox
2011-09-03 11:51:32.227 +0200      28        I:     <run: chrome
2011-09-03 11:51:32.228 +0200      28        I:     >run: firefox
2011-09-03 11:51:42.244 +0200      28        I:     <run: firefox

你会发现doGet方法立即完成,而worker仍在运行。这2个测试请求:http://localhost:8080/pc/TestServlet?name=chromehttp://localhost:8080/pc/TestServlet?name=firefox
简单的Servlet示例。
@WebServlet(asyncSupported = true, value = "/TestServlet", loadOnStartup = 1)
public class TestServlet extends HttpServlet {
    private static final Logger LOG = Logger.getLogger(TestServlet.class.getName());
    private static final long serialVersionUID = 1L;
    private static final int NUM_WORKER_THREADS = 1;

    private ExecutorService executor = null;

    @Override
    public void init() throws ServletException {
        this.executor = Executors.newFixedThreadPool(NUM_WORKER_THREADS);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {

        final String name = request.getParameter("name");
        LOG.info(">doGet: " + name);

        AsyncContext ac = request.startAsync(); // obtain async context
        ac.setTimeout(0); // test only, no timeout

        /* Create a worker */
        Runnable worker = new TestWorker(name, ac);

        /* use your own executor service to execute a worker thread (TestWorker) */
        this.executorService.execute(worker);

        /* OR delegate to the container */
        // ac.start(worker);

        LOG.info("<doGet: " + name);
    }
}

...and the TestWorker

public class TestWorker implements Runnable {
    private static final Logger LOG = Logger.getLogger(TestWorker.class.getName());
    private final String name;
    private final AsyncContext context;
    private final Date queued;

    public TestWorker(String name, AsyncContext context) {
        this.name = name;
        this.context = context;
        this.queued = new Date(System.currentTimeMillis());
    }

    @Override
    public void run() {

        LOG.info(">run: " + name);

        /* do some work for 10 sec */
        for (int i = 0; i < 100; i++) {
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }

        ServletResponse response = this.context.getResponse();
        response.setContentType("text/plain");

        try {
            PrintWriter out = response.getWriter();
            out.println("Name:\t\t" + this.name);
            out.println("Queued:\t\t" + this.queued);
            out.println("End:\t\t" + new Date(System.currentTimeMillis()));
            out.println("Thread:\t\t" + Thread.currentThread().getId());
            out.flush();
        } catch (IOException e) {
            throw new RuntimeException(e);
        }

        this.context.complete();

        LOG.info("<run: " + name);
    }
}

感谢澄清。关于CometProcessor API,是否有一个线程在轮询所有当前连接,以查看哪个连接“有可读数据”?具体而言,当有10个请求与100个请求时,预期的线程数量将是多少?假设这些请求已收到BEGIN事件,但尚未收到READ事件。http://tomcat.apache.org/tomcat-7.0-doc/aio.html - John Cheng
@John 如果你对Home的回答满意,应该批准它。你的额外问题应该成为一个新问题(最终,你可以参考这个问题)。 - Jérôme Verstrynge
嗨@JVerstry,说实话,我对这个答案还不太满意。我仍然觉得自己对Tomcat的“异步处理”架构没有很好的理解。 - John Cheng
@John:如果你想完全理解异步处理,你必须进行一些研究。这个相当复杂的概念不能仅通过一个SO答案来描述。 - home
ExecutorService也可以在Tomcat 5.5中使用!太好了!感谢您的帖子! - surfealokesea

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接