Как Jetty и другие контейнеры используют NIO, придерживаясь спецификации Servlet?

Я новичок в NIO, и я пытаюсь понять, как Jetty использует NIO.

Мое понимание того, как традиционные контейнеры сервлетов, использующие службу блокировки IO, имеют следующий вид:

  • Прибывает запрос
  • Для обработки запроса выделяется поток, и вызывается метод сервлета (doGet и т.д.)
  • Метод сервлета передается InputStream и OutputStream
  • Метод сервлета читается из InputStream и записывается в OutputStream
  • InputStream и OutputStream в основном связаны с соответствующими потоками базового Socket

Что происходит при использовании разъема NIO? Я предполагаю следующее:

  • Прибывает запрос
  • Jetty использует NIO-коннектор и асинхронно буферизирует весь запрос
  • После того, как запрос был прочитан, полностью оберните буфер в InputStream
  • Создайте пустой буфер ответа (завернутый в OutputStream)
  • Выделите поток и вызовите метод сервлета (doGet и т.д.), передав указанные выше потоки обертки.
  • Метод сервлета записывает в завершенный (буферизованный) поток ответов и возвращается из метода сервлета
  • Jetty использует NIO для записи содержимого буфера ответа в базовый SocketChannel

В документации Jetty я нашел следующее:

SelectChannelConnector - Этот коннектор использует эффективные буферы NIO с неблокирующей моделью потоков. Jetty использует Direct NIO-буферы и выделяет потоки только для соединений с запросами. Синхронизация имитирует блокировку для API сервлета, а любой незапланированный контент в конце обработки запроса обрабатывается асинхронно.

Я не уверен, что понимаю, что означает Synchronization simulates blocking for the servlet API?

Ответы

Ответ 1

У вас его нет точно. Когда причал использует разъем NIO (и только 9 поддерживает NIO), он работает следующим образом:

  • Состояние ожидания как несколько потоков (1-4 в зависимости от # ядра), вызывающего селектор, ища активность IO. Это было увеличено до более чем 1 000 000 подключений на Jetty.
  • Когда селектор видит активность IO, он вызывает метод соединения в соединении, который:

    • что-то еще зарегистрировалось, что он заблокирован, ожидая ввода-вывода для этого соединения, поэтому в этом случае селектор просто просыпается, кто бы ни был заблокирован.
    • иначе поток отправляется для обработки соединения.
  • Если поток отправлен, он попытается прочитать соединение и проанализировать его. Теперь все зависит от того, является ли соединение http, spdy, http2 или websocket.

    • для http, если заголовки запросов завершены, поток продолжает вызывать обработку запроса (в конечном итоге это попадает на сервлет), не дожидаясь содержимого.
    • для http2/spdy требуется другая отправка, но см. обсуждение стратегии Eat-What-You-Kill в списке: http://dev.eclipse.org/mhonarc/lists/jetty-dev/msg02166.html
    • для websocket вызывается обработка сообщений.
  • Как только поток отправляется сервлету, он выглядит так, как блокирует IO сервлета, но под уровнем HttpInputStream и HttpOutputStream все IO асинхронны с обратными вызовами. API блокировки использует специальный блокирующий обратный вызов для достижения блокировки. Это означает, что если сервлет выбирает использовать async IO, то он просто обходит блокирующий обратный вызов и использует асинхронный API более или менее напрямую.

  • Сервлет может приостанавливаться с использованием request.startAsync, и в этом случае отправленный поток возвращается в пул потоков, но связанное соединение не помечено как заинтересованное в IO. Async IO может выполняться, но для события AsyncContext необходимо либо перераспределить поток, либо повторно зарегистрировать соединение для активности IO после завершения цикла асинхронизации.

Этот вид немного усложняется http2 и spdy, которые мультиплексированы, поэтому они могут включать дополнительную отправку.

Любая инфраструктура HTTP, которая не отправляет, может очень быстро работать в тестовом коде, но когда сталкивается с реальным приложением, которое может делать глупые вещи, такие как блок в базах данных, файловая система, службы REST и т.д.... тогда отсутствие отправки просто означает, что одно соединение может поддерживать все другие соединения в системе.

Для получения дополнительной информации о том, как причал обрабатывает асинхронную и диспетчерскую операции, см.: