django - execstart - run gunicorn
Asegúrese de que solo un trabajador inicie el evento apscheduler en una aplicación web piramidal que ejecute varios trabajadores (2)
Tenemos una aplicación web hecha con pirámide y servida a través de gunicorn + nginx. Funciona con 8 procesos / hilos de trabajo.
Necesitábamos puestos de trabajo, hemos elegido apscheduler. Así es como lo lanzamos.
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)
El problema es que todos los procesos de trabajo de gunicorn recogen el programador. Intentamos implementar un bloqueo de archivo, pero no parece una solución lo suficientemente buena. ¿Cuál sería la mejor manera de asegurarse de que, en un momento dado, solo uno de los procesos de trabajo recoge el evento programado y ningún otro hilo lo recoge hasta el próximo JOB_INTERVAL
?
La solución debe funcionar incluso con mod_wsgi en caso de que decidamos cambiar a apache2 + modwsgi más adelante. Necesita trabajar con un servidor de desarrollo de proceso único, que es camarera.
Actualización del patrocinador de la recompensa
Estoy enfrentando el mismo problema descrito por el OP, solo con una aplicación Django. Estoy seguro de que agregar este detalle no cambiará mucho si la pregunta original. Por este motivo, y para ganar un poco más de visibilidad, también etiqueté esta pregunta con django
.
Debido a que Gunicorn está comenzando con 8 trabajadores (en su ejemplo), esto convierte la aplicación 8 veces en 8 procesos. Estos 8 procesos se bifurcan del proceso Maestro , que controla cada uno de sus estados y tiene la capacidad de agregar / eliminar trabajadores.
Cada proceso obtiene una copia de su objeto APScheduler, que inicialmente es una copia exacta del APScheduler de sus procesos maestros. Esto da como resultado que cada "nth" trabajador (proceso) ejecute cada trabajo un total de "n" veces.
Un truco alrededor de esto es ejecutar gunicorn con las siguientes opciones:
env/bin/gunicorn module_containing_app:app -b 0.0.0.0:8080 --workers 3 --preload
La bandera --preload
le dice a Gunicorn que " cargue la aplicación antes de bifurcar los procesos de trabajo ". Al hacerlo, a cada trabajador se le "da una copia de la aplicación, ya instanciada por el Maestro, en lugar de crear una instancia de la aplicación en sí" . Esto significa que el siguiente código solo se ejecuta una vez en el proceso Maestro:
rerun_monitor = Scheduler()
rerun_monitor.start()
rerun_monitor.add_interval_job(job_to_be_run,/
seconds=JOB_INTERVAL)
Además, debemos configurar el almacén de trabajo para que sea diferente a : memoria:. De esta manera, aunque cada trabajador es su propio proceso independiente que no puede comunicarse con los otros 7, mediante el uso de una base de datos local (en lugar de la memoria) garantizamos una -punto de verdad para las operaciones de CRUD en el almacén de trabajo.
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)
Por último, queremos usar el BackgroundScheduler debido a su implementación de start()
. Cuando llamamos a start()
en el BackgroundScheduler, un nuevo hilo se gira en segundo plano, que es responsable de programar / ejecutar trabajos. Esto es importante porque recuerde que en el paso (1), debido a nuestro indicador de --preload
, solo ejecutamos la función start()
una vez, en el proceso Master Gunicorn. Por definición, los procesos bifurcados no heredan los subprocesos de sus padres, por lo que cada trabajador no ejecuta el subproceso 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)
Como resultado de todo esto, todos los trabajadores de Gunicorn tienen un APScheduler que ha sido engañado en un estado "INICIADO", ¡pero en realidad no se está ejecutando porque deja caer los hilos de su padre! Cada instancia también es capaz de actualizar la base de datos del almacén de trabajo, ¡simplemente no ejecuta ningún trabajo!
Visite flask-APScheduler para obtener una manera rápida de ejecutar APScheduler en un servidor web (como Gunicorn), y habilite las operaciones CRUD para cada trabajo.
Encontré una solución que funcionó con un proyecto de Django que tenía un problema muy similar. Simplemente ato un socket TCP la primera vez que se inicia el programador y lo verifico posteriormente. Creo que el siguiente código puede funcionar para ti también con pequeños ajustes.
import sys, socket
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.bind(("127.0.0.1", 47200))
except socket.error:
print "!!!scheduler already started, DO NOTHING"
else:
from apscheduler.schedulers.background import BackgroundScheduler
scheduler = BackgroundScheduler()
scheduler.start()
print "scheduler started"