Celery: Característica: Beat debe evitar invocaciones simultáneas

Creado en 17 nov. 2010  ·  48Comentarios  ·  Fuente: celery/celery

Exigir al usuario que se asegure de que solo exista una instancia de celerybeat en su clúster crea una carga de implementación sustancial (ya sea creando un único punto de falla o alentando a los usuarios a lanzar su propio mutex distribuido).

celerybeat debe proporcionar un mecanismo para evitar la concurrencia inadvertida, o la documentación debe sugerir un enfoque de mejores prácticas.

Celerybeat

Comentario más útil

@ ankur11 single-beat asegura que solo se esté ejecutando una instancia de apio, pero no sincroniza el estado de programación entre instancias.

Si usé el programador predeterminado con una tarea periódica destinada a ejecutarse cada 15 minutos y tuve una conmutación por error con un solo latido 14 minutos después de la última vez que se ejecutó la tarea, la tarea no se ejecutaría hasta 15 minutos después del nuevo latido del apio. instancia comenzó, lo que resultó en un intervalo de 29 minutos.

Para compartir el estado de programación entre instancias, necesitaba usar un programador alternativo . django-celery-beat es la alternativa mencionada en los documentos de Celery, pero mi preferencia era usar Redis como backend para la sincronización de horarios, ya que ya estaba usando Redis como mi backend de Celery.

Redbeat incluye tanto el estado de programación compartido respaldado por Redis como el bloqueo para garantizar que solo una instancia esté programando tareas, por lo que no necesité un solo latido o BeatCop una vez que comencé a usar eso.

En nuestra implementación, el supervisor inicia el ritmo de apio en todas las instancias, con Redbeat como programador (por ejemplo, exec celery beat --scheduler=redbeat.RedBeatScheduler --app=myproject.celery:app ). Lamentablemente, no puedo compartir el código relacionado con el trabajo, pero me complace responder cualquier pregunta adicional sobre la implementación en general.

Todos 48 comentarios

Esto podría resolverse usando kombu.pidbox , así es como celeryd detecta que ya hay un nodo con el mismo nombre en ejecución. Dado que celerybeat está centralizado, podría usar un nombre de nodo fijo.

Como efecto secundario podremos controlar celerybeat con comandos de control remoto (podría haber un comando para recargar el horario, por ejemplo, o ver qué tareas vencen en un futuro próximo). Ese es un efecto secundario bastante impresionante si me preguntas.

Necesita más planificación, ya que existe un caso de uso para ejecutar varias instancias en el mismo clúster. Por ejemplo, para "fragmentar" el programa en varias partes. Debe existir al menos la posibilidad de seleccionar un nombre de nodo para cada instancia. Posponiendo a 2.3.0.

Tuvimos un problema en el que el cuadro que ejecutaba celerybeat se desconectaba sin una buena alternativa para iniciar una nueva instancia celerybeat que ocupara su lugar. ¿Cuál es la forma de alta disponibilidad recomendada para ejecutar celerybeat ?

¿El enfoque kombu.pidbox nos permitiría ejecutar múltiples instancias de celerybeat que simplemente dormirían si detectaran que una instancia ya se estaba ejecutando con el nombre de nodo fijo y sondeo para promocionarse a sí mismo al activo si eso instancia baja?

Ejecutar varias instancias activas suena interesante: ¿qué otros beneficios podría haber además de compartir la programación?

+1

+1

Esto es algo que es una preocupación real para las grandes implementaciones donde la resiliencia de la programación es importante.

+9999;)
¿Hay algún problema con el uso de la solución kombu.pidbox ? Incluso sin fragmentación y características sofisticadas, esto sería genial y muy útil. Ahora mismo necesito iniciar celerybeat manualmente en otro host.

Se podría usar Pidbox, pero el problema es que beat no es un consumidor. Para responder a mensajes de difusión como "¿hay instancias de beat aquí?" tendría que escuchar constantemente los mensajes en su cola de transmisión, y actualmente no puede porque está ocupado programando mensajes.

Técnicamente, podría estar usando un segundo hilo, pero eso puede reducir el rendimiento y es una gran sobrecarga solo para esta función.

Una segunda solución podría ser utilizar un candado, pero con la desventaja de tener que abrirlo. Es decir, si se mata el proceso de tiempo, el bloqueo obsoleto requeriría una intervención manual para iniciar una nueva instancia.

También podría tener un tiempo de espera de 2 segundos en la cerradura y actualizar la cerradura cada segundo. Entonces eso significa que una nueva instancia tendría que esperar 2 segundos si se mantiene el bloqueo.

Se podría crear un bloqueo en amqp declarando una cola, por ejemplo, `queue_declare ('celerybeat.lock', argument = {'x-expires': 2000}` `

+1

Me encantaría ver esto

+1

+1

+1 también

+1

¿Alguien ha implementado realmente la solución kombu.pidbox o cualquier otro mecanismo que resuelva este problema? Si es así, compártelo. Hay mucha gente que todavía se pregunta cuál es la mejor práctica.

¿Alguien se ha alejado del apio por completo debido a esto? Me interesaría saber eso también.

EDITAR:

Encontré esta esencia (https://gist.github.com/winhamwr/2719812) a través de una discusión de Google (https://www.google.co.in/search?q=celerybeat+lock&aq=f&oq=celerybeat+lock&aqs= chrome.0.57j62l3.2125j0 & sourceid = chrome & ie = UTF-8).

También me pregunto si alguien acaba de usar un archivo pid compartido para celerybeat directamente, tal vez con un EBS en AWS o tal vez en un bucket de S3… celerybeat --pidfile=/path/to/shared/volume .

Noté que el maestro actual (3.1 dev) tiene un paso de chismes para el consumidor. ¿Sería posible aprovechar la cola de chismes y la elección del líder para coordinar los procesos de ritmo integrados? Es decir, cada trabajador ejecutaría el proceso de tiempo integrado, pero solo el líder pondría en cola la tarea periódica. Esto probablemente supondría un almacenamiento de programación compartido.

@mlavin Esto podría funcionar, pero solo para transportes de intermediarios que admiten transmisión

El problema con la solución pidbox es que el programa celerybeat debe reescribirse para usar Async I / O.
Actualmente, no puede consumir tareas y producirlas, ya que el programador está bloqueando.

En la mayoría de los casos, esta no es una característica necesaria, ya que la mayoría de las implementaciones de producción tienen un host dedicado para el proceso de ritmo, y luego usar --pidfile es suficiente para asegurarse de no iniciar varias instancias.

He descubierto que a menudo las personas que se ven afectadas por este problema son las que utilizan la opción -B en una demonización
script y luego duplica esa configuración en otro host.

Entonces entiendo que es molesto, pero no creo que sea crítico. Si alguien realmente quiere una solución, puede contribuir con ella o contratarme / donarme para implementarla.

Se puede usar uWSGI para tener un proceso de un solo latido con respaldo a otros nodos

+1, lanzamos instancias de Amazon EC2 idénticas y será bueno tener tareas periódicas que se ejecuten solo en un nodo. Mientras tanto intentaré usar uWSGI gracias por la sugerencia.

+1

+1

He estado defendiendo en el trabajo el uso de Celerybeat para la programación, pero no tener HA listo para usar lo hace muy difícil. De hecho, parece que lo abandonaremos por completo debido a esto. En pocas palabras, ejecutar solo 1 instancia de Celerybeat hace que este sea un punto único de falla y, por lo tanto, no esté lista para la producción.

@junaidch No creo que

Sería mejor tener alguna funcionalidad incorporada en el apio, ya que es una especie de solución, pero aún así se puede usar en producción sin problemas.

Gracias @ 23doors.

Mis tareas ya mantienen un bloqueo de Redis para evitar que se ejecute otra instancia de la tarea. Si ejecuto 2 latidos en 2 máquinas diferentes y mis tareas están programadas para intervalos de 5 minutos, creo que funcionará aunque ambos latidos empujarán las tareas a la cola. Hacer un caso para la adopción se vuelve más difícil cuando tiene que implementar una solución para su funcionalidad principal.

Investigaré la recomendación de subclasificación. Ese podría ser un enfoque más limpio.

¡Gracias por las sugerencias!

En Lulu resolvimos esto escribiendo un administrador singleton de clúster simple (llamado BeatCop). Utiliza un bloqueo de Redis que vence para garantizar que solo haya un Celerybeat ejecutándose en un grupo de autoescalado de trabajadores de Celery. Si algo le sucede a ese Celerybeat (como que la instancia se escala o muere o Celerybeat se bloquea), otro nodo genera automáticamente un nuevo Celerybeat. Tenemos BeatCop de código abierto .

@ingmar escribimos esto https://github.com/ybrs/single-beat por las mismas razones, la última vez que verifiqué no vi tu comentario. También lanzamos como código abierto que podría ser útil para otros. más o menos hace lo mismo.

por lo que puedo ver, las principales diferencias con beatcop -, usamos pyuv - por lo que beatcop es más portátil, creo que menos dependencias -, redirigir el stderr y stdout del niño como padres, y salir si el niño muere con el mismo código, configúrelo con variables env. por lo que es un poco más fácil de agregar a supervisor.

Espero que pueda ser útil para otra persona.

+1

+1

Estoy considerando usar los valores-clave de Consul, como controlador de bloqueo, ¿alguien ha probado este enfoque? Entonces, mientras hay un momento en funcionamiento, los otros "dormirán" hasta que la cerradura no se actualice, entonces el mecanismo de elección del Cónsul decidiría quién sería el que actualizaría el valor de la clave con marca de tiempo. Quien actualice la cerradura es el que está trabajando.

@ingmar ¡ Gracias por esto! Voy a darle una oportunidad a esto en mi grupo de trabajadores.

+10 ya que la implementación actual significa un único punto de falla que se aleja de por qué usamos una cola distribuida en primer lugar

+1

+1

Parece que esto va a estar en v5.0.0 https://github.com/celery/celery/milestones/v5.0.0

+1

Cerrar esto, al igual que con los recursos actuales, llevará 10 años completarlo.

Lo sentimos, pero este es un problema grave para una cola denominada "distribuida". Independientemente del tiempo que lleve implementar esto, eventualmente debería arreglarse. Cerrar un problema perfectamente válido porque no tienes los recursos _ ahora mismo_ no parece correcto. ¿Quizás podría volver a abrirlo y aplicar una etiqueta que indique que es de baja prioridad en este momento?

Sé que el motivo de mi cierre fue absurdamente abrupto, así que como usuario de software puedo entender su opinión, pero técnicamente Beat es más como una función adicional. Está completamente desacoplado del resto de Celery, y fue diseñado intencionalmente para no ser distribuido para mantener la implementación simple. Comenzó como una forma ordenada de definir cronjobs de Python como una ventaja para los usuarios que ya usaban Celery, luego cada vez más personas usaban Celery como reemplazo de cron.

El problema ha estado abierto durante SEIS años y, aunque se solicita con frecuencia e innumerables empresas dependen de él, ninguna se ha ofrecido a pagar por su implementación.

En realidad, fue uno de los temas que pensé que sería interesante para las empresas patrocinar. Por supuesto, no es común que las empresas ofrezcan pagar por cualquier función, corrección de errores o incluso para ayudar a resolver un problema de producción. Probablemente pueda contarlos con una mano (eres increíble), así que ahora sé lo absolutamente ingenua que fue esa idea :)

Hoy también cerré un duplicado de este número, vea el n. ° 1495. Ha habido solicitudes de extracción que intentan resolver el problema, y ​​varias son prometedoras, pero dada la dedicación requerida para demostrar que una implementación determinada funciona, todavía no he tenido tiempo de revisarlas correctamente. Tal vez esto haga que alguien entre en acción, incluso si no, creo que es mejor que mantener abierta una solicitud de función durante seis años, cuando nadie está trabajando en ella. Eso también es una especie de perjuicio para los usuarios que quieren que esto se solucione.

@preguntar Bastante justo. Es cierto que el cron distribuido es un problema grande y complicado, como dices en el otro hilo. Y suena como algo que debería vivir fuera de Celery.

Gracias por tomarse el tiempo para explicar su razonamiento en detalle.

@preguntar Me preguntaba si este problema podría evitarse ubicando el archivo celerybeat-schedule (utilizado por celery.beat.PersistentScheduler ) dentro de un volumen NFS que se comparte en todos los nodos del clúster.

La clase PersistentScheduler usa shelve como módulo de base de datos, por lo que las escrituras simultáneas en el archivo celerybeat-schedule deben evitarse por diseño. Este es un extracto del shelve documentación :

El módulo de estantería no admite el acceso simultáneo de lectura / escritura a los objetos de estantería. (Los accesos de lectura múltiples simultáneos son seguros). Cuando un programa tiene un estante abierto para escribir, ningún otro programa debe tenerlo abierto para leer o escribir.

Suponiendo que comenzamos a batir el apio de esta manera:

celery -A project-name beat -l info -s /nfs_shared_volume/celerybeat-schedule

donde /nfs_shared_volume es el volumen compartido (p. ej., administrado por AWS Elastic File System), ¿podemos esperar que los horarios no se estropeen incluso si hay un proceso de apio en cada nodo del clúster?

@mikeschaekermann Si estoy leyendo los documentos correctamente, shelve no hace ningún esfuerzo por evitar el acceso de escritura simultáneo. Simplemente te dice que no dejes que suceda. La sección que citó continúa diciendo "El bloqueo de archivos Unix se puede usar para resolver esto, pero esto difiere entre las versiones de Unix y requiere conocimiento sobre la implementación de la base de datos utilizada".

@ ze-phyr-us Creo que tienes razón y malinterpreté los shelve docs. Aún así, me pregunto si el problema se resolvería asumiendo que el backend Scheduler garantiza las operaciones @ask hace el django-celery-beat paquete de atomicidad ayuda a resolver el problema? Vi que usa transacciones para hacer algunas de las actualizaciones.

Para cualquier otra persona que termine aquí mientras busca un ritmo de apio amigable distribuido / autoescalable, y esté feliz de usar Redis como backend; Probé tanto BeatCop como el single-beat mencionado anteriormente, pero finalmente elegí RedBeat .

Hola @ddevlin
Estoy teniendo problemas similares, ¿qué problemas enfrentó al usar Single-Beat? Además, si no es demasiado, ¿podría compartir la muestra de implementación de cómo configuró redbeat para múltiples servidores?

@ ankur11 single-beat asegura que solo se esté ejecutando una instancia de apio, pero no sincroniza el estado de programación entre instancias.

Si usé el programador predeterminado con una tarea periódica destinada a ejecutarse cada 15 minutos y tuve una conmutación por error con un solo latido 14 minutos después de la última vez que se ejecutó la tarea, la tarea no se ejecutaría hasta 15 minutos después del nuevo latido del apio. instancia comenzó, lo que resultó en un intervalo de 29 minutos.

Para compartir el estado de programación entre instancias, necesitaba usar un programador alternativo . django-celery-beat es la alternativa mencionada en los documentos de Celery, pero mi preferencia era usar Redis como backend para la sincronización de horarios, ya que ya estaba usando Redis como mi backend de Celery.

Redbeat incluye tanto el estado de programación compartido respaldado por Redis como el bloqueo para garantizar que solo una instancia esté programando tareas, por lo que no necesité un solo latido o BeatCop una vez que comencé a usar eso.

En nuestra implementación, el supervisor inicia el ritmo de apio en todas las instancias, con Redbeat como programador (por ejemplo, exec celery beat --scheduler=redbeat.RedBeatScheduler --app=myproject.celery:app ). Lamentablemente, no puedo compartir el código relacionado con el trabajo, pero me complace responder cualquier pregunta adicional sobre la implementación en general.

@mikeschaekermann , podría intentar envolver su ritmo de apio con / use / bin / flock para bloquear el acceso ...

flock /nfs/lock.file celery beat ...

Suponiendo que confía en su implementación de bloqueo NFS :)

Esto aseguraría que solo uno se ejecute y que los demás se bloqueen hasta que el casillero muera.

@mikeschaekermann , podría intentar envolver su ritmo de apio con / use / bin / flock para bloquear el acceso ...

flock /nfs/lock.file apio batido ...

Suponiendo que confía en su implementación de bloqueo NFS :)

Esto aseguraría que solo uno se ejecute y que los demás se bloqueen hasta que el casillero muera.

Probé este método. Desafortunadamente, si el cliente que tiene el bloqueo NFS pierde la conectividad con el servidor NFS, el servidor NFS puede revocar el bloqueo y entregarlo a otro cliente. Cuando el soporte de la cerradura original recupera la conectividad, flock no se da cuenta de que la cerradura ha sido revocada, por lo que ahora hay dos nodos que creen que son el "líder".

Terminé usando un bloqueo de aviso en Postgres. Hice un comando de administración de Django que usa el módulo django_pglocks y ejecuta celery beat en un subproceso.

Terminé usando un bloqueo de aviso en Postgres. Hice un comando de administración de Django que usa el módulo django_pglocks y ejecuta celery beat en un subproceso.

Esto parece que podría ser susceptible a los mismos problemas que vi con el uso de NFS. ¿Qué sucede si el cliente que mantiene el bloqueo pierde la conexión con el servidor de Postgres o si se reinicia el servidor de Postgres?

@ swt2c Argh, ¡por supuesto que tienes razón! Tiene que haber algún tipo de mantener vivo.

Ahora mismo estoy haciendo:

def _pre_exec():
    prctl.set_pdeathsig(signal.SIGTERM)

with advisory_lock(LOCK_ID) as acquired:
            assert acquired
            logging.info("Lock acquired: %s", acquired)
            p = subprocess.Popen(
                celery,
                shell=False,
                close_fds=True,
                preexec_fn=_pre_exec,
            )
            sys.exit(p.wait())

advisor_lock admite la recursividad, pero no sé si realmente está verificando la base de datos:

In [8]:  with advisory_lock('foo') as acquired:
   ...:     print acquired
   ...:     while True:
   ...:        with advisory_lock('foo') as acquired:
   ...:           print acquired
   ...:        time.sleep(1)
   ...:       

# Yes, it does:

True
True
True
<shutdown the instsance>
InterfaceError: cursor already closed

Entonces ... podría modificarlo para seguir adquiriendo el bloqueo / sondeo y matar el ritmo si falla. No garantiza la exclusión mutua, pero podría ser lo suficientemente bueno para mis propósitos.

Para mi caso, los latidos concurrentes son una molestia inútil, pero no un problema de integridad. Si lo fuera, también podría envolver la tarea en un bloqueo de aviso que si la base de datos falla, la tarea falla de todos modos.

También he guardado el cronograma en la base de datos, pero no he probado lo que hace el ritmo cuando la base de datos baja.

@ddevlin Me alegré de ver tu comentario, ya que esa era la solución que también estaba pensando en implementar.

Sin embargo, si pudiera compartir la lógica de cómo el supervisor inicia automáticamente redbeat-1 cuando redbeat-2 baja, sería de gran ayuda.

Esto podría deberse a mi falta de comprensión con respecto a supervisor , pero parece que el autorestart=True es efectivo solo para programas que al menos ingresan al estado RUNNING una vez.

Mi problema es:

  1. Tengo dos program en mi supervisor.conf de celery beat con redbeat.RedBeatScheduler .
  2. Supervisor inicial, un beat ( beat-1 ) obtiene el bloqueo y se ejecuta, mientras que el otro ( beat-2 ) intenta comenzar un par de veces y entra en el FATAL estado (con el error Seems we're already running? ).
  3. Idealmente, si beat-1 detiene, entonces quiero que el supervisor comience beat-2 .
  4. Sin embargo, eso no sucede ya que, para empezar, nunca estuvo en un estado RUNNING . Lo que significa que si detengo beat-1 , entonces se detiene y luego no pasa nada.

En lo alto de mi cabeza, la solución sería tener un cron que siga haciendo un supervisorctl restart all cada 5 segundos más o menos, pero solo quería saber lo que piensa sobre cómo pudo lograr esa redundancia con supervisor.

Hola @harisibrahimkv , tu problema es que estás iniciando dos instancias idénticas de apio en el mismo anfitrión; Supongo que verá ERROR: Pidfile (celerybeat.pid) already exists. en sus registros por beat-2 ? Puedo ver que tener dos instancias de celery beat ejecutándose en el mismo host sería útil para probar la conmutación por error entre ellos, pero para una redundancia real, probablemente desee que celery beat se ejecute en varios hosts.

Para que varias instancias se ejecuten en el mismo host, haga que el supervisor las inicie con el argumento --pidfile y les proporcione archivos pid por separado: p. Ej.

# beat-1 
celery beat --scheduler=redbeat.RedBeatScheduler --pidfile="beat-1.pid" ...
# beat-2
celery beat --scheduler=redbeat.RedBeatScheduler --pidfile="beat-2.pid" ...

Ambas instancias deben iniciarse con éxito bajo el supervisor, pero si verifica los archivos de registro, solo una de ellas debe ser la programación de tareas. Si detiene esa instancia, debería ver que la otra instancia se hace cargo de la programación de tareas.

Nuestro objetivo era tener un grupo de escalado automático de anfitriones idénticos ejecutando trabajadores de apio y batido de apio bajo supervisión. Cada anfitrión tiene una única instancia de ritmo de apio. En esta configuración, el ritmo de apio debería iniciarse con éxito en todos los hosts, pero cualquier instancia de ritmo de apio que no adquiera el bloqueo se convertirá efectivamente en espera activa y no programará tareas (aunque todos los hosts del grupo procesarán las tareas). Si la instancia con el bloqueo se detiene (por ejemplo, cuando el grupo se reduce o cuando estamos realizando una actualización progresiva de los hosts en el grupo), una de las instancias en espera adquirirá el bloqueo y se hará cargo de las tareas de programación.

@ddevlin ¡ Muchas gracias por

  1. El bit pidfile funcionó y estaba tan feliz de ver a beat-2 asumir las tareas cuando el otro se detuvo. Podría configurar el tiempo del compás con CELERYBEAT_MAX_LOOP_INTERVAL = 25 (en celery 3.x).

  2. Sí, para obtener una redundancia real, planeamos tener esta configuración en diferentes instancias por completo. Gracias por explicar la configuración que estaba utilizando. Voy a trabajar en eso ahora. La configuración de "host múltiple en la misma instancia", como entendió correctamente, fue para validar inicialmente si el concepto de conmutación por error funciona con esta configuración de supervisor.

Caluroso agradecimiento,
De una pequeña aldea en el extremo sur del subcontinente indio. :)

¿Fue útil esta página
0 / 5 - 0 calificaciones