因为Gunicorn从8个工作进程开始(在您的示例中),所以这forks个应用程序在8个进程中执行8次.这8个进程是从Master进程派生出来的,Master进程监视每个进程的状态,并且能够添加/删除工作进程.
每个进程都会获得APScheduler对象的副本,该副本最初是主进程的APScheduler的精确副本.这导致每个"第n"个工作者(进程)总共执行每个作业(job)"n"次.
解决此问题的一种方法是使用以下选项运行Gunicorn:
env/bin/gunicorn module_containing_app:app -b 0.0.0.0:8080 --workers 3 --preload
--preload
旗告诉Gunicorn是"load the app before forking the worker processes".这样一来,每个工人就是"given a copy of the app, already instantiated by the Master, rather than instantiating the app itself"人.这意味着以下代码在主进程中只执行一次:
rerun_monitor = Scheduler()
rerun_monitor.start()
rerun_monitor.add_interval_job(job_to_be_run,\
seconds=JOB_INTERVAL)
此外,我们需要将jobstore设置为:memory:以外的任何值.通过这种方式,尽管每个工作进程都是自己的独立进程,无法与其他7个进程通信,但通过使用本地数据库(而不是内存),我们保证了jobstore上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)
最后,我们希望使用BackgroundScheduler,因为它实现了start()
.当我们在Backround Scheduler中调用start()
时,会在后台启动一个新线程,负责调度/执行作业(job).这一点很重要,因为请记住,在步骤(1)中,由于我们的--preload
标志,我们在Master Gunicorn进程中只执行一次start()
函数.根据定义,为forked processes do not inherit the threads of their Parent,,这样每个工作线程就不会运行Background Scheduler线程.
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 worker都有一个APScheduler,它被诱骗到"启动"状态,但实际上没有运行,因为它会删除其父线程!每个实例还能够更新jobstore数据库,只是不执行任何作业(job)!
查看flask-APScheduler,了解在web服务器(如Gunicorn)中运行APScheduler并 for each 作业(job)启用CRUD操作的快速方法.