web-dev-qa-db-ja.com

同時に実行されるスクリプトの重複を防ぐ

スクレイピーを使用していくつかのリソースをフェッチしていますが、30分ごとに開始できるcronジョブとして作成したいです。

Cron:

0,30 * * * * /home/us/jobs/run_scrapy.sh`

run_scrapy.sh:

#!/bin/sh
cd ~/spiders/goods
PATH=$PATH:/usr/local/bin
export PATH
pkill -f $(pgrep run_scrapy.sh | grep -v $$)
sleep 2s
scrapy crawl good

スクリプトが示したように、スクリプトプロセスと子プロセス(スクレイピー)も強制終了しようとしました。

ただし、2つのスクリプトを実行しようとしたときに、スクリプトの新しいインスタンスが古いインスタンスを強制終了することはありません。

それを修正する方法は?


更新:

cronで設定された異なる周波数で実行される複数の.shスクレイピースクリプトがあります。


更新2-Sergの回答のテスト:

テストを実行する前に、すべてのcronジョブが停止しました。

次に、w1、w2、w3という名前の3つのターミナルウィンドウを開き、次の順序でコマンドを実行します。

Run `pgrep scrapy` in w3, which print none.(means no scrapy running at the moment).

Run `./scrapy_wrapper.sh` in w1

Run `pgrep scrapy` in w3 which print one process id say it is `1234`(means scrapy have been started by the script)

Run `./scrapy_wrapper.sh` in w2 #check the w1 and found the script have been terminated.

Run `pgrep scrapy` in w3 which print two process id `1234` and `5678`

Press `Ctrl+C` in w2(twice)

Run `pgrep scrapy` in w3 which print one process id `1234` (means scrapy of `5678` have been stopped)

現時点では、pkill scrapyのIDでスクレイピーを停止するには1234を使用する必要があります

5
hguser

より良いアプローチは、メインスクリプトを呼び出すラッパースクリプトを使用することです。これは次のようになります。

#!/bin/bash
# This is /home/user/bin/wrapper.sh file
pkill -f 'main_script.sh'
exec bash ./main_script.sh

もちろん、ラッパーには別の名前を付ける必要があります。そうすれば、pkillはメインスクリプトのみを検索できます。これにより、メインスクリプトは次のようになります。

#!/bin/sh
cd /home/user/spiders/goods
PATH=$PATH:/usr/local/bin
export PATH
scrapy crawl good

この例では、スクリプトが現在の作業ディレクトリにあるため、./を使用していることに注意してください。最良の結果を得るには、スクリプトへのフルパスを使用してください

このアプローチは、無限whileループとラッパースクリプトを実行するだけの単純なメインスクリプトでテストしました。スクリーンショットでわかるように、ラッパーの2番目のインスタンスを起動すると、以前の

enter image description here

スクリプト

これは単なる例です。私は実際にこれをテストするためにスクレイピーにアクセスできないので、状況に応じてこれを調整してください。

Cronエントリは次のようになります。

0,30 * * * * /home/us/jobs/scrapy_wrapper.sh

scrapy_wrapper.shの内容

#!/bin/bash
pkill -f 'run_scrapy.sh'
exec sh /home/us/jobs/run_scrapy.sh

run_scrapy.shの内容

#!/bin/bash
cd /home/user/spiders/goods
PATH=$PATH:/usr/local/bin
export PATH
# sleep delay now is not necessary
# but uncomment if you think it is
# sleep 2
scrapy crawl good
9

親シェルスクリプトpidファイルを作成してスクリプトが実行されているかどうかを監視し、pidファイルをチェックして以前実行中の親シェルスクリプトを強制終了することをお勧めします。そんな感じ

#!/bin/sh
PATH=$PATH:/usr/local/bin
PIDFILE=/var/run/scrappy.pid
TIMEOUT="10s"

#Check if script pid file exists and kill process
if [ -f "$PIDFILE" ]
then
  PID=$(cat $PIDFILE)
  #Check if process id is valid
  ps -p $PID >/dev/null 2>&1
  if [ "$?" -eq "0" ]
  then
    #If it is valid kill process id
    kill "$PID"
    #Wait for timeout
    sleep "$TIMEOUT"
    #Check if process is still running after timeout
    ps -p $PID >/dev/null 2>&1
    if [ "$?" -eq "0" ]
    then
      echo "ERROR: Process is still running"
      exit 1
    fi
  fi 
fi

#Create PID file
echo $$ > $PIDFILE
if [ "$?" -ne "0" ]
then
  echo "ERROR: Could not create PID file"
  exit 1
fi

export PATH
cd ~/spiders/goods
scrapy crawl good
#Delete PID file
rm "$PIDFILE"
2
iuuuuan

私があなたが正しくしていることを理解しているなら、あなたは30分ごとに(cron経由で)プロセスを呼び出したいです。ただし、cronを介して新しいプロセスを開始するとき、まだ実行中の既存のバージョンを強制終了したいですか?

「timeout」コマンドを使用して、30分後に実行中の場合、scrappyが強制的に終了するようにすることができます。

これにより、スクリプトは次のようになります。

#!/bin/sh
cd ~/spiders/goods
PATH=$PATH:/usr/local/bin
export PATH
timeout 30m scrapy crawl good

最後の行に追加されたタイムアウトに注意してください

期間を「30m」(30分)に設定しました。次のジョブが開始される前にプロセスが確実に終了するように、少し短い時間(29mなど)を選択することもできます。

Crontabで生成間隔を変更する場合は、スクリプトも編集する必要があることに注意してください

2
Nick Sillito

pkillは指定されたプロセスのみを終了するため、-Pオプションを使用してその子サブプロセスを終了する必要があります。したがって、変更されたスクリプトは次のようになります。

#!/bin/sh

cd /home/USERNAME/spiders/goods
PATH=$PATH:/usr/local/bin
export PATH
PID=$(pgrep -o run_scrapy.sh)
if [ $$ -ne $PID ] ; then pkill -P $PID ; sleep 2s ; fi
scrapy crawl good

trapは、イベントEXIT、つまりrun_scrapy.shが終了したときに、定義されたコマンドを(二重引用符で)実行します。他にもイベントがあります。help trapにあります。
pgrep -oは、定義された名前を持つプロセスの最も古いインスタンスを見つけます。

PSgrep -v $$を使用したアイデアは優れていますが、run_scrapy.shの他のインスタンスのPIDは返されません。 $$は、サブプロセスのPID $(pgrep run_scrapy.sh | grep -v $$)であり、それを開始したrun_scrapy.shのPIDではありません。それが私が別のアプローチを使用した理由です。
P.P.S。Bash here には、サブプロセスを終了する他の方法がいくつかあります。

1
whtyger

まあ、私はpopen()を使用してCで同様の問題があり、タイムアウトの親とすべての子の後に殺すのが好きです。トリックは、自分自身を殺さないように親を開始するときにプロセスグループIDを設定することです。これを行う方法はここで読むことができます: https://stackoverflow.com/questions/6549663/how-to-set-process-group-of-a-Shell-script with "ps -eo pid、ppid、cmd、etime "ランタイムに沿ってフィルタリングできます。したがって、両方の情報を使用すると、すべての古いプロセスをフィルタリングして、それらを強制終了できるはずです。

0
0x0C4

環境変数をチェックしてスクリプトのステータスを追跡し、スクリプト開始時に適切に設定して、次のような擬似コードを作成できます。

if "$SSS" = "Idle"
then 
    set $SSS=Running"
    your script
    set $SSS="Idle"

touch /pathname/myscript.is.runningなどのマーカーファイルを作成/確認/削除し、起動時に存在する場合はrm /pathname/myscript.is.runningを使用して、ステータスを追跡することもできます。

このアプローチにより、スクレイピースクリプトごとに異なる識別子を使用して、間違ったスクリプトを殺すことを防ぐことができます。

スクリプトの状態をどのように追跡するか、起動の防止または実行中のプロセスの強制終了によって問題に対処するかどうかに関係なく、@ JacobVlijmおよび@Sergが示唆するラッパースクリプトを使用することで、作業がはるかに楽になると思います。

0
Elder Geek