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

2024-02-01

我们有一个用金字塔制作的网络应用程序,并通过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。它需要与单一进程开发服务器(即waitress)一起工作。

来自赏金赞助商的更新

我面临着 OP 描述的相同问题,只是使用 Django 应用程序。我最确定添加这个细节不会改变原来的问题。出于这个原因,并且为了获得更多的可见性,我还将这个问题标记为django.


因为 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

The --preload标志告诉 Gunicorn“在分叉工作进程之前加载应用程序”。通过这样做,每个工人都“给定应用程序的副本,已由 Master 实例化,而不是实例化应用程序本身”。这意味着以下代码仅在 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)

最后,我们想使用后台调度程序由于其实施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,它被欺骗进入“已启动”状态,但实际上并未运行,因为它丢弃了其父级的线程!每个实例还能够更新作业存储数据库,只是不执行任何作业!

查看Flask-APScheduler https://github.com/viniciuschiele/flask-apscheduler一种在 Web 服务器(如 Gunicorn)中运行 APScheduler 的快速方法,并为每个作业启用 CRUD 操作。

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

确保只有一名工作人员在运行多个工作人员的金字塔 Web 应用程序中启动 apscheduler 事件 的相关文章

随机推荐