我们有一个用 pyramid 做的网络应用程序,通过Gunicorn+nginx提供服务.它使用8个工作线程/进程

我们需要找到工作,我们 Select 了ApsScheduler.下面是我们如何启动它

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的所有工作进程都会 Select 调度程序.我们try 实现文件锁定,但它似乎不是一个足够好的解决方案.要确保在任何给定时间只有一个工作进程拾取计划的事件,并且在下一个JOB_INTERVAL之前没有其他线程拾取它,最好的方法是什么?

该解决方案甚至需要使用mod_wsgi,以防我们稍后决定切换到apache2+modwsgi.它需要与单一流程开发服务器,即服务员.

来自赏金赞助商的最新消息

我正面临着操作员描述的相同问题,只是使用了一款Django应用程序.我敢肯定,如果原来的问题是这样的话,添加这个细节不会有太大的改变.出于这个原因,为了获得更多的可见性,我还将这个问题标记为django.

推荐答案

因为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操作的快速方法.

Django相关问答推荐

如何显示日期?

Django-HTMX:呈现到不同目标的表单验证错误

如何在Django表单中传递self.请求数据?

新的 Django 开发人员无法在网站上显示Hello World,而不是默认安装成功页面

在 Django 中按月份和年份对帖子进行分类

dj_rest_auth 中的 PyTest 警告- RemovedInDjango40Warning: django.conf.urls.url() 已弃用,取而代之的是 django.urls.re_path()

Django:获取每组最新的N条记录

获取 Django Rest API 的第一个和最后一个相关对象

Django中的 联合(Union) 和相交(Intersect)

如何解决 AssertionError: .accepted_renderer not set on Response in django and ajax

直接在模型类上使用 Django 管理器与静态方法

更好的 ArrayField 管理小部件?

无法通过 pip 安装 Django 2.0

Django App 配置不当 - 应用程序模块有多个文件系统位置

如何在django中生成临时文件然后销毁

在 django 中的查询集上运行 explain的简单方法

如何在 Django 中配置 X-Frame-Options 以允许 iframe 嵌入一个视图?

Django 什么是反向关系?

在 django admin 中链接到外键对象

javascript 文件中的 Django {% static 'path' %}