スクリプトの重複起動禁止制御を行いたい。
crontabで、定期的にスクリプトを実行することは多々ある。
複数のスクリプトがそれぞれ、数分から数十分の間隔でスケジュールされている場合、
一つのスクリプトの処理が完了しない間に、別のスクリプトが処理されてしまう。
例えば次のような設定では、30分毎に動作するタイミングが重なってしまう。
*/1 * * * * /root/Scripts/scriptA.sh
*/30 * * * * /root/Scripts/scriptB.sh
そしてそれらが同じタイミングで実行されてはいけないものであれば不具合が発生する。
このような問題を避けるには、これらが同時実行されないように排他処理を行う必要がある。
後に挙げた「排他的実行スクリプト(RunScriptAlone.sh)」を作成することで排他処理を実現できた。
■「排他的実行スクリプト(RunScriptAlone.sh)」の概要
(スクリプト本体は次の項に挙げている。)
次の特徴がある。
○汎用的に使える
考慮した点は、汎用的に動作することである。
即ち、排他的に動作させたいユーザースクリプトを一つ目の引数に指定するだけで良い。
既存のユーザースクリプトには一切手を加える必要がない。
(指定例)
# /root/Scripts/RunScriptAlone.sh /root/scriptA.sh
したがって、crontab -e で次のように指定しておけば、
ユーザースクリプト( /root/scriptA.sh )と、
ユーザースクリプト( /root/scriptB.sh )は排他的に実行される。
(タイミングが重なっても、一方の処理が完了するのを待って他方は実行される。)
*/1 * * * * /root/Scripts/RunScriptAlone.sh /root/scriptA.shこれは、次に挙げている「検出モード 1」を利用している。
*/30 * * * * /root/Scripts/RunScriptAlone.sh /root/scriptB.sh
○排他効果を及ぼす範囲を自由に決定できる。
どのユーザースクリプトと、どのユーザースクリプトを排他的に実行するかを決められる。
この仕組みを、二通りの方法で実現した。
(検出モード 1)デフォルト
一つは、第一引数に指定されたユーザースクリプトを排他的に動作させるために、
「排他的実行スクリプト」自身の実行パスをプロセスの検索キーとして用いる。
これにより、同じ実行パスがプロセス一覧に存在すれば待機することになる。
これがデフォルト動作である。上記に挙げた例はこれを用いている。
これを応用すれば、互いに排他的に動作させたいユーザースクリプトのグループごとに、
それぞれ「排他的実行スクリプト」を異なるパスに用意すればいい。
「排他的実行スクリプト」自身の実行パスをプロセスの検索キーなので、排他的に動作させるべき関係を区別できる。
crontabには次のように指定すればよい。
*/1 * * * * /root/group1/RunScriptAlone.sh /root/group1/scriptA.sh
*/10 * * * * /root/group1/RunScriptAlone.sh /root/group1/scriptB.sh
*/20 * * * * /root/group2/RunScriptAlone.sh /root/group2/scriptC.sh
*/30 * * * * /root/group2/RunScriptAlone.sh /root/group2/scriptD.sh
ユーザースクリプト /root/group1/scriptA.sh と、/root/group1/scriptB.sh は、
同じ実行パス(/root/group1/RunScriptAlone.sh)の「排他的実行スクリプト」から起動されている。
そのため、この二つのユーザースクリプトは互いに排他的に起動される。
同様に、
ユーザースクリプト /root/group2/scriptC.sh と、/root/group2/scriptD.sh は、
同じ実行パス(/root/group2/RunScriptAlone.sh)の「排他的実行スクリプト」から起動されている。
そのため、この二つのユーザースクリプトは互いに排他的に起動される。
(検出モード 2)
もう一つは、第二引数に任意の文字列を指定して、プロセスの検索キーとして用いる。
これを指定すると、さきの「排他実行スクリプト」の実行パスに関わらず、
排他的に起動すべきユーザースクリプトをグループ化できる。
(指定例)
# /root/Scripts/RunScriptAlone.sh /root/scriptA.sh qawsedrftgyhujikolpcrontabには次のように指定することになる。
*/1 * * * * /root/ABC/RunScriptAlone.sh /root/GHI/scriptA.sh qawsedrftgyhujikolpユーザースクリプト /root/GHI/scriptA.sh と、/root/JKL/scriptB.sh は、
*/30 * * * * /root/DEF/RunScriptAlone.sh /root/JKL/scriptB.sh qawsedrftgyhujikolp
同じ検索キー「qawsedrftgyhujikolp」が指定されているため、排他的に実行される。
(タイミングが重なっても、一方の処理が完了するのを待って他方は実行される。)
■「排他的実行スクリプト(RunScriptAlone.sh)」
自作の荒削りで下手糞なスクリプトだが、一応上記の機能は果たせている。
これを用いて何か問題が生じても、一切責任を取りません。
内容を読んで理解できる人のみお試しください。
動作は、CentOS 6.9で確認しています。
こんなスクリプトだけど、自分で作成したものなので、
利用される方はこのサイトを出典としてあげてください。お願いします。
# cat RunScriptAlone.sh
#!/bin/bash
#LOGFILE=/tmp/RunScriptAlone.log
LOGFILE=/dev/null
#引数1(必須)排他的に実行するユーザースクリプトパス
#引数2(オプション)明示的に指定するプロセス検索キー
#(引数2の指定がなければこの制御スクリプトの実行パスが検索キーとして使用される)
KEY=${2:-$0}
echo "$KEY as Key for pgrep" >> $LOGFILE
echo "PPID is $PPID" >> $LOGFILE
echo "PID is $$" >> $LOGFILE
cmdline=`/bin/cat /proc/$$/cmdline`
echo "/proc/$$/cmdline is $cmdline" >> $LOGFILE
#echo $1 >> $LOGFILE
#ここから、判定ルーチン
while :
do
# コマンドラインで指定したパス名 $KEY を検索キーにして、既に動作中のプロセスを検索し、最も古いプロセスのIDを取得する。
psnum=`pgrep -fo "$KEY"`
echo "The oldest PID derived from $KEY is $psnum" >> $LOGFILE
# 取得した最古のプロセスIDと、自身のプロセスIDとを比較する。
# 一致しなければ、同一パス名 $KEY で呼び出されたプロセスが他に既に存在しているので待機する。
if [ "$$" != "$psnum" ]
then
echo "no" >> $LOGFILE # 既に同じパスで指定されるスクリプトは起動中である。(二重起動防止のため待機/次のコメントを外すと待機せず中断)
#exit 1
echo "Now Waiting..." >> $LOGFILE
sleep 10
else
break
fi
done
echo "ok" >> $LOGFILE #同じパスで指定されるスクリプトは起動していない。(さらに処理を継続)
#ここまで
#echo "pgrep -fo $KEY is $psnum" >> $LOGFILE
echo `date` "Starting Script $1" >> $LOGFILE
source $1
以上