Gunicorn は 8 つのワーカーで開始されるため (この例では)、これはアプリを 8 回フォークして 8 つのプロセスにします。これらの 8 つのプロセスはマスタープロセスからフォークされ、各プロセスのステータスを監視し、ワーカーを追加/削除する機能を備えています。
各プロセスは、最初はマスター プロセスの APScheduler の正確なコピーである APScheduler オブジェクトのコピーを取得します。これにより、「n 番目」の各ワーカー (プロセス) が各ジョブを合計で「n」回実行することになります。
これを回避するハックは、以下のオプションで gunicorn を実行することです:
env/bin/gunicorn module_containing_app:app -b 0.0.0.0:8080 --workers 3 --preload
この--preload
フラグは、Gunicorn に「ワーカー プロセスを fork する前にアプリをロードする」ように指示します。そうすることで、各ワーカーには「アプリ自体をインスタンス化するのではなく、マスターによって既にインスタンス化されたアプリのコピーが与えられます」。これは、次のコードがマスター プロセスで 1 回だけ実行されることを意味します。
rerun_monitor = Scheduler()
rerun_monitor.start()
rerun_monitor.add_interval_job(job_to_be_run,\
seconds=JOB_INTERVAL)
さらに、ジョブストアを :memory: 以外のものに設定する必要があります。このように、各ワーカーは他の 7 つのワーカーと通信できない独自の独立したプロセスですが、(メモリではなく) ローカル データベースを使用することで、単一のワーカーを保証します。ジョブストアでの CRUD 操作の -point-of-truth。
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)
最後に、 BackgroundSchedulerの実装により、BackgroundScheduler を使用したいと考えていますstart()
。BackgroundSchedulerを呼び出すstart()
と、新しいスレッドがバックグラウンドで起動され、ジョブのスケジューリング/実行を担当します。これは重要です。なぜなら、ステップ (1) で覚えているように、--preload
フラグによりstart()
、Master Gunicorn プロセスで関数を 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 を持っていますが、親のスレッドをドロップするため、実際には実行されていません! 各インスタンスは、ジョブを実行するだけでなく、ジョブストア データベースを更新することもできます。
APSchedulerを Web サーバー (Gunicorn など) で実行し、各ジョブの CRUD 操作を有効にする簡単な方法については、flask-APSchedulerを確認してください。