Подтвердить что ты не робот

Tomcat 7 Async Processing

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

Как обрабатывается запрос async? Есть ли отдельный пул потоков, который обрабатывает асинхронные запросы? Я предполагаю, что блокирование ввода-вывода обрабатывается с использованием чего-то вроде java.nio.Selector для производительности. Что относительно потоков, которые блокируют вычисления ЦП?

4b9b3361

Ответ 1

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

  • Асинхронная обработка запросов в соответствии с Servlet 3.0; API, который позволяет отделить входящий запрос сервлета от пула потоков веб-контейнера. Он не создает потоки на лету. Он предоставляет пользователю интерфейс для реализации правильного многопоточного решения. Это не относится к неблокирующему IO.
  • Пул потоков; предоставляет механизм для получения и управления потоками в веб-контейнере. Когда дело доходит до асинхронного запроса, у вас есть 2 варианта. Вы можете определить свой собственный ExecutorService и использовать его для дальнейшей обработки запроса, или вы можете создать новый Runnable и отправить его на полученный AsyncContext, вызывая AsyncContext.start(). В случае Tomcat в последнем подходе используется пул потоков Tomcat, определенный в server.xml.
  • Неблокирующий 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);
    }
}