Как обрабатывает множество одновременных запросов серверная часть сайта на php?

Допустим одновременно в одну секунду сразу 1000 пользователей открывают страницу сайта,куда подгружается много данных из бд. Каков в таком случае порядок и принцип обработки множества одновременных запросов?Неужели все запросы обрабатываются поочереди? как сервер выбирает какому пользователю первым отправить ответ, если пользователи обратились к нему одновременно?
  • Вопрос задан
  • 1648 просмотров
Решения вопроса 1
iiiBird
@iiiBird
Пока ты спишь - твой конкурент совершенствуется
зависит от настроек nginx/apache - он может асинхронно направить пользователей с учётом доступного пула у сервера или по очереди.
d85c864ff8f54a4fad70cfe103137ed5.png
также в зависимости от настроек кэша nginx/apache/framework'a будет либо 1000 запросов к бд (у бд тоже может быть кэш), либо 1, а остальное отдастся из кэша.
Ответ написан
Пригласить эксперта
Ответы на вопрос 2
seriyPS
@seriyPS
Объяснение не будет привязано конкретно к PHP и Nginx, но будет более-менее одинаково применимо к любому сетевому сервису работающему по prefork-модели.

Пререквизиты:
1. Имеется серверное ПО, которое делает системный вызов `accept()` на сокете на определённом TCP порту (например, Nginx на порту 443)
2. Имеется ограниченное количество (N) процессов-обработчиков (бэкенд), каждый из которых может обрабатывать только один запрос одновременно. Но сами по себе процессы-обработчики могут работать параллельно и независимо друг от друга. Т.е. в момент времени может обрабатываться не больше N запросов (например, php-fpm)
3. Для простоты будем считать что запросы - HTTP и не будем касаться KEEP-ALIVE; так же будем считать, что `accept()` делается только из одного потока

Дело в том, что в реальном мире не существует "1000 запросов пришло одновременно". Они всегда приходят в определённом порядке, т.к. просто сетевые пакеты приходят по проводу в каком-то порядке. Системный вызов `accept()` всегда возвращает только один подключившийся сокет, чтобы получить следующий нужно вызвать `accept()` ещё раз. Таким образом, какой из клиентов первым установил TCP соединение тот и будет считаться первым (правда в случае с nginx и http скорее всего nginx не будет передавать запрос бэкенду до того как клиент не отправит весь запрос полностью, т.е. первым будет передан на обработку бэкенду тот запрос, который будет первым полностью отправлен клиентом - т.е. как только после очередного `read()` из сокета http парсер nginx поймет что клиент отправил запрос полностью, тогда и передаст запрос бэкендам).

Бэкенды как правило бывают организованы в пул.
Наиболее популярная реализация пула выглядит так:

* Имеется очередь для запросов. Очередь может быть бесконечного размера, может иметь ограничение на максимальное количество запросов в очереди (backlog), может иметь ограничение на то как долго запрос может лежать в очереди (timeout) и прочее.
* Воркеры бэкенда, не занятые обработкой запроса, тем или иным способом (mutex/spin-wait etc) проверяют есть ли сейчас задания в конце очереди. Если нет, блокируются в ожидании их появления. Если есть, забирает задание из очереди, обрабатывает его, возаращая ответ, и снова идет проверять очередь. Если все воркеры заняты обработкой запросов, то длина очереди не будет уменьшаться пока один из них не освободится.
* Ну а сервер который принимает запросы (nginx) просто добавляет запросы в начало очереди. Если очередь имеет ограничения по длине и переполнена, попытка добавить новый запрос приводит к ошибке (например, "502 Bad Gateway"). Если у очереди есть максимальное время нахождения в ней запроса и это время исткело, запрос будет удалён из очереди и будет возвращена ошибка (напр "504 Gateway Time-out").

Теперь представим ситуацию, что у нас есть nginx и N = 50 процессов-обработчиков (например 50 форков php-fpm). На сервер пришло "одновременно" 1000 запросов (как выяснили выше, одновременно всё-же подразумевает явный порядок). Т.е. все 1000 запросов будут добавлены в очередь пула "одновременно" в цикле один за другим. Пусть на обработку одного запроса процессу бэкенда нужна одна секунда, лимит длины очереди (backlog) выставлен в 800 запросов и timeout в 10 секунд. При таком раскладе первые 50 запросов сразу будут подхвачены воркерами и будут обрабатыватсья 1 секунду. Из оставшихся 950ти следующие 800 будут помещены в очередь а последние 150 сразу же отвалятся с ошибкой "502 Bad Gateway".
Т.к. наш пул воркеров способен обработать 50 запросов в секунду (`50 воркеров / 1 секунда на запрос`) то из 800 добавленных в очередь запросов за время до таймаута 10 секунд они успеют обработать ещё 50 * 10 = 500 запросов (или 50 * 9 = 450 если время собственно обработки тоже считается). Остальные 800 - 500 = 300 отвалятся с "504 Gateway Time-out". А ответы на запросы будут возвращены так: через 1 секунду с момента отправки запросов ответы на первые 50 запросов, через 2 секунды на следующие 50, через 3 на следующие 50 и т.д. до 10 секунд. Т.е. наиболее удачливые будут ждать 1 секунду, менее удачливые 2 секунды, максимальое ожидание составит 10 секунд.

Немного упрощённо и грубовато, но общее представление должно дать.
Ответ написан
@engineerVadim
Почитайте https://habr.com/post/312662/ к примеру или аналогичное. Там все есть. удачи
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Войти через TM ID
Похожие вопросы