Add new comment

Как работает веб сервер

Системные вызовы операционных систем:

  1. socket - создает сокет, для веб сервера, обычно это tcp;
  2. bind - привязывает сокет к локальному ip-адресу сервера (железки) и на определенный порт. Нужно из-за того, что на железке может быть много ip-адресов;
  3. listen - переводит сокет в состояние "слушать" (включает его на прием и передачу - если просто), это значит, что сокет готов принимать соединения от клиента (см tcp handshake) и ставить его в очередь ОС (очередь приложения backlog);
  4. accept - создает сокет к клиенту, позволяя уже с ним обмениваться данными. Accept вызывает само приложение, в зависимости от алгоритма, учитывая что есть ресурсы на обработку клиентского запроса. Если accept не сделать, то соединение повиснит в очереди (backlog) на время таймаута выставленного со стороны клиента или сервера.

После accept, apache создает дочерний процесс, который уже и начинает из данных извлеченных из tcp "парсить" http по rfq (аля соглашению) и обрабатывать пользовательский запрос - разбирая зоголовки и выплевывая в сетевой буфер прочтенный с диска index.html с правильными заголовками http 1.0 или 1.1.

Цель веб-сервера

Цель веб-сервера проста - обслуживать одновременно большое количество клиентов, максимально эффективно используя hardware.

Работа с соединениями

С чего начинается обработка запроса? Очевидно - с приема соединения от пользователя.

Для этого в разных OS используются разные системные вызовы. Наиболее известный и медленный на большом количестве соединений - select. Более эффективные - poll, kpoll, epoll.

Современные веб-серверы постепенно отказываются от select.


Оптимизации ОС

Еще до приема соединения возможны оптимизации на уровне ядра ОС. Например, ядро ОС, получив соединение, может не беспокоить веб-сервер, пока не произошло одно из событий.

  1. пока не пришли данные (dataready)
  2. пока не пришел целиком HTTP-запрос (httpready)

На момент написания статьи оба способа поддерживаются во FreeBSD (ACCEPT_FILTER_HTTP, ACCEPT_FITER_DATA), и только первый - в Linux (TCP_DEFER_ACCEPT).

Эти оптимизации позволяют серверу меньше времени простаивать в ожидании данных, повышая таким образом общую эффективность работы.

Соединение принято

Итак, соединение принято. Теперь на плечи сервера ложится основная задача - обработать запрос и отослать ответ посетителю. Будем здесь рассматривать только динамические запросы, существенно более сложные, чем отдача картинок.

Во всех серверах используется асинхронный подход.

Он заключается в том, что обработка запроса спихивается куда-нибудь "налево" - отдается на выполнение вспомогательному процессу/потоку, а сервер продолжает работать и принимать-отдавать на выполнение все новые соединения.

В зависимости от реализации - процесс-помощник ("worker") может пересылать результат обратно серверу целиком (для последующей отдачи клиенту), может передавать серверу только дескриптор результата (без копирования), или может отдавать результат клиенту сам.

Основные стратегии работы с worker'ами

Работа с воркерами состоит из нескольких элементов, которые можно по-разному комбинировать и получать разный результат.
Тип worker'а

Основных типов два - это процесс и поток. Для улучшения производительности иногда используют оба типа одновременно, порождая несколько процессов и кучу потоков в каждом.

Процесс

Различные worker'ы могут быть процессами. В этом случае они не взаимодействуют между собой, и данные различных worker'а полностью независимы друг от друга.
Поток

Потоки, в отличие от процессов, имеют общие, разделяемые структуры данных. В коде worker'а должна быть реализована синхронизация доступа, чтобы одновременная запись одной и той же структуры не привела к хаосу.

Адресное пространство

Каждый процесс, в том числе и сервер, обладает своим адресным пространством, которое использует для разделения данных.

Внутри сервера

При работе внутри сервера - worker имеет доступ к данным сервера. Он может поменять любые структуры и делать разные гадости, особенно если написан с ошибками.

Плюсом является отсутствие пересылки данных из одного адресного пространства в другое.

Снаружи сервера

Worker может быть запущен вообще независимо от сервера и принимать данные на обработку по специальному протоколу (например FastCGI).

Конечно, этот вариант - самый безопасный для сервера. Но требует дополнительной работы по пересылке запроса - результата между сервером и worker'ом.

Рождение worker'ов

Чтобы обрабатывать много соединений одновременно - нужно иметь достаточное количество рабочих.

Основных стратегий - две.
Статика

Количество рабочих может быть жестко фиксированно. Например, 20 рабочих процессов всего. Если же все рабочие заняты и приходит 21й запрос - сервер выдает код Temporary Unavailable - "временно недоступен".

Динамика

Для более гибкого управления ресурсами - рабочие могут порождаться динамически, в зависимости от загрузки. Алгоритм порождения рабочих может быть параметризован, например (Apache pre-fork), так:

  • Минимальное количество свободных рабочих = 5
  • Максимальное количество свободных рабочих = 20
  • Всего рабочих не более = 30
  • Начальное количество рабочих = 10

Чистка между запросами

Рабочие могут либо заново инициализовать себя между запросами, либо - просто обрабатывать запросы один за другим.

Чистый

Перед каждым запросом очищается от того, что было раньше, чистит внутренние переменные и пр.
В результате нет проблем и ошибок, связанных с использованием переменных, оставшихся от старого запроса.

Персистентный

Никакой очистки состояния. В результате - экономия ресурсов.

Разбор типичных конфигураций

Посмотрим, как эти комбинации работают на примере различных серверов.

Apache (pre-fork MPM) + mod_php

Для обработки динамических запросов используется модуль php, работающий в контексте сервера.

  • Процесс
  • Внутри сервера
  • Динамика
  • Чистый

Для обработки динамических запросов используется модуль php, работающий в контексте сервера.

При этом, так как php работает в адресном пространстве сервера, разделяемые потоками данные периодически портятся, поэтому связка нестабильна и не рекомендована. Это происходит из-за ошибок в mod_php, который включает в себя ядро PHP и различные php-модули.

Ошибка в модуле, благодаря одному адресному пространству, может повалить весь сервер.

  • Поток
  • Внутри сервера
  • Динамика
  • Чистый

Apache (event mpm) + mod_php

Event MPM - это стратегия работы с worker'ами, которую использует только Apache. Все - точно так же, как с обычными потоками, но с небольшим дополнением для обработки Keep-Alive

Установка Keep-Alive служит для того, чтобы клиент мог прислать много запросов в одном соединении. Например, получить веб-страницу и 20 картинок. Обычно, worker заканчивает обработку запроса - и ждет какое-то время (keep-alive time), не последуют ли в этом соединении дополнительные запросы. То есть, просто висит в памяти.

Event MPM создает дополнительный поток, который берет на себя ожидание всех Keep-Alive запросов, освобождая рабочего для других полезных дел. В результате, общее количество worker'ов значительно сокращается, т.к никто теперь не ждет клиентов, а все работают.

  • Поток
  • Внутри сервера
  • Динамика
  • Чистый

FastCGI

FastCGI - интерфейс общения web-сервера с внешними worker'ами, которые обычно запущены как процессы. Сервер в специальном (не HTTP) формате передает переменные окружения, заголовки и тело запроса, а worker - возвращает ответ.

Есть два способа порождения таких worker'ов.

  • Интегрированный с сервером
  • Отдельный от сервера

В первом случае сервер сам создает внешние рабочие процессы и управляет их числом.

Во втором случае - для порождения рабочих процессов используется отдельный "spawner", второй, дополнительный сервер, который умеет общаться только по FastCGI-протоколу и управлять рабочими. Обычно spawner порождает рабочих в виде процессов, а не потоков. Динамика/Статика - определяется настройками spawner'а, а Чистый/Персистентный - характеристиками рабочего процесса.

Пути работы с FastCGI

С FastCGI можно работать двумя путями. Первый способ - самый простой, его использует Apache.
получить запрос -> отдать на обработку в FastCGI -> подождать ответа -> отдать ответ клиенту.

Второй способ используют сервера типа lighttpd/nginx/litespeed/и т.п.
получить запрос -> отдать на обработку в FastCGI -> обработать других клиентов -> отдать ответ клиенту, когда придет.

Отмеченное отличие позволяет Lighttpd + fastcgi работать эффективнее, чем это делает Apache, т.к пока процесс Apache ждет - Lighttpd успевает обслужить другие соединения.

Режимы работы FastCGI

У FastCGI есть два режима работы.

  • Responder - обычный режим, когда FastCGI принимает запрос и переменные, и возвращает ответ
  • Authorizer - режим, когда FastCGI в качестве ответа разрешает или запрещает доступ. Удобно для контроля за закрытыми статическими файлами

Оба режима поддерживаются не во всех серверах. Например, в сервере Lighttpd - поддерживаются оба.

Категория: 
The code has been tested and works

Filtered HTML

  • Web page addresses and e-mail addresses turn into links automatically.
  • Allowed HTML tags: <a> <em> <strong> <cite> <blockquote> <code> <ul> <ol> <li> <dl> <dt> <dd>
  • Lines and paragraphs break automatically.

Plain text

  • No HTML tags allowed.
  • Web page addresses and e-mail addresses turn into links automatically.
  • Lines and paragraphs break automatically.