- PVSM.RU - https://www.pvsm.ru -

Проблемы «долгих» скриптов PHP

Проблемы «долгих» скриптов PHP Иногда возникает необходимость писать скрипты, работа которых занимает продолжительное время. Например, скрипты создания/развертывания бэкапов, установки демо-версии какого-то приложения, агрегирования больших объемов данных, импорта/экспорта данных и т.п. Для того, чтобы такие скрипты не прекращали свою работу в неожиданный момент, нужно знать и помнить о некоторых вещах.

Внешний таймаут

В первую очередь нужно установить подходящее значение параметра max_execution_time [1] в конфиге PHP.

Если скрипт запускается веб-сервером (т.е. в ответ на HTTP-запрос от пользователя), то следует также правильно настроить параметры таймаута в конфиге веб-сервера. Для apache это параметры TimeOut [2] и FastCgiServer… -idle-timeout ... [3] (если PHP работает через FastCGI), для nginx send_timeout [4] и fastcgi_read_timeout [5] (если PHP работает через FastCGI).

Веб-сервер может также проксировать запросы на другой веб-сервер, который и запустит PHP скрипт (не редкий пример, nginx — фронтенд, apache — бэкэнд). В этом случае на проксирующем веб-сервере необходимо также настраивать таймаут проксирования. Для apache ProxyTimeout [6], для nginx proxy_read_timeout [7].

Прерывание пользователем

Если скрипт запускается в ответ на HTTP-запрос, то пользователь может остановить выполнение запроса в своем браузере, в этом случае прекратит свою работу и PHP скрипт. Если же требуется, чтобы скрипт продолжил свою работу даже после остановки запроса, установите в TRUE параметр ignore_user_abort [8] в конфиге PHP.

Потеря открытых соединений

Если в скрипте открывается соединение с каким-либо сервисом/службой (с БД, с почтовым сервером, с FTP-сервером, ...), и во время выполнения скрипта некоторое время соединение не используется, то оно может быть закрыто этим сервисом. Например, если во время работы скрипта некоторое время не выполнять запросы к MySQL, то MySQL закроет соединение через время, заданное в параметре wait_timeout [9]. Как следствие, при попытке выполнить очередной запрос возникнет ошибка.

В таких случаях следует проверять активность соединения, в тех местах кода, где возможны простои его использования, и переподключаться при необходимости. Например в модуле MySQLi есть полезная функция mysqli::ping [10] для проверки активности соединения, а также параметр конфигурации mysqli.reconnect [11] для автоматического переподключения, при разрыве соединения. При отсутствии подобных функций для других видов соединений, можно попробовать написать ее самому. В ней нужно тривиальным образом обратиться к сервису (например, выполнить запрос SELECT 1 FROM dual), и в случае ошибки (отловить при помощи try… catch ...) переподключиться.

Параллельный запуск

Нередко долгие скрипты запускаются по расписанию (по cron), и ожидается, что в один момент времени будет работать только одна копия скрипта. Но может случиться так, что очередной запуск скрипта произойдет раньше, чем закончит работу предыдущий, и как правило это нежелательно (дважды импортируются одни и те же данные, затрутся данные используемые первым скриптом, ...).

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

if (lockStart('script.php'))
{
    // основной код скрипта
    ...
    lockStop('script.php');
}

Нагрузка на веб-сервер

В случаях, когда долгие скрипты запускаются через веб-сервер, соединение клиента с этим самым веб-сервером остается открытым до тех пор, пока не отработает скрипт. Это не есть хорошо, т.к. задача веб-сервера как можно быстрее обработать запрос и отдать результат. Если же соединение остается висеть, то один из воркеров (процессов) веб-сервера на долгое время будет занят. А если одновременно будет запущено достаточно много таких скриптов, то они могут занять все (ну или почти все) свободные воркеры (для apache см. MaxClients [12]), и веб-сервер просто не сможет обрабатывать другие запросы.

Поэтому следует при обработке запроса пользователя, запускать скрипт в фоновом режиме через php-cli, чтобы не нагружать веб-сервер, а пользователю отвечать что его запрос обрабатывается. При необходимости можно периодически проверять состояние обработки при помощи AJAX запросов.

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

Автор: gegokk

Источник [13]


Сайт-источник PVSM.RU: https://www.pvsm.ru

Путь до страницы источника: https://www.pvsm.ru/php-2/31325

Ссылки в тексте:

[1] max_execution_time: http://www.php.net/manual/ru/info.configuration.php#ini.max-execution-time

[2] TimeOut: http://httpd.apache.org/docs/2.2/mod/core.html#timeout

[3] FastCgiServer… -idle-timeout ...: http://www.fastcgi.com/mod_fastcgi/docs/mod_fastcgi.html#FastCgiServer

[4] send_timeout: http://nginx.org/ru/docs/http/ngx_http_core_module.html#send_timeout

[5] fastcgi_read_timeout: http://nginx.org/ru/docs/http/ngx_http_fastcgi_module.html#fastcgi_read_timeout

[6] ProxyTimeout: http://httpd.apache.org/docs/2.2/mod/mod_proxy.html#proxytimeout

[7] proxy_read_timeout: http://nginx.org/ru/docs/http/ngx_http_proxy_module.html#proxy_read_timeout

[8] ignore_user_abort: http://www.php.net/manual/ru/misc.configuration.php#ini.ignore-user-abort

[9] wait_timeout: http://dev.mysql.com/doc/refman/5.6/en/server-system-variables.html#sysvar_wait_timeout

[10] mysqli::ping: http://www.php.net/manual/ru/mysqli.ping.php

[11] mysqli.reconnect: http://www.php.net/manual/en/mysqli.configuration.php#ini.mysqli.reconnect

[12] MaxClients: http://httpd.apache.org/docs/2.2/mod/mpm_common.html#maxclients

[13] Источник: http://habrahabr.ru/post/175651/