Иногда возникает необходимость писать скрипты, работа которых занимает продолжительное время. Например, скрипты создания/развертывания бэкапов, установки демо-версии какого-то приложения, агрегирования больших объемов данных, импорта/экспорта данных и т.п. Для того, чтобы такие скрипты не прекращали свою работу в неожиданный момент, нужно знать и помнить о некоторых вещах.
Внешний таймаут
В первую очередь нужно установить подходящее значение параметра max_execution_time в конфиге PHP.
Если скрипт запускается веб-сервером (т.е. в ответ на HTTP-запрос от пользователя), то следует также правильно настроить параметры таймаута в конфиге веб-сервера. Для apache это параметры TimeOut и FastCgiServer… -idle-timeout ... (если PHP работает через FastCGI), для nginx send_timeout и fastcgi_read_timeout (если PHP работает через FastCGI).
Веб-сервер может также проксировать запросы на другой веб-сервер, который и запустит PHP скрипт (не редкий пример, nginx — фронтенд, apache — бэкэнд). В этом случае на проксирующем веб-сервере необходимо также настраивать таймаут проксирования. Для apache ProxyTimeout, для nginx proxy_read_timeout.
Прерывание пользователем
Если скрипт запускается в ответ на HTTP-запрос, то пользователь может остановить выполнение запроса в своем браузере, в этом случае прекратит свою работу и PHP скрипт. Если же требуется, чтобы скрипт продолжил свою работу даже после остановки запроса, установите в TRUE параметр ignore_user_abort в конфиге PHP.
Потеря открытых соединений
Если в скрипте открывается соединение с каким-либо сервисом/службой (с БД, с почтовым сервером, с FTP-сервером, ...), и во время выполнения скрипта некоторое время соединение не используется, то оно может быть закрыто этим сервисом. Например, если во время работы скрипта некоторое время не выполнять запросы к MySQL, то MySQL закроет соединение через время, заданное в параметре wait_timeout. Как следствие, при попытке выполнить очередной запрос возникнет ошибка.
В таких случаях следует проверять активность соединения, в тех местах кода, где возможны простои его использования, и переподключаться при необходимости. Например в модуле MySQLi есть полезная функция mysqli::ping для проверки активности соединения, а также параметр конфигурации mysqli.reconnect для автоматического переподключения, при разрыве соединения. При отсутствии подобных функций для других видов соединений, можно попробовать написать ее самому. В ней нужно тривиальным образом обратиться к сервису (например, выполнить запрос SELECT 1 FROM dual), и в случае ошибки (отловить при помощи try… catch ...) переподключиться.
Параллельный запуск
Нередко долгие скрипты запускаются по расписанию (по cron), и ожидается, что в один момент времени будет работать только одна копия скрипта. Но может случиться так, что очередной запуск скрипта произойдет раньше, чем закончит работу предыдущий, и как правило это нежелательно (дважды импортируются одни и те же данные, затрутся данные используемые первым скриптом, ...).
В таких случаях можно использовать блокировку используемых ресурсов, но эта задача всегда решается индивидуально. Либо можно просто проверять, не запущена ли другая копия этого скрипта, и либо подождать завершения его работы, либо завершить текущий запуск. Для этого можно просматривать список запущенных процессов, либо использовать блокировку запуска самого скрипта, что то вроде:
if (lockStart('script.php'))
{
// основной код скрипта
...
lockStop('script.php');
}
Нагрузка на веб-сервер
В случаях, когда долгие скрипты запускаются через веб-сервер, соединение клиента с этим самым веб-сервером остается открытым до тех пор, пока не отработает скрипт. Это не есть хорошо, т.к. задача веб-сервера как можно быстрее обработать запрос и отдать результат. Если же соединение остается висеть, то один из воркеров (процессов) веб-сервера на долгое время будет занят. А если одновременно будет запущено достаточно много таких скриптов, то они могут занять все (ну или почти все) свободные воркеры (для apache см. MaxClients), и веб-сервер просто не сможет обрабатывать другие запросы.
Поэтому следует при обработке запроса пользователя, запускать скрипт в фоновом режиме через php-cli, чтобы не нагружать веб-сервер, а пользователю отвечать что его запрос обрабатывается. При необходимости можно периодически проверять состояние обработки при помощи AJAX запросов.
Вот, пожалуй, и все что я могу рассказать по этой теме. Надеюсь, для кого-то будет полезным.
Автор: gegokk