Проблемы «долгих» скриптов PHP
Иногда возникает необходимость писать скрипты, работа которых занимает продолжительное время. Например, скрипты создания/развертывания бэкапов, установки демо-версии какого-то приложения, агрегирования больших объемов данных, импорта/экспорта данных и т.п. Для того, чтобы такие скрипты не прекращали свою работу в неожиданный момент, нужно знать и помнить о некоторых вещах.
Внешний таймаут
В первую очередь нужно установить подходящее значение параметра 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. Как следствие, при попытке выполнить очередной запрос возникнет ошибка.
В таких случаях следует в первую очередь попробовать увеличить таймаут соединения. Например, для MySQL можно выполнить запрос (спасибо Snowly)
SET SESSION wait_timeout = 9999
Если же такой возможности нет или этот вариант по каким то причинам не подходит, то можно проверять активность соединения, в тех местах кода, где возможны простои его использования, и переподключаться при необходимости. Например в модуле MySQLi есть полезная функция mysqli::ping для проверки активности соединения, а также параметр конфигурации mysqli.reconnect для автоматического переподключения, при разрыве соединения. При отсутствии подобных функций для других видов соединений, можно попробовать написать ее самому. В ней нужно тривиальным образом обратиться к сервису и в случае ошибки (отловить при помощи try… catch . ) переподключиться. Например
class FtpConnection < private $ftp; public function connect() < $this->ftp = ftp_connect('ftp.server'); . > public function reconnect() < try < if (!ftp_pwd($this->ftp)) $this->connect(); > catch($e) < $this->connect(); > > . >
class MssqlConnection < private $db; public function connect() < $this->db = mssql_connect('mssql.server'); . > public function reconnect() < try < if (!mssql_query('SELECT 1 FROM dual', $this->db)) $this->connect(); > catch($e) < $this->connect(); > > . >
Параллельный запуск
Нередко долгие скрипты запускаются по расписанию (по cron), и ожидается, что в один момент времени будет работать только одна копия скрипта. Но может случиться так, что очередной запуск скрипта произойдет раньше, чем закончит работу предыдущий, и как правило это нежелательно (дважды импортируются одни и те же данные, затрутся данные используемые первым скриптом, . ).
В таких случаях можно использовать блокировку используемых ресурсов, но эта задача всегда решается индивидуально. Либо можно просто проверять, не запущена ли другая копия этого скрипта, и либо подождать завершения его работы, либо завершить текущий запуск. Для этого можно просматривать список запущенных процессов, либо использовать блокировку запуска самого скрипта, что то вроде:
Нагрузка на веб-сервер
В случаях, когда долгие скрипты запускаются через веб-сервер, соединение клиента с этим самым веб-сервером остается открытым до тех пор, пока не отработает скрипт. Это не есть хорошо, т.к. задача веб-сервера как можно быстрее обработать запрос и отдать результат. Если же соединение остается висеть, то один из воркеров (процессов) веб-сервера на долгое время будет занят. А если одновременно будет запущено достаточно много таких скриптов, то они могут занять все (ну или почти все) свободные воркеры (для apache см. MaxClients), и веб-сервер просто не сможет обрабатывать другие запросы.
Поэтому следует при обработке запроса пользователя, запускать скрипт в фоновом режиме через php-cli, чтобы не нагружать веб-сервер, а пользователю отвечать что его запрос обрабатывается. При необходимости можно периодически проверять состояние обработки при помощи AJAX запросов.
Вот, пожалуй, и все что я могу рассказать по этой теме. Надеюсь, для кого-то будет полезным.
set_time_limit
Задает время в секундах, в течение которого скрипт должен завершить работу. Если скрипт не успевает, вызывается фатальная ошибка. По умолчанию дается 30 секунд, либо время, записанное в настройке max_execution_time в php.ini (если такая настройка установлена).
При вызове set_time_limit() перезапускает счетчик с нуля. Другими словами, если таймаут изначально был 30 секунд, и через 25 секунд после запуска скрипта будет вызвана функция set_time_limit(20), то скрипт будет работать максимум 45 секунд.
Список параметров
Максимальное время выполнения в секундах. Если задан ноль, время выполнения неограничено.
Возвращаемые значения
Возвращает TRUE в случае успеха, иначе FALSE .
Примечания
Эта функция не работает, если PHP работает в безопасном режиме (безопасный режим). Обойти это ограничение можно только выключив безопасный режим или изменив значение настройки в php.ini .
Замечание:
Функция set_time_limit() и директива max_execution_time влияют на время выполнения только самого скрипта. Время затраченное на различные действия вне скрипта, такие как системные вызовы функции system() , потоковые операции, запросы к базам данных и т.п. не включаются в расчет времени выполнения скрипта. Это не относится к Windows системам, где расчитывается абсолютное время выполнения.