web-dev-qa-db-ja.com

複数のワーカーを実行しているピラミッドWebアプリで1人のワーカーのみがapschedulerイベントを起動することを確認してください

ピラミッドで作成され、gunicorn + nginxを介して提供されるWebアプリがあります。 8つのワーカースレッド/プロセスで動作します

私たちは仕事をする必要がありました、私たちはapschedulerを選びました。これが私たちがそれを起動する方法です

from apscheduler.events import EVENT_JOB_EXECUTED, EVENT_JOB_ERROR
from apscheduler.scheduler import Scheduler

rerun_monitor = Scheduler()
rerun_monitor.start()
rerun_monitor.add_interval_job(job_to_be_run,\
            seconds=JOB_INTERVAL)

問題は、gunicornのすべてのワーカープロセスがスケジューラーを取得することです。ファイルロックを実装しようとしましたが、十分な解決策ではないようです。常に、ワーカープロセスの1つだけがスケジュールされたイベントを取得し、他のスレッドが次のJOB_INTERVALまでそれを取得しないようにするための最良の方法は何でしょうか?

後でApache2 + modwsgiに切り替えることにした場合に備えて、このソリューションはmod_wsgiでも機能する必要があります。ウェイトレスである単一プロセス開発サーバーで動作する必要があります。

バウンティスポンサーからの更新

OPで説明されているのと同じ問題に直面していますが、Djangoアプリです。元の質問の場合、この詳細を追加してもそれほど変わらないと確信しています。このため、可視性をもう少し高めるために、この質問にもDjangoのタグを付けました。

29

Gunicornは(あなたの例では)8人のワーカーで開始しているため、このforksアプリは8回8つのプロセスになります。これらの8つのプロセスは、Masterプロセスから分岐します。このプロセスは、各ステータスを監視し、ワーカーを追加/削除する機能を備えています。

各プロセスは、APSchedulerオブジェクトのコピーを取得します。これは、最初はマスタープロセスのAPSchedulerの正確なコピーです。これにより、各「n番目」のワーカー(プロセス)が各ジョブを合計「n」回実行します。

これを回避するハックは、次のオプションを使用してgunicornを実行することです。

_env/bin/gunicorn module_containing_app:app -b 0.0.0.0:8080 --workers 3 --preload
_

_--preload_フラグは、Gunicornに「ワーカープロセスをフォークする前にアプリをロードする」ように指示します。そうすることで、各ワーカーは「アプリ自体をインスタンス化するのではなく、マスターによってすでにインスタンス化されたアプリのコピーを与えられます」。これは、次のコードがマスタープロセスで1回だけ実行されることを意味します。

_rerun_monitor = Scheduler()
rerun_monitor.start()
rerun_monitor.add_interval_job(job_to_be_run,\
            seconds=JOB_INTERVAL)
_

さらに、jobstore:memory:以外に設定する必要があります。この方法では、各ワーカーは他の7と通信できない独自の独立したプロセスですが、 (メモリではなく)ローカルデータベースを使用することで、ジョブストアでのCRUD操作の信頼できる唯一の情報源を保証します。

_from apscheduler.schedulers.background import BackgroundScheduler
from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore

rerun_monitor = Scheduler(
    jobstores={'default': SQLAlchemyJobStore(url='sqlite:///jobs.sqlite')})
rerun_monitor.start()
rerun_monitor.add_interval_job(job_to_be_run,\
            seconds=JOB_INTERVAL)
_

最後に、start()が実装されているため、BackgroundSchedulerを使用します。 BackgroundSchedulerでstart()を呼び出すと、ジョブのスケジューリング/実行を担当する新しいスレッドがバックグラウンドでスピンアップされます。手順(1)で、_--preload_フラグがあるため、マスターGunicornプロセスでstart()関数を1回だけ実行することを覚えておいてください。定義上、フォークされたプロセスは親のスレッドを継承しませんしたがって、各ワーカーはBackgroundSchedulerスレッドを実行しません。

_from apscheduler.jobstores.sqlalchemy import SQLAlchemyJobStore

rerun_monitor = BackgroundScheduler(
    jobstores={'default': SQLAlchemyJobStore(url='sqlite:///jobs.sqlite')})
rerun_monitor.start()
rerun_monitor.add_interval_job(job_to_be_run,\
            seconds=JOB_INTERVAL)
_

これらすべての結果として、すべてのGunicornワーカーには「STARTED」状態にだまされたAPSchedulerがありますが、親のスレッドをドロップするため、実際には実行されていません。各インスタンスは、ジョブを実行せずに、ジョブストアデータベースを更新することもできます。

flask-APScheduler をチェックして、Webサーバー(Gunicornなど)でAPSchedulerを実行し、各ジョブのCRUD操作を有効にする簡単な方法を確認してください。

19
The Aelfinn

非常によく似た問題があるDjangoプロジェクトで機能する修正を見つけました。スケジューラーが最初に起動したときにTCPソケットをバインドし、それをチェックするだけです。次のコードは、微調整を加えるだけでも機能すると思います。

import sys, socket

try:
    sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    sock.bind(("127.0.0.1", 47200))
except socket.error:
    print "!!!scheduler already started, DO NOTHING"
else:
    from apscheduler.schedulers.background import BackgroundScheduler
    scheduler = BackgroundScheduler()
    scheduler.start()
    print "scheduler started"
13
Paolo