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; } 

Плохая новость заключается в том, что вы почти наверняка не сможете решить свою проблему, как вы хотите ее решить. Сигнал 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 просто не так хорош в том, чтобы действовать как очередь, и вам было бы лучше использовать соответствующую технологию с самого начала, вместо того, чтобы использовать неправильную технологию для запуска, а затем придется ее заменять в чрезвычайной ситуации, когда ваша база данных становится перегружен, когда он пытается сделать сотни вложений, обновлений в секунду для управления задачами.

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

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

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

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

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

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