Ответ 1
Поскольку Gunicorn начинается с 8 рабочих (в вашем примере), это forks приложение 8 раз в 8 процессов. Эти 8 процессов разворачиваются из процесса Мастер, который контролирует каждый их статус и имеет возможность добавлять/удалять работников.
Каждый процесс получает копию вашего объекта APScheduler, который изначально является точной копией ваших APScheduler ваших мастер-процессов. Это приводит к тому, что каждый "n-й" рабочий (процесс) выполняет каждое задание в общей сложности "n" раз.
Взлом вокруг этого заключается в том, чтобы запустить стрельбу из-за следующих вариантов:
env/bin/gunicorn module_containing_app:app -b 0.0.0.0:8080 --workers 3 --preload
Флаг --preload
сообщает Gunicorn "загружать приложение перед тем, как развернуть рабочие процессы". Таким образом, каждому работнику предоставляется "экземпляр приложения, уже созданный мастером, а не экземпляр самого приложения". Это означает, что следующий код выполняется только один раз в Мастер-процессе:
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. По определению forked процессы не наследуют потоки их родителя,, поэтому каждый рабочий не запускает поток 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, который был обманут в состояние "НАЧАЛО", но на самом деле не работает, потому что он отбрасывает потоки его родителя! Каждый экземпляр также может обновлять базу данных storestore, просто не выполняя никаких заданий!
Зайдите в flask-APScheduler, чтобы быстро запустить APScheduler на веб-сервере (например, Gunicorn) и включить операции CRUD для каждого работа.