Одной из ключевых особенностей PHP является - легкость для разработчика в написании первой программы. Во многих мануалах для старта разработки сокращают информацию о web-сервере до минимума, например, запустите openserver или скопируйте собранный докер образ, где уже будет все настроено и просто перейдите по адресу http://localhost. Все это приводит к сужению знаний общей картины как работает web-приложение, что негативно влияет на репутацию разработчиков на этом языке программирования в целом. В прошлой статье я обещал рассказать о web-серверах для PHP, как раз для того, чтобы расширить кругозор тех людей, кто пропустил эту тему и постараться раскрыть ее максимально простым и понятным языком.
Язык программирования PHP является интерпретируемым, в нем нет встроенного production-ready http сервера. Чаще всего на своей практике встречал:
Apache с mod_php или mod_fcgid модулем
Nginx + PHP-FPM
RoadRunner
Все они работают по простому принципу:
Слушается IP + port
Запускаются worker с интерпретатором
Передают входящие запросы worker’у, а по полученный ответ передают запросившему клиенту.
![](https://habrastorage.org/getpro/habr/upload_files/684/a3b/4c4/684a3b4c445f8729f05e8d18f2cf6641.jpg)
В теории все просто, но как и в любом деле есть свои нюансы.
В большинстве случаев за работу web-сервера отвечают системные администраторы, но эта статья больше для разработчиков, поэтому будем рассматривать только моменты связанные с разработкой, а не полной настройкой web-серверов.
Во всех типах web-серверов в конфигурации указываются правила, по которым находится точка входа в приложение, а также какие файлы являются интерпретируемыми, а какие статичными. В популярных PHP-framework’ах все сводится к одной точке входа, на которую попадают все запросы, а роутинг уже настраивается внутри приложения. После запуска web-сервера вместе с ним запускаются worker’ы. В один момент времени 1 worker может обрабатывать только 1 запрос. Это самое важное, что должен понимать разработчик, когда пишет код на PHP.
![](https://habrastorage.org/getpro/habr/upload_files/50f/c66/10b/50fc6610b2524ae89c7f027dc42403c3.jpeg)
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 запросов:
В работу будет взято 20 запросов, остальные 30 запросов - уйдут в очередь либо будут сразу отброшены web-сервером.
После обработки каждого запроса будет браться следующий запрос, уменьшая очередь ожидания
Если время ожидания и время выполнения запроса укладывается в общее время ожидания запроса 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-сервер, который обращаясь к базе данных и выдавая ответ может одновременно обрабатывать большое количество соединений с отсутствием проблемы с обработкой запросов в один момент времени.
Комментарии (20)
AlexeyPolunin
28.07.2024 13:44Да, пжлст больше вариантов использования накидайте и свои наблюдения от использования (они самые ценные). Нам тут есть куда применить. Спасибо!
avegad
28.07.2024 13:44Разработчики на PHP умеют писать код, но не всегда знают как устроен web-server
ИМХО, с 2010+ года, разработчики php умеют писать говнокод, и совсем не знают как устроен веб-сервер, не умеют его настраивать и более того, не могут выдать внятные системные требования к веб-серверу, версии php, необходимым библиотекам и т.д., для работы того, что они наговнокодили.
1Tiger1
28.07.2024 13:44загляни в composer. json, там все написано.
если там не написано, загляни в Dockerfile и docker-compose. yml
Andrey_Solomatin
28.07.2024 13:44Где гарантия что там цифры не скопированные с прошлого проекта и вообще они внятные а не завышенные в 10 раз?
Хотя я бы хотел посмотреть на того человека который может предугадать системные требования. Обычно подключаешь мониторинг и когда графики упираются, то что-то меняешь.
Paveldjdhhd
28.07.2024 13:44+1Невозможно подстраивать под себя окружение и не уметь его настраивать. Но большинству не нужно это, ведь буквально всё в вебе делается для облегчения работы
ignatfomenko
28.07.2024 13:44Ну по мне делами сервера должен админ заниматься. Если вы берете к примеру виртуальный хостинг, то там все уже настроено по максимуму изначально.
Кодер на PHP должен знать следующее:
Если у вас 2 и более запроса к базе данных, по одному вопросу, то нужно просто взять руководства по SQL. В большинстве случаев можно все к одному запросу к базе свести.
Если у вас большая работа с данными идет на странице, к примеру графики строятся, таблицы, рисунки, то если данные не секретные передайте их в браузер клиента, и пусть уже там JS мучается с этим вопросом.
Andreyika
28.07.2024 13:44+1никак не решают проблемы с синхронным выполнением запросов во внешние системы, а 90% кода как раз состоит из того, чтобы получить данные из stateful системы, преобразовать и выдать результат и/или передать дальше в stateful систему.
Подскажите, пожалуйста, ваш типичный 90% кодовый случай - что именно и откуда вы берете из внешних систем, как вам тут поможет "асинхронщина для хайлоада" и в чем у вас вообще проблема с асинхронными вызовами в пхп? (ну понятно, что у вас проблем нет, свуля и все такое, но была же в "обычном" пхп)
Nnnnoooo
28.07.2024 13:44+1текст вообще ни о чем, и похож на поток сознания. Смесь всем известной информации из документации и личных домыслов. И выводы просто замечательные (facepalm)
смешались в кучу кони, люди...
У меня вопрос. А как асинхронный код поможет в плане уменьшения ожидания от сторонних систем?
Nnnnoooo
28.07.2024 13:44+3С одной стороны понятен смысл данного поста. Прорекламировать асинхронную разработку под PHP.
Но сделано уж очень непрофессионально (опенсервер для профессиональной разработки - шта?), без указания кучи минусов, которые приносит асинхронщина в PHP.
michael_v89
28.07.2024 13:44мы заняли воркер на 3 секунды
никак не решают проблемы с синхронным выполнением запросов во внешние системыА их обычно и не надо решать. Просто воркеров должно быть больше, чем число запросов, приходящих за эти 3 секунды. Чем длиннее конвейер, тем шире он должен быть.
Асинхронное выполнение имеет смысл, когда есть запросы, которые можно выполнять параллельно, это случается не так уж часто. Если вам надо сначала сохранить заказ, а потом отправить его в стороннюю систему, то асинхронные запросы ничего не изменят.
Пример кода выложил на github
В результате теста у меня получилосьsleep(3); echo 'Hello world';
Угу, хороший тест. Написали 3 секунды ожидания, получили 3 секунды ожидания.
Daniel217D
28.07.2024 13:44Получилось предисловие к статье про асинхронность в PHP. Жду основной материал)
zorn-v100500
А они должны ?
Понятно что это "приятный бонус" для работодателя, но серверами разве не должны другие люди заниматься ?
На других языках по другому ?
Каждый питонист знает как настроить rate limit в nginx или отбросить ненужные запросы чтобы они даже до приложения не доходили ?
-- ADDED ибо косякопор (лимит на сообщения)
Я понял о чем речь. Видимо про это https://habr.com/ru/articles/179399/
В мозгах ПХП разработчика нет таких проблем как неочищенная память, статические переменные между запросами и т.п.
Есть запрос, твой скрипт должен вернуть ответ - ВСЕ.
Каждый запрос "новая жизнь".
Очень удобно, но не всегда, как вы заметили. Так и живем )
koksharov Автор
Web-сервер является неотъемлемой частью при разработке web-приложений.
Зная основы работы можно оптимизировать код, чтобы расходов на серверы было меньше.
Данная статья не про настройку web-сервера, а про основные моменты, которые разработчик должен понимать.
Да, в других языках нет прямой зависимости 1 worker = 1 запрос
Rate-limit это уже настройка от перегрузки запросами, зная особенности работы web-сервера на PHP можно ускорить обработку запросов, например, вынеся часть операций в фоновую обработку.
Andrey_Solomatin
Желательно чтобы хотя бы один человек на проекте это знал. Хотя если запросов мало, то пофиг на оптимизации.
Mausglov
если эти основы знает только один человек на проекте, а разработчиков несколько, то получается неудобно: практически все решения должны проходить через этого человека; он становится "бутылочным горлышком". Проще дать людям 10 минут прочитать эту статью, чтобы они сами отсеивали самую лютую дичь из своих решений.
Andrey_Solomatin
Да не надо всё замыкать на одного человека. Получат проблему, он быстро найдёт причины и расскажет, что произошло. Такой подход проблема - решение - объяснение хорошо запоминаетмся.
michael_v89
Процессор тоже является неотъемлемой частью при разработке любых приложений. Вы хорошо ассемблер знаете? Можете написать программу перехода из реального в защищенный режим? Провести отладку с GDB или IDA?