Como executar o cron mais de uma vez por minuto usando PHP

O arquivo de configuração clássico para entradas de tarefas cron no sistema operacional Linux é o seguinte:

# ┌─────────────  (0 - 59)
# │ ┌─────────────  (0 - 23)
# │ │ ┌─────────────  (1 - 31)
# │ │ │ ┌─────────────  (1 - 12)
# │ │ │ │ ┌─────────────   (0 - 6)
# │ │ │ │ │
# * * * * * <  >

Os cinco primeiros parâmetros indicam o tempo de execução dessa tarefa e o sexto - o próprio comando, que deve ser executado. Os parâmetros de tempo são: minutos, horas, dias, meses e dia da semana. Além disso, todos os números devem ser representados como números inteiros ou na forma de sintaxe especial .

Como resultado, o intervalo mínimo possível para iniciar um comando é uma vez por minuto.

Para muitas tarefas, a execução de comandos é necessária com muito mais frequência, por exemplo, a cada 10 segundos. Para algumas tarefas de automação de processos de negócios, o atraso máximo permitido geralmente não passa de 1 a 1,5 segundos.

Obviamente, o cron clássico não é adequado para isso - ele precisa ser aprimorado.

Abaixo está uma implementação passo a passo da criação de funcionalidades adicionais (em PHP) para o cron clássico no Linux usando proteção adicional contra processos de reinicialização.

Configuração de tarefas e configuração de cron


Por exemplo, usaremos a seguinte tarefa:

  1. No front end, o usuário pode iniciar a execução de alguma tarefa complexa clicando no botão "Executar";
  2. O back-end após escrever uma nova linha no banco de dados informa o usuário da confirmação;
  3. Através do cron, “rastrearemos” essas novas tarefas e as concluiremos o mais rápido possível, para que o usuário não obtenha o resultado em um minuto, mas imediatamente *.

* Se você usar o início de comandos, apenas por minuto, a tarefa será iniciada quando os segundos atingirem o zero mais próximo (o início de um novo minuto). Portanto, na forma clássica, o usuário precisará aguardar a execução de 0 a 59 segundos.

Portanto, o cron será configurado em sua forma máxima, ou seja, uma vez por minuto:

* * * * * /usr/bin/php -q /run.php > /dev/null 2>&1
#/usr/bin/php -    PHP   (      )
#/run.php -   PHP   
#> /dev/null 2>&1 - ,            run.php

Ciclo único


Inicialmente, você deve decidir com que frequência solicitaremos novas tarefas no banco de dados - dependendo disso, o número de ciclos e o sono lógico (função sleep) serão alterados.No

exemplo atual, uma etapa de 10 segundos é usada. Portanto, o número de ciclos é 60/10 = 6. Total, o código geral é o seguinte:

for ($cycle = 1; $cycle <= 6; $cycle++) { # 6 
    $all_tasks = get_all_tasks(); #     
    if ($all_tasks) { #    - 
        foreach($all_tasks as $one_task) { #     
            solve_one_task($one_task); #  
        }
    }
    sleep(10); #  ,  ""  10 
}

Esclarecimento : neste exemplo, é usada uma etapa de 10 segundos, que pode fornecer um intervalo mínimo de execução de script uma vez a cada 10 segundos. Assim, para execução mais frequente, você deve alterar o número de ciclos e o "tempo de suspensão".

Evitando a execução repetida de tarefas


Na forma apresentada, há uma incerteza, a saber, a execução repetida da tarefa, se ela já foi iniciada. Isso se torna especialmente verdadeiro se a tarefa for "difícil" e exigir alguns segundos para implementá-la.

Isso cria um problema de reexecução:

  • A função solve_one_task()está em execução, mas ainda não concluiu seu trabalho;
  • Portanto, no banco de dados a tarefa ainda está marcada como não realizada;
  • O próximo ciclo receberá essa tarefa novamente e executará a função solve_one_task()novamente, com a mesma tarefa.

Obviamente, isso pode ser resolvido, por exemplo, alterando algum status no banco de dados para esta tarefa.

Mas não carregaremos o banco de dados: com base nos meus testes, o MYSQL pode aceitar a solicitação, mas não processá-la imediatamente. Uma diferença de até 0,5 segundos pode levar à execução repetida - o que categoricamente não é adequado.

Também neste caso, estamos falando apenas sobre o status das tarefas, portanto, é melhor usar o sistema de arquivos do servidor.

O modelo básico de verificação é construído usando flock- uma função que define e desbloqueia um arquivo.

Na execução do PHP, a função pode ser representada da seguinte maneira:

$lock_file_abs = 'file'; #  
$fp = fopen($lock_file_abs,"w+"); #   
if (flock($fp, LOCK_EX | LOCK_NB)) { #,    
    solve_one_task($one_task); #  
    flock($fp, LOCK_UN); #   ,     
}
else {
    #,   , ..       
}
fclose($fp); #   
unlink($lock_file_abs); # ,   

Resultado


A visão geral de todo o ciclo é a seguinte:

for ($cycle = 1; $cycle <= 6; $cycle++) {
    $all_tasks = get_all_tasks();
    if ($all_tasks) {
        foreach($all_tasks as $one_task) {
            $lock_file_abs = __DIR__.'/locks/run_'.$one_task['id'];
            $fp = fopen($lock_file_abs,"w+");
            if (flock($fp, LOCK_EX | LOCK_NB)) {
                solve_one_task($one_task);
                flock($fp, LOCK_UN);
            }
            else {
                #    
            }
            fclose($fp);
            unlink($lock_file_abs);
        }
    }
    sleep(10);
}

Portanto, esse algoritmo permite iniciar uma execução cíclica do processamento de tarefas e não se preocupa que a tarefa seja processada mais de uma vez.

E agora basta executar esse código através do cron a cada minuto e, por sua vez, executará ciclos menores já dentro de si.

All Articles