Разработчики на PHP умеют писать код, но не всегда знают как устроен web-server

от автора

Одной из ключевых особенностей PHP является — легкость для разработчика в написании первой программы. Во многих мануалах для старта разработки сокращают информацию о web-сервере до минимума, например, запустите openserver или скопируйте собранный докер образ, где уже будет все настроено и просто перейдите по адресу http://localhost. Все это приводит к сужению знаний общей картины как работает web-приложение, что негативно влияет на репутацию разработчиков на этом языке программирования в целом. В прошлой статье я обещал рассказать о web-серверах для PHP, как раз для того, чтобы расширить кругозор тех людей, кто пропустил эту тему и постараться раскрыть ее максимально простым и понятным языком.

Язык программирования PHP является интерпретируемым, в нем нет встроенного production-ready http сервера. Чаще всего на своей практике встречал:

  • Apache с mod_php или mod_fcgid модулем

  • Nginx + PHP-FPM

  • RoadRunner

Все они работают по простому принципу:

  1. Слушается IP + port

  2. Запускаются worker с интерпретатором

  3. Передают входящие запросы worker’у, а по полученный ответ передают запросившему клиенту.

В теории все просто, но как и в любом деле есть свои нюансы. 

В большинстве случаев за работу web-сервера отвечают системные администраторы, но эта статья больше для разработчиков, поэтому будем рассматривать только моменты связанные с разработкой, а не полной настройкой web-серверов. 

Во всех типах web-серверов в конфигурации указываются правила, по которым находится точка входа в приложение, а также какие файлы являются интерпретируемыми, а какие статичными. В популярных PHP-framework’ах все сводится к одной точке входа, на которую попадают все запросы, а роутинг уже настраивается внутри приложения. После запуска web-сервера вместе с ним запускаются worker’ы. В один момент времени 1 worker может обрабатывать только 1 запрос. Это самое важное, что должен понимать разработчик, когда пишет код на PHP. 

Apache и Nginx + PHP-FPM — при каждом запросе запускают скрипт с точки входа, заново подключают все подключаемые файлы и исполняют код, в случае с RoadRunner запускается новый cli процесс, в котором приложение может быть уже инициализировано. Как правило для ускорения работы используют opcache, он не является частью web-сервера, а является частью интерпретатора. Его основная функция — сохранять полученный от Zend VM opcode для дальнейшего его переиспользования.  

Для снижения накладных расходов самого web-сервера на обработку запроса они изначально, согласно конфигурации, поднимают определенное количество worker. Исходя из этого, легко можно сделать вывод, что в один момент времени, web-сервер может обработать количество запросов равное количеству worker’ов, поэтому основная задача разработчика — освободить worker как можно быстрее, чтобы он мог обработать новый запрос. 

Важно, что worker освобождается не в момент отправки ответа клиенту, а в момент, когда последняя инструкция кода была выполнена, то есть, вызваны все __destruct функции, выполнены все shutdown функции.

Для более качественной работы важно для каждого внешнего соединения указывать timeout самого соединения и timeout ответа на запрос. Для защиты от этого в PHP есть специальные настройки — max_execution_time (по умолчанию: 60 секунд), default-socket-timeout (по умолчанию: 60 секунд). 

Что происходит, если у нас есть 20 воркеров, а пришло 50 запросов:

  1. В работу будет взято 20 запросов, остальные 30 запросов — уйдут в очередь либо будут сразу отброшены web-сервером. 

  2. После обработки каждого запроса будет браться следующий запрос, уменьшая очередь ожидания

  3. Если время ожидания и время выполнения запроса укладывается в общее время ожидания запроса web-сервером, то такие запросы будут обработаны, если же нет, то такие запросы, в основном, вернут ошибку 504 Gateway time out.

Что важно знать разработчику об Apache

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

Что важно знать разработчику о Nginx + PHP-FPM сервере

PHP-FPM — не является web-сервером, он является FastCGI Process Manager, поэтому вместе с ним необходим Nginx, который проксирует http-запросы в PHP-FPM. 

Что важно знать разработчику о RoadRunner

RoadRunner — web-сервер написанный на golang. Он запускает cli-команду воркера и по сокету общается с запущенным скриптом. В зависимости от настроек он может перезапускать worker’ы по разным параметрам, но чаще всего запущенный worker обрабатывает более одного запроса. Все это приводит к тому, что необходимо следить:

  • За открытыми соединениями (может быть база данных, брокер сообщений, сокет для отправки логов и любое другое открытое соединение); 

  • За статическими переменными, они будут актуальны только в рамках работы одного worker’а, но доступны в разных запросах, в идеале отказаться от них или же использовать с полным пониманием как работает позднее статическое связывание;

  • Очисткой памяти (тут есть нюансы, сам Zend VM, в котором работает код не идеален, поэтому утечки памяти могут быть при плохой организации кода и от них никак не избавитесь).

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

В результате теста у меня получилось:

User CPU

6.048ms

System CPU

3.629ms

Memory

0.38MB

Memory real usage

2.00MB

Total time

3.009s

Это говорит о том, что мы заняли воркер на 3 секунды, а в действительности использовали только 3.5мс времени процессора, все остальное время просто занимали worker. 

Заключение

Все это говорит о том, что базово PHP не подходит для того, чтобы использоваться в highload проектах, в чистом виде. Opcache с preload, jit, roadrunner, которые помогают сократить момент инициализации — никак не решают проблемы с синхронным выполнением запросов во внешние системы, а 90% кода как раз состоит из того, чтобы получить данные из stateful системы, преобразовать и выдать результат и/или передать дальше в stateful систему. Но не спешите расстраиваться и отвергать язык, в большинстве случаев даже в относительно крупных проектах не требуется выдерживать большие нагрузки и допустима обработка запросов в пределах одной секунды и написание бизнес логики на этом языке довольно простое. 

PS. Лично для меня PHP стал близким ЯП, хотя я работаю с разными языками, golang, java, c-lang… это отличные языки для своих целей, в каждом есть свои плюсы и минусы. Понимая суть, как работают разные языки и какие есть недостатки у PHP, я хочу продолжить разговор о языке и его возможностях. Например, у меня получилось с помощью swoole 5й версии (предыдущие версии действительно были плохие) создать web-сервер, который обращаясь к базе данных и выдавая ответ может одновременно обрабатывать большое количество соединений с отсутствием проблемы с обработкой запросов в один момент времени. 

Только зарегистрированные пользователи могут участвовать в опросе. Войдите, пожалуйста.

Интересно узнать как повел себя PHP с помощью Swoole

64.18% Да43
35.82% Нет24

Проголосовали 67 пользователей. Воздержались 3 пользователя.

ссылка на оригинал статьи https://habr.com/ru/articles/832040/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *