1
2

More than 3 years have passed since last update.

PHPでバッチの二重起動防止処理を実装する

Posted at

バッチの二重起動防止処理

バッチをcronで定期的に実行する場合、二重防止処理を入れていないと何らかの理由でバッチの起動時間が延びた場合に、バッチプロセスが複数溜まり続けてしまう。

実装方針

  • pidファイルを作成し、ファイルにはプロセスIDを記録するようにする。
  • 異常終了したときや、Ctrl+Cで終了したときはpidファイルが残り続けないようにする。

実装内容

PIDを取得するためにphp-processが必要。

yum install php-process
class なんたらバッチ {
    const PID_PATH = '/var/run/hogehoge/hogebatch.pid';

    public function __construct() {
        // 既に同一タスクが起動中の場合
        if (file_exists(static::PID_PATH)) {
            exit(1);
        }
        file_put_contents(static::PID_PATH, posix_getpid());

        // Ctrl+C等で中断されたときにメッセージを表示し、pidファイルが削除されるようにする
        pcntl_async_signals(true);
        pcntl_signal(SIGINT, function() {
            exit;
        });
    }

    public function __destruct() {
        unlink(static::PID_PATH);
    }
}

FuelPHPのTaskクラスでの実装例

<?php

/**
 * タスクの基底クラス
 */
abstract class Task {

    /**
     * @var boolean タスクを実行しているか
     */
    protected $running = false;

    /**
     * タスクの事前処理を行う。
     */
    public function __construct() {
        // 既に同一タスクが起動中の場合
        if ($this->is_running()) {
            // ログとコンソールに出力し、終了する
            $message = get_class($this) . ' already running';
            \Log::warning($message);
            \Cli::error($message);
            exit(1);
        }

        // 実行状態にして開始ログを出す
        $this->running = true;
        $this->log_start_message();
        $this->set_running();

        // Ctrl+C等で中断されたときにメッセージを表示し、pidファイルが削除されるようにする
        pcntl_async_signals(true);
        pcntl_signal(SIGINT, function() {
            \Log::warning(get_class($this) . ' halted');
            exit;
        });
    }

    /**
     * タスクの事後処理を行う。
     */
    public function __destruct() {
        // 終了ログを出す
        $this->log_finish_message();
        // 実行を行っていた場合はpidファイルを削除
        if ($this->running) {
            $this->set_running(false);
        }
    }

    /**
     * タスクが既に起動中かどうかを返す。
     *
     * @return boolean タスクが起動中か
     */
    protected function is_running() {
        $filename = $this->get_pid_filename();
        // PIDファイル名を設定していない場合はチェックをしない
        if ($filename === null) {
            return false;
        }
        // PIDファイルがあるかチェック
        return file_exists(\Config::get('run_dir') . "$filename.pid");
    }

    /**
     * PIDファイルを生成もしくは削除してタスクの起動状態を切り替える。
     *
     * @param boolean $running 起動中にするか
     */
    protected function set_running($running = true) {
        $filename = $this->get_pid_filename();
        // PIDファイル名を設定していない場合は何もしない
        if ($filename === null) {
            return;
        }

        $pid = \Config::get('run_dir') . "$filename.pid";
        // 起動中にする場合はPIDファイルを生成
        if ($running) {
            file_put_contents($pid, posix_getpid());
        // 終了にする場合はPIDファイルを削除
        } elseif (file_exists($pid)) {
            unlink($pid);
        }
    }

    /**
     * 開始メッセージをログに出力する。
     */
    protected function log_start_message() {
        \Log::warning(get_class($this) . ' task start');
    }

    /**
     * 終了メッセージをログに出力する。
     */
    protected function log_finish_message() {
        $time = number_format(microtime(true) - FUEL_START_TIME, 4);
        \Log::warning(get_class($this) . " task finish [execution time: $time sec.]");
    }

    /**
     * このタスクのPIDファイル名を返す。
     *
     * @return string PIDファイル名 (拡張子部分は除く)
     */
    protected function get_pid_filename() {
        return preg_replace('/^.*\\\\/u', '', strtolower(get_class($this)));
    }
}

こんなクラスを用意しておけば、Task作成時にこのクラスを継承するだけで二重起動防止処理が実装され、
バッチ単位で別々のpidファイルを自動的に生成してくれる。

1
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
2