6 изображений, объясняющих принцип работы Tomcat и процесс запроса

Архитектура

предисловие

Многие вещи очень понятны на диаграмме последовательности, и нет необходимости представлять пошагово.Эта статья посвящена диаграмме, а затем кратко объясняет часть содержания.
  • Инструменты, используемые для рисования графики: PlantUML + Visual Studio Code + PlantUML Extension.
Представление Tomcat в этой статье основано на Tomcat-9.0.0.M22.
Tomcat-9.0.0.M22 — это последняя версия Tomcat, но она еще не выпущена.Он реализует Servlet4.0 и JSP2.3 и предоставляет множество новых функций.Для поддержки требуется JDK 1.8 и выше и т. д. Подробнее , обратитесь к Tomcat-9.0-doc
  • Overview
  • Connector Init and Start
  • Requtst Process
  • Acceptor
  • Poller
  • Worker
  • Container
  • At last

Overview

  1. После запуска коннектора запускается набор потоков для разных этапов обработки запроса.
  2. Группа потока-акцептора. Используется для принятия новых подключений, инкапсуляции новых подключений и выбора опросчика для добавления новых подключений в очередь событий опросчика.
  3. Группа потоков опроса. Он используется для мониторинга событий Socket.Когда Socket доступен для чтения или записи и т. д., Socket инкапсулируется и добавляется в очередь задач пула рабочих потоков.
  4. Группа рабочих потоков. Он используется для обработки запроса, включая анализ сообщения запроса и создание объекта Request, а также вызов конвейера контейнера для обработки.
  • ThreadPoolExecutor, в котором расположены Acceptor, Poller и worker, поддерживаются в NioEndpoint.

Connector Init and Start

  1. initServerSocket() откройте ServerSocket через ServerSocketChannel.open(), который по умолчанию привязан к порту 8080. Длина очереди ожидания соединения по умолчанию составляет 100. Если больше 100, в услуге будет отказано. Мы можем настроить его, настроив свойство acceptCount коннектора в conf/server.xml.
  2. createExecutor() используется для создания пула рабочих потоков. По умолчанию запущено 10 рабочих потоков, при обработке запросов Tomcat максимальное количество рабочих потоков равно 200. Мы можем настроить эти два свойства, настроив minSpareThreads и maxThreads для Connector в conf/server.xml.
  3. Поллор используется для обнаружения готовых сокетов. По умолчанию не более 2, Math.min(2,Runtime.getRuntime(). AvailableProcessors());. Мы можем настроить его, настроив pollerThreadCount.
  4. Acceptor используется для принятия новых подключений. По умолчанию 1. Мы можем настроить его, настроив acceptorThreadCount.

Requtst Process

Acceptor

  1. Acceptor будет блокироваться в методе ServerSocketChannel.accept(); после его запуска.Когда приходит новое соединение, метод возвращает SocketChannel.
  2. После настройки Socket инкапсулируйте Socket в NioChannel и зарегистрируйте его в Poller.Упомянутое значение заключается в том, что мы запускаем несколько потоков Poller в начале.При регистрации соединение справедливо распределяется между каждым Poller. NioEndpoint поддерживает массив Pollers, когда соединение назначается pollers[index], следующее соединение назначается pollers[(index+1)%pollers.length].
  3. Метод addEvent() добавит сокет в очередь PollerEvent этого опросчика. На этом задача Акцептора завершена.

Poller

  1. селектор.выбрать(1000). Когда Poller запускается, поскольку в селекторе нет зарегистрированного канала, он может блокироваться только при выполнении метода. Все опрашивающие устройства имеют общий селектор, класс реализации которого — sun.nio.ch.EPollSelectorImpl.
  2. Метод events() зарегистрирует Socket, добавленный в очередь событий методом addEvent(), в EPollSelectorImpl, и Poller обработает его, когда Socket станет доступным для чтения.
  3. Метод createSocketProcessor() инкапсулирует Socket в SocketProcessor, который реализует интерфейс Runnable. Рабочий поток обрабатывает Socket, вызывая его метод run().
  4. Метод execute(SocketProcessor) отправляет SocketProcessor в пул потоков и помещает его в workQueue пула потоков. workQueue — это экземпляр BlockingQueue. На этом задача Поллера завершена.

Worker

  1. После того, как рабочий поток создан, он выполняет метод runWorker() класса ThreadPoolExecutor, пытаясь получить отложенные задачи из workQueue, но сначала workQueue пуст, поэтому рабочий поток заблокируется в методе workQueue.take().
  2. Когда в workQueue добавляется новая задача, метод workQueue.take() возвращает Runnable, обычно SocketProcessor, а затем рабочий поток вызывает метод run() SocketProcessor для обработки Socket.
  3. createProcessor() создаст Http11Processor, который используется для анализа сокета и инкапсуляции содержимого сокета в запрос. Обратите внимание, что этот запрос является временным классом, полное имя которого — org.apache.coyote.Request.
  4. Метод postParseRequest() инкапсулирует запрос и обрабатывает отношение сопоставления (сопоставление URL-адреса с соответствующим хостом, контекстом, оболочкой).
  5. CoyoteAdapter инкапсулирует org.apache.coyote.Request в org.apache.catalina.connector.Request перед отправкой Rquest в Container для обработки, а запрос, переданный в Container для обработки, называется org.apache.catalina.connector.Request.
  6. Connector.getService().getMapper().map() используется для запроса отношения сопоставления URL-адресов в Mapper. Отношение сопоставления будет сохранено в org.apache.catalina.connector.Request. На этапе обработки контейнера request.getHost() использует узел сопоставления, запрошенный на этом этапе, и так далее request.getContext(), request.getWrapper( ) Оба есть.
  7. Connector.getService().getContainer().getPipeline().getFirst().invoke() передаст запрос в Контейнер для обработки.Конечно, обработка Контейнера также выполняется в потоке Worker, но это относительно независимый модуль.Так что это отдельный раздел.

Container

  1. Следует отметить, что в основном StandardPipeline каждого контейнера будет иметь несколько зарегистрированных клапанов, и мы сосредоточимся только на базовом клапане каждого контейнера. Другие клапаны выполняются перед основным клапаном.
  2. request.getHost().getPipeline().getFirst().invoke() сначала получает соответствующий StandardHost и выполняет его конвейер.
  3. request.getContext().getPipeline().getFirst().invoke() сначала получает соответствующий StandardContext и выполняет его конвейер.
  4. request.getWrapper().getPipeline().getFirst().invoke() сначала получает соответствующий StandardWrapper и выполняет его конвейер.
  5. Наиболее достойным упоминания является базовый клапан StandardWrapper, StandardWrapperValve.
  6. allocate() используется для загрузки и инициализации сервлетов. Упомянутое значение заключается в том, что сервлеты не все являются синглтонами. Когда сервлет реализует интерфейс SingleThreadModel, StandardWrapper будет поддерживать набор экземпляров сервлета, что является режимом легковеса. Конечно, SingleThreadModel устарела, начиная с Servlet 2.4.
  7. Метод createFilterChain() получит все фильтры из StandardContext, а затем выберет все фильтры, соответствующие URL-адресу запроса, и добавит их в filterChain.
  8. doFilter() выполняет цепочку фильтров и вызывает метод service() сервлета после выполнения всех фильтров.

Reference

  1. «Как работает Томкэт»
  2. «Анализ архитектуры Tomcat» — Лю Гуанжуй
  3. Tomcat-9.0-doc
  4. apache-tomcat-9.0.0.M22-src
  5. анализ архитектуры tomcat (реализация коннектора NIO)

Наконец

Прошу всех обратить внимание на мой паблик [Программист в погоне за ветром], в нем будут обновляться статьи, а также размещаться отсортированная информация.