2008-10-22 19 views
8

Estoy apostando a que alguien ya ha resuelto esto y tal vez estoy usando los términos de búsqueda incorrectos para que google me diga la respuesta, pero esta es mi situación.script cron para actuar como una cola O una cola para cron?

Tengo un script que quiero ejecutar, pero quiero que se ejecute solo cuando está programado y solo de a uno por vez. (No se puede ejecutar el guión simultáneamente)

Ahora la parte pegajosa es que digo que tengo una tabla llamada "myhappyschedule" que tiene los datos que necesito y la hora programada. Esta tabla puede tener múltiples horarios programados, incluso al mismo tiempo, cada uno ejecutaría este script. Entonces, esencialmente, necesito una cola cada vez que el script se activa y todos deben esperar cada uno antes de que termine. (a veces esto puede tomar solo un minuto para que la secuencia de comandos se ejecute a veces muchos minutos)

Lo que estoy pensando hacer es hacer una secuencia de comandos que comprueba myhappyschedule cada 5 minutos y reúne los que están programados, los pone en una cola donde otro script puede ejecutar cada 'trabajo' u ocurrencia en la cola en orden. Que todo esto suena desordenado.

Para hacer esto más largo, debería decir que les estoy permitiendo a los usuarios programar cosas en myhappyschedule y no editar crontab.

¿Qué se puede hacer al respecto? ¿Bloqueos de archivos y scripts que invocan scripts?

Respuesta

3

agregar una columna exec_status a (tal vez también time_started y time_finished, ver pseudocódigo)

ejecute el siguiente script cron cada x minutos

pseudocode de script cronológico:

[create/check pid lock (optional, but see "A potential pitfall" below)] 
get number of rows from myhappytable where (exec_status == executing_now) 
if it is > 0, exit 
begin loop 
    get one row from myhappytable 
    where (exec_status == not_yet_run) and (scheduled_time <= now) 
    order by scheduled_time asc 
    if no such row, exit 
    set row exec_status to executing_now (maybe set time_started to now) 
    execute whatever command the row contains 
    set row exec_status to completed 
    (maybe also store the command output/return as well, set time_finished to now) 
end loop 
[delete pid lock file (complementary to the starting pid lock check)] 

esta manera , el script comprueba primero si ninguno de los comandos se está ejecutando, luego ejecuta el primer comando que aún no ejecuta, hasta que no haya más comandos para ejecutar en el momento dado. Además, puede ver qué comando se está ejecutando al consultar la base de datos.

Una posible trampa: si se cancela el script cron, una tarea programada permanecerá en el estado "executing_now". Para eso sirve el bloqueo pid al principio y al final: para ver si el script cron terminó correctamente. pseudocódigo de crear/verificar pidlock:

if exists pidlockfile then 
    check if process id given in file exists 
    if not exists then 
    update myhappytable set exec_status = error_cronscript_died_while_executing_this 
     where exec_status == executing_now 
    delete pidlockfile 
    else (previous instance still running) 
    exit 
    endif 
endif 
create pidlockfile containing cron script process id 
+0

Esto es un pseudocódigo que me ha salvado de una ruta muy insoportable en lo que respecta a la cola de trabajos. Muchas gracias Piskvor. – bouvard

2

Puede usar el comando at (1) dentro de su script para programar su siguiente ejecución. Antes de que finalice, puede verificar myhappyschedule para el próximo tiempo de ejecución. No necesitas cron en absoluto, realmente.

+0

Bueno .. eso era algo que no pienso. Me pregunto si se puede pasar una hora programada mientras se ejecuta la secuencia de comandos. Digamos que se ejecuta a las 8:00 a.m. y la secuencia de comandos se ejecuta durante 10 minutos y falla en cualquier momento que esté programado entre el inicio y el tiempo de finalización de la ejecución. – user30413

+0

Buen punto. Es posible que desee manipular atq en cualquier proceso que esté editando myhappyschedule. Tendré que pensar en esto durante el almuerzo. –

+0

Creo que si la secuencia de comandos comprueba myhappyschedule al final de su ejecución, y muestra la primera hora de inicio programada fuera de myhappyschedule para averiguar cuál es la siguiente en el momento de la ejecución, funcionará según lo previsto. La clave es tomar el tiempo más temprano aún en myhappyschedule. –

0

Me encontré con esta pregunta mientras investigaba una solución al problema de las colas. Para beneficio de cualquier otra persona que busque aquí, está mi solución.

Combine esto con un cron que inicia trabajos tal como están programados (incluso si están programados para ejecutarse al mismo tiempo) y eso resuelve el problema que usted describió también.

Problema


  • lo más una instancia del script deben estar en ejecución.
  • Queremos seguir las solicitudes para procesarlas lo más rápido posible.

ie. Necesitamos una tubería para el guión.

Solución:


Crear un gasoducto para cualquier secuencia de comandos. Hecho usando un pequeño script bash (más abajo).

El guión puede ser llamado como
./pipeline "<any command and arguments go here>"

Ejemplo:

./pipeline sleep 10 & 
./pipeline shabugabu & 
./pipeline single_instance_script some arguments & 
./pipeline single_instance_script some other_argumnts & 
./pipeline "single_instance_script some yet_other_arguments > output.txt" & 
..etc 

El script crea un nuevo named pipe para cada comando. Por lo que los tubos anteriores se crearán con nombre: sleep, shabugabu, y single_instance_script

En este caso la llamada inicial lanzará un lector y ejecutar single_instance_script con some arguments como argumentos. Una vez que la llamada se completa, el lector va a agarrar la siguiente solicitud de la tubería y ejecutar con some other_arguments, completa, agarra la siguiente etc ...

Este script se bloqueará solicitando procesos llamarla así como un trabajo en segundo plano (en el & final) o como un proceso independiente con at (at now <<< "./pipeline some_script")

#!/bin/bash -Eue 

# Using command name as the pipeline name 
pipeline=$(basename $(expr "$1" : '\(^[^[:space:]]*\)')).pipe 
is_reader=false 

function _pipeline_cleanup { 
     if $is_reader; then 
       rm -f $pipeline 
     fi 
     rm -f $pipeline.lock 

     exit 
} 
trap _pipeline_cleanup INT TERM EXIT 

# Dispatch/initialization section, critical 
lockfile $pipeline.lock 
     if [[ -p $pipeline ]] 
     then 
       echo "$*" > $pipeline 
       exit 
     fi 

     is_reader=true 
     mkfifo $pipeline 
     echo "$*" > $pipeline & 
rm -f $pipeline.lock 

# Reader section 
while read command < $pipeline 
do 
     echo "$(date) - Executing $command" 
     ($command) &> /dev/null 
done