一尘不染

确保只有一个工作进程在运行多个工作进程的金字塔web应用程序中启动apscheduler事件

django

我们有一个使用金字塔制作的网络应用程序,并通过gunicorn + nginx提供服务。它与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的员工进程都在调度程序。我们尝试实现文件锁,但它似乎还不够好。什么是确保在任何给定时间只有一个辅助进程拾取预定事件而没有其他线程拾取它的最佳方法是JOB_INTERVAL什么?

该解决方案甚至需要与mod_wsgi配合使用,以防我们稍后决定切换到apache2 + modwsgi。它需要与作为服务员的单个流程开发服务器一起使用。

更新
仅使用Django应用程序,我就面临OP描述的相同问题。我最确定的是,如果原始问题添加此细节不会有太大变化。因此,为了获得更多可见性,我还用标记了这个问题django。


阅读 554

收藏
2020-04-02

共1个答案

一尘不染

由于Gunicorn开始用8名工人(在你的例子),这fork了该程序8次进8点的过程。这8个流程是从Master流程派生的,Master流程监视每个流程的状态并具有添加/删除工作人员的能力。

每个进程都会获取你的APScheduler对象的副本,该对象最初是你主流程的APScheduler的精确副本。这导致每个“第n个”工作人员(进程)总共执行“ n”次作业。

解决此问题的方法是使用以下选项来运行gunicorn:

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

该–preload标志告诉Gunicorn“ 在派生工作进程之前加载应用程序 ”。这样,每个工作人员都“获得了已由Master实例化的应用程序副本,而不是实例化了应用程序本身”。这意味着以下代码在主进程中仅执行一次:

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)

最后,由于要执行BackgroundScheduler,我们要使用它start()。当我们调用start()BackgroundScheduler时,将在后台启动一个新线程,该线程负责调度/执行作业。这很重要,因为请记住在步骤(1)中,由于我们的–preload标志,我们start()在Master Gunicorn过程中仅执行一次该功能。根据定义,分叉的进程不会继承其父级的线程,因此每个工作进程都不会运行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工人都有一个APScheduler,该APScheduler被欺骗为“ STARTED”状态,但实际上并没有运行,因为它会丢弃其父线程!每个实例还能够更新作业存储数据库,只是不执行任何作业!

2020-04-02