Как определить, запущен ли PHP-скрипт? - PullRequest
11 голосов
/ 10 ноября 2010

У меня есть скрипт cron, который исполняет скрипт PHP каждые 10 минут. Скрипт проверяет очередь и обрабатывает данные в очереди. Иногда в очереди достаточно данных для обработки более 10 минут, что создает вероятность того, что два сценария попытаются получить доступ к одним и тем же данным. Я хочу быть в состоянии определить, запущен ли скрипт, чтобы предотвратить запуск нескольких копий скрипта. Я думал о создании флага базы данных, который говорит, что скрипт обрабатывается, но если скрипт когда-нибудь потерпит крах, он оставит его в положительном состоянии. Есть ли простой способ узнать, запущен ли PHP-сценарий без использования PHP или сценария оболочки?

Ответы [ 10 ]

32 голосов
/ 10 июля 2014

Вы можете просто использовать файл блокировки.Функция PHP flock() предоставляет простую оболочку для функции Unix flock, которая обеспечивает консультативную блокировку файлов.

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

Вы также можете следовать свободному соглашению Unix, согласно которому ваш файл блокировки должен быть «PID-файлом» -Получив блокировку файла, попросите ваш скрипт записать в него свой PID.Даже если вы никогда не читаете это из своего скрипта, вам будет удобно, если ваш скрипт зависает или сходит с ума, и вы хотите найти его PID, чтобы вручную убить его.

Вот копия / вставка-реализация:

#!/usr/bin/php
<?php

$lock_file = fopen('path/to/yourlock.pid', 'c');
$got_lock = flock($lock_file, LOCK_EX | LOCK_NB, $wouldblock);
if ($lock_file === false || (!$got_lock && !$wouldblock)) {
    throw new Exception(
        "Unexpected error opening or locking lock file. Perhaps you " .
        "don't  have permission to write to the lock file or its " .
        "containing directory?"
    );
}
else if (!$got_lock && $wouldblock) {
    exit("Another instance is already running; terminating.\n");
}

// Lock acquired; let's write our PID to the lock file for the convenience
// of humans who may wish to terminate the script.
ftruncate($lock_file, 0);
fwrite($lock_file, getmypid() . "\n");

/*
    The main body of your script goes here.
*/
echo "Hello, world!";

// All done; we blank the PID file and explicitly release the lock 
// (although this should be unnecessary) before terminating.
ftruncate($lock_file, 0);
flock($lock_file, LOCK_UN);

Просто укажите путь к файлу блокировки, где вам удобно, и все готово.

6 голосов
/ 10 ноября 2010

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

Более простой подход - создатьзапись БД или файл в начале выполнения и удаление его в конце.Вы всегда можете проверить «возраст» этой записи / файла, и если он старше, чем, скажем, в 3 раза больше нормального выполнения скрипта, предположите, что он сломался и удалите его.

Нет «серебряной пули», это просто зависитна ваши нужды.

5 голосов
/ 09 октября 2015

Если вы работаете в Linux, это должно работать в верхней части вашего скрипта:

$running = exec("ps aux|grep ". basename(__FILE__) ."|grep -v grep|wc -l");
if($running > 1) {
   exit;
}
3 голосов
/ 06 января 2015

Вы можете использовать новый Symfony 2.6 LockHandler . Источник

$lock = new LockHandler('update:contents');
if (!$lock->lock()) {
    echo 'The command is already running in another process.';
}
3 голосов
/ 15 октября 2013

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

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

// The file to store our process file
define('PROCESS_FILE', 'process.pid');

// Check I am running from the command line
if (PHP_SAPI != 'cli') {
    log_message('Run me from the command line');
    exit;
}

// Check if I'm already running and kill myself off if I am
$pid_running = false;
if (file_exists(PROCESS_FILE)) {
    $data = file(PROCESS_FILE);
    foreach ($data as $pid) {
        $pid = (int)$pid;
        if ($pid > 0 && file_exists('/proc/' . $pid)) {
            $pid_running = $pid;
            break;
        }
    }
}
if ($pid_running && $pid_running != getmypid()) {
    if (file_exists(PROCESS_FILE)) {
        file_put_contents(PROCESS_FILE, $pid);
    }
    log_message('I am already running as pid ' . $pid . ' so stopping now');
    exit;
} else {
    // Make sure file has just me in it
    file_put_contents(PROCESS_FILE, getmypid());
    log_message('Written pid with id '.getmypid());
}

Это не будет работать без изменений в Windows, но должно быть хорошо в системах на основе Unix.

3 голосов
/ 10 ноября 2010

Обычный способ для * nix демонов (хотя это не обязательно PHP-скрипты, но он будет работать) - это использование файла .pid.

Когда скрипт запускается, проверьте наличие файла .pid с именем для скрипта (обычно хранится в / var / run /). Если он не существует, создайте его, установив его содержимое в PID процесса, выполняющего скрипт (используя getmypid ), затем продолжите нормальное выполнение. Если он существует, прочитайте PID и посмотрите, работает ли этот процесс, возможно, запустив ps $pid. Если он работает, выйдите. В противном случае перезапишите его содержимое своим PID (как указано выше) и продолжите нормальное выполнение.

По завершении выполнения удалите файл.

0 голосов
/ 09 января 2019

Предполагается, что это сервер Linux, и у вас есть cronjobs

///Check for running script and run if non-exist///

#! /bin/bash

check=$(ps -fea | grep -v grep | grep script.php | wc -l)
date=$(date +%Y-%m%d" "%H:%M:%S)
if [ "$check" -lt 1 ]; then
echo "["$date"] Starting script" >> /path/to/script/log/
/sbin/script  ///Call the script here - see below///
fi

файл сценария

#/usr/bin/php /path/to/your/php/script.php
0 голосов
/ 23 января 2018

Я пробовал это. Хорошо работает и в Windows.

    $status_file=__DIR__.'/mail_process.txt';
    if(file_exists($status_file) && file_get_contents($status_file)=='running'){
                echo 'Program running';
            exit;
    }
    file_put_contents($status_file,'running');

       // Code block
       //...........

    file_put_contents($status_file,'finished');

Спасибо @martinodf за его идею.

0 голосов
/ 25 сентября 2015

Я знаю, что это старый вопрос, но есть подход, который ранее не упоминался, и который, я думаю, стоит рассмотреть.

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

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

function checkrun() {
    exec("ps auxww",$ps);
    $r = 0;
    foreach ($ps as $p) {
        if (strpos($p,basename(__FILE__))) {
            $r++;
            if ($r > 1) {
                echo "too many instances, exiting\n";
                exit();
            }
        }
    }
}

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

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

0 голосов
/ 08 июля 2014

Это сработало для меня.Установите запись базы данных с флагом блокировки и отметкой времени.Мой сценарий должен завершиться в течение 15 минут, поэтому добавил, что в качестве последнего заблокированного поля необходимо проверить:

       $lockresult = mysql_query("
       SELECT *
       FROM queue_locks
       WHERE `lastlocked` > DATE_SUB(NOW() , INTERVAL 15 MINUTE)    
       AND `locked` = 'yes'
       AND `queid` = '1'
       LIMIT 1
        "); 
$LockedRowCount = mysql_num_rows($lockresult);

if($LockedRowCount>0){
    echo "this script is locked, try again later";
    exit;   
}else{
//Set the DB record to locked and carry on son
$result = mysql_query("
            UPDATE `queue_locks` SET `locked` = 'yes', `lastlocked` = CURRENT_TIMESTAMP WHERE `queid` = 1;
            ");

}

Затем разблокировать его в конце сценария:

    $result = mysql_query("UPDATE `queue_locks` SET `locked` = 'no' WHERE `queid` = 1;");
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...