Tomcat 7 Асинхронная обработка - PullRequest
12 голосов
/ 02 сентября 2011

Мне интересно, как Tomcat 7 реализует асинхронную обработку.Я понимаю, что поток запросов немедленно возвращается, что позволяет потоку запросов немедленно прослушивать новый запрос и отвечать на него.

Как обрабатывается запрос 'async'?Существует ли отдельный пул потоков, который обрабатывает асинхронные запросы?Я предполагаю, что блокировка ввода-вывода обрабатывается с использованием чего-то вроде java.nio.Selector для производительности.А как насчет потоков, которые блокируют вычисления процессора?

1 Ответ

40 голосов
/ 03 сентября 2011

Вы смешиваете разные понятия. Вы должны различать:

  1. Асинхронная обработка запросов согласно сервлету 3.0 ; API, который позволяет отделить входящий запрос сервлета от пула потоков веб-контейнера. Он не создает никаких потоков на лету. Пользователь интерфейса реализует правильное многопоточное решение. Это не относится к неблокирующему IO.
  2. Пул потоков ; предоставляет механизм для получения и управления потоками в веб-контейнере. Когда дело доходит до асинхронной обработки запроса , у вас есть 2 варианта. Вы можете определить свой собственный ExecutorService и использовать его для дальнейшей обработки запроса, или вы можете создать новый Runnable и отправить его полученному AsyncContext, позвонив по номеру AsyncContext.start()* 1019. *. В случае Tomcat последний подход использует пул потоков Tomcat, определенный в server.xml.
  3. Неблокирующая IO (NIO) ; Хотя это асинхронный , но это уже другая история. Это относится к неблокирующим операциям ввода-вывода, таким как дисковый или сетевой ввод-вывод. Если вы хотите включить NIO для обработки HTTP-запросов, посмотрите документацию 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 сразу заканчиваются, тогда как рабочий все еще работает. 2 запроса на тестирование: http://localhost:8080/pc/TestServlet?name=chrome и http://localhost:8080/pc/TestServlet?name=firefox.

Простой пример сервлета

@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);
    }
}

... и 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);
    }
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...