Nginx + PHP: остановить процесс при аннулированном запросе

У меня есть Nginx 1.4.4 и PHP 5.5.6. Я делаю запросы на длительный опрос. Проблема в том, что если я отменяю HTTP-запрос, отправленный через Ajax, запросы все еще обрабатываются (они не останавливаются). Я протестировал его с помощью функции PHP mail() в конце файла, а почта все еще идет, и файл не останавливался).

Я беспокоюсь, потому что я думаю, что это может привести к сбою сервера из-за высокой нагрузки незакрытых запросов. Да, я пробовал ignore_user_abort(false);, но без изменений. Возможно, что я должен что-то изменить в Nginx?

  location ~ \.php$ {    
    try_files $uri =404;
    include fastcgi_params;
    fastcgi_pass 127.0.0.1:9000;
    fastcgi_index index.php;
    fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;  
  }

Ответы

Ответ 1

Плохая новость: вы почти наверняка не сможете решить свою проблему, как вы хотите ее решить. Сигнал FastCGI, отправленный, когда клиент закрывает соединение до получения запроса, FCGI_ABORT_REQUEST

Веб-сервер прерывает запрос FastCGI, когда клиент HTTP закрывает в то время как запрос FastCGI работает от имени этого клиента. Ситуация может показаться маловероятной; большинство запросов FastCGI будет иметь короткое время отклика, с выходом веб-сервера буферизация, если клиент работает медленно. Но приложение FastCGI может быть задержка связи с другой системой или выполнение сервера толчок.

К сожалению, он не выглядит как оригинальная реализация fast-cgi, а PHP-FPM поддерживают сигнал FCGI_ABORT_REQUEST и поэтому не могут быть прерваны.

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

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

В дополнение к этим 3 основным вещам - если вы обеспокоены тратой ресурсов системы, когда клиент больше не интересуется результатами запроса, вы должны добавить:

  • Разбивайте задачи на небольшие части работы и перемещайте задачи только из одного состояния работы на другое, если клиент все еще запрашивает результат.

Вы не говорите, какова ваша давно работающая задача - пусть притворяется, что она загружает большой файл изображения с другого сервера, манипулирует этим изображением, а затем сохраняет его в S3. Таким образом, состояния для этой задачи будут выглядеть примерно так:

TASK_STATE_QUEUED
TASK_STATE_DOWNLOADING //Moves to next state when finished download
TASK_STATE_DOWNLOADED
TASK_STATE_PROCESSING  //Moves to next state when processing finished
TASK_STATE_PROCESSED
TASK_STATE_UPLOADING_TO_S3 //Moves to next state when uploaded
TASK_STATE_FINISHED

Итак, когда клиент отправляет исходный запрос, он возвращает идентификатор taskID, а затем, когда он запрашивает состояние этой задачи, либо:

  • Сервер сообщает, что задача все еще обрабатывается

или

  • Если он находится в одном из следующих состояний, клиентский запрос удаляет его до следующего состояния.

то есть.

TASK_STATE_QUEUED => TASK_STATE_DOWNLOADING
TASK_STATE_DOWNLOADED => TASK_STATE_PROCESSING
TASK_STATE_PROCESSED => TASK_STATE_UPLOADING_TO_S3

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

btw Я настоятельно рекомендую использовать что-то, предназначенное для работы в качестве очереди для хранения очереди задач (например, Rabbitmq, Redis или Gearman), а не просто используя MySQL или любую базу данных. В принципе, SQL просто не так хорош в том, чтобы действовать как очередь, и вам было бы лучше использовать соответствующую технологию с самого начала, вместо того, чтобы использовать неправильную технологию для запуска, а затем придется ее заменять в чрезвычайной ситуации, когда ваша база данных становится перегружен при попытке сделать сотни вложений, обновлений в секунду для управления задачами.

В качестве побочного преимущества, разбивая долгий процесс работы на задачи, становится очень легко:

  • Посмотрите, где тратится время обработки.
  • Смотрите и определите колебания во время обработки (например, если CPUS достигнет 100% -ной загрузки, тогда размер изображения будет значительно увеличиваться).
  • Выбросьте ресурсы на медленных шагах.
  • Вы можете отправлять сообщения о состоянии обновления клиенту, чтобы они могли видеть прогресс в задаче, что дает лучший UX, а не просто сидит там "ничего не делает".

Ответ 2

Что именно вы делаете в этих длительных запросах? Если все, что вы делаете, заставляет процесс FastCGI ждать какого-либо системного вызова, например, ожидая возвращения базы данных в результате, прерванное соединение HTTP-клиента не приведет к прерыванию этого вызова. Если я правильно помню, эффект ignore_user_abort(false) заключается только в том, что PHP скрипт прерывается, как только он пытается вывести что-то в соединение (теперь потерянное). script не будет писать какой-либо вывод, пока он ждет системного вызова.

Если возможно, вы должны разделить задачу, которую выполняет длительный script на меньшие куски, и проверить состояние соединения между ними. Убедитесь, что script завершается, если соединение было прервано:

while (!$done_yet) {
    if(connection_status() != CONNECTION_NORMAL) {
        break;
    }
    do_more_work();
}

В документации PHP вы найдете более подробную информацию о обработке соединений, если хотите.