実行中のcronジョブの重複を防ぐ


92

cronジョブを1分ごとに実行するようにスケジュールしましたが、スクリプトが完了するまでに1分以上かかることがあり、ジョブがお互いに「積み重なって」起動したくない場合があります。これは並行性の問題だと思います。つまり、スクリプトの実行は相互に排他的である必要があります。

この問題を解決するために、スクリプトに特定のファイル( " lockfile.txt ")の存在を検索させ、存在する場合は存在touchしない場合は終了します。しかし、これはかなりお粗末なセマフォです!知っておくべきベストプラクティスはありますか?代わりにデーモンを作成する必要がありますか?

回答:


118

この機能を自動化し、迷惑や潜在的なバグを自分でやることを防ぎ、舞台裏で群れを使用することで古いロックの問題を回避するプログラムがいくつかあります(タッチだけを使用している場合はリスクです) 。私が使ってきたlockrunし、lckdo過去に、今がありますflock素晴らしいです(utilの-linuxの新しめのバージョンで)(1)。使い方はとても簡単です。

* * * * * /usr/bin/flock -n /tmp/fcj.lockfile /usr/local/bin/frequent_cron_job

2
fck(1)がutil-linuxに含まれるようになったため、lckdoはmoreutilsから削除されます。そして、そのパッケージはLinuxシステムでは基本的に必須であるため、その存在に依存できるはずです。使用方法については、以下をご覧ください。
jldugger

ええ、群れは私の優先オプションです。私も答えを合わせて更新します。
ワンブル

誰もが違い知っていますflock -n file commandとのflock -n file -c command
ナネ

2
@Nanne、私は確かにコードをチェックする必要があると思いますが、私の推測はつまり-c、「裸の」(非ながら、(manページあたりのように)シェルを介して指定されたコマンドを実行します-c)フォームはただexec与えられたコマンドがね。シェルを介して何かを入力すると、シェルのようなこと(;またはで区切られた複数のコマンドを実行するなど&&)を行うことができますが、信頼できない入力を使用している場合はシェル拡張攻撃も可能になります。
ワンブル

1
それはfrequent_cron_job毎分実行されていることを表示しようとする(仮想の)コマンドへの引数でした。有用なものは何も追加せず、混乱を引き起こしました(長年にわたって誰もいない場合はあなたのものです)ので、私はそれを削除しました。
ウォンブル

28

シェルでの最良の方法は、flock(1)を使用することです

(
  flock -x -w 5 99
  ## Do your stuff here
) 99>/path/to/my.lock

1
fdリダイレクトのトリッキーな使用を支持することはできません。それは非常に驚くほど素晴らしいです。
ワンブル

1
bashやZSHに私のために解析されない、との間のスペースを排除する必要がある99>それがある99> /...
カイル・ブラント

2
@Javier:それはトリッキーで難解ではないという意味ではなく、文書化され、トリッキーで、不可解であるというだけです。
ウォンブル

1
これの実行中に再起動したり、プロセスを何らかの方法で強制終了した場合はどうなりますか?それは永遠にロックされますか?
アレックスR 14

5
私はこの構造が排他的ロックを作成することを理解していますが、これがどのように達成されるかのメカニズムを理解していません。この回答の「99」の機能は何ですか?これを説明したい人はいますか?ありがとう!
Asciiom

22

実際にflock -nは、lckdo*の代わりに使用される可能性があるため、カーネル開発者のコ​​ードを使用します。

wombleの例基づいて、次のように記述します。

* * * * * flock -n /some/lockfile command_to_run_every_minute

ところで、すべてのコードを見てflocklockrunと、lckdoそれだけであなたに最も容易に利用可能となっている問題ですので、まったく同じことを行います。


2

ロックファイルを使用できます。スクリプトの開始時にこのファイルを作成し、終了時に削除します。スクリプトは、メインルーチンを実行する前に、ロックファイルが存在するかどうかを確認し、それに応じて続行する必要があります。

ロックファイルは、initscriptおよびUnixシステムの他の多くのアプリケーションとユーティリティで使用されます。


1
これは私がこれまでに実装したのを見た唯一の方法です。私はOSSプロジェクトのミラーとしてメンテナーの提案に従って使用します
ウォーレン

2

前の実行が完了するまでスクリプトを待機させるかどうかを指定していません。「ジョブが互いに「積み重なる」ことを望まない」ということは、すでに実行されている場合はスクリプトを終了することを意味していると思います。

そのため、lckdoなどに依存したくない場合は、次のようにできます。


PIDFILE=/tmp/`basename $0`.pid

if [ -f $PIDFILE ]; then
  if ps -p `cat $PIDFILE` > /dev/null 2>&1; then
      echo "$0 already running!"
      exit
  fi
fi
echo $$ > $PIDFILE

trap 'rm -f "$PIDFILE" >/dev/null 2>&1' EXIT HUP KILL INT QUIT TERM

# do the work


あなたの例が参考になったことに感謝します-すでに実行されている場合はスクリプトを終了したいです ickdoに言及してくれてありがとう-トリックをしているようです。
トム

FWIW:このソリューションは、スクリプトに含めることができるため、スクリプトが呼び出される方法に関係なくロックが機能するため、気に入っています。
デビッドG

1

これは、あなたが間違ったことをしていることの表れかもしれません。ジョブがそれだけ頻繁に頻繁に実行される場合は、クローンを解除してデーモン形式のプログラムにすることを検討する必要があります。


3
私はこれに心から反対します。定期的に実行する必要があるものがある場合、それをデーモンにすることは「ナットの大ハンマー」ソリューションです。事故を防ぐためにロックファイルを使用することは、私が使用する上で問題がなかった完全に合理的なソリューションです。
ワンブル

@womble同意します。しかし、私はハンマーでナッツを粉砕するのが好きです!:-)
wzzrd 2009年

1

cronデーモンは、それらの以前のインスタンスがまだ実行されている場合、ジョブを呼び出してはなりません。私は1つのcronデーモンdcronの開発者であり、特にそれを防止しようとしています。Vixie cronまたは他のデーモンがこれをどのように処理するかわかりません。


1

run-oneコマンドを使用することをお勧めします -ロックを処理するよりもはるかに簡単です。ドキュメントから:

run-oneは、一意の引数セットを持つコマンドの一意のインスタンスを1つだけ実行するラッパースクリプトです。これは、一度に複数のコピーを実行したくない場合に、cronジョブでしばしば役立ちます。

run-this-onerun-oneとまったく同じです。ただし、pgrepとkillを使用して、ユーザーが所有し、ターゲットコマンドと引数に一致する実行中のプロセスを見つけて強制終了します。run-this-oneは、一致するすべてのプロセスが停止するまで、一致するプロセスを強制終了しようとしてブロックすることに注意してください。

run-one-constantlyrun-oneとまったく同じように動作しますが、COMMANDが終了するたびに「COMMAND [ARGS]」が再生成されます(ゼロまたは非ゼロ)。

keep-one-runningはrun-one-constantlyのエイリアスです。

run-one-until-successは、COMMANDが正常に終了する(つまりゼロを終了する)まで "COMMAND [ARGS]"を再生成することを除いて、run-one-constantlyとまったく同じように動作します。

run-one-until-failureはrun-one-constantlyとまったく同じように動作しますが、COMMANDが失敗して終了する(つまり、ゼロ以外で終了する)まで「COMMAND [ARGS]」が再生成されます。


1

systemdがリリースされたので、Linuxシステムには別のスケジューリングメカニズムがあります。

A systemd.timer

/etc/systemd/system/myjob.serviceまたは~/.config/systemd/user/myjob.service

[Service]
ExecStart=/usr/local/bin/myjob

/etc/systemd/system/myjob.timerまたは~/.config/systemd/user/myjob.timer

[Timer]
OnCalendar=minutely

[Install]
WantedBy=timers.target

タイマーが次にアクティブになったときにサービスユニットがすでにアクティブになっている場合、サービスの別のインスタンスは開始されません。

起動時にジョブを開始し、各実行が終了してから1分後にジョブを開始する代替方法:

[Timer]
OnBootSec=1m
OnUnitInactiveSec=1m 

[Install]
WantedBy=timers.target

0

重複したcronが実行されているなどの問題を解決するために1つのjarを作成しました。これは、javaまたはシェルcronです。Duplicates.CloseSessions( "Demo.jar")にcron名を渡すだけで、現在のcronを除き、このcronの既存のpidを検索して削除できます。このようなことをするメソッドを実装しました。文字列proname = ManagementFactory.getRuntimeMXBean()。getName(); 文字列pid = proname.split( "@")[0]; System.out.println( "現在のPID:" + pid);

            Process proc = Runtime.getRuntime().exec(new String[]{"bash","-c"," ps aux | grep "+cronname+" | awk '{print $2}' "});

            BufferedReader stdInput = new BufferedReader(new InputStreamReader(proc.getInputStream()));
            String s = null;
            String killid="";

            while ((s = stdInput.readLine()) != null ) {                                        
                if(s.equals(pid)==false)
                {
                    killid=killid+s+" ";    
                }
            }

そして、再びシェルコマンドでkillid文字列を殺します


これは本当に質問に答えているとは思わない。
カスペルド16

0

@Philip Reynoldsの回答は、ロックを取得せずに5秒間待機した後、コードの実行を開始します。次のFlockが機能していないようです @Philip Reynoldsの回答を修正しました

(
  flock -w 5 -x 99 || exit 1
  ## Do your stuff here
) 99>/path/to/my.lock

そのため、コードが同時に実行されることはありません。代わりに、5秒待機した後、それまでにロックを取得しなかった場合、プロセスは1で終了します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.