Desenvolvimento

19 dez, 2012

Evite o overlap de Cronjobs no Linux

Publicidade

É um problema comum em muitos sistemas: você programou tarefas definidas no cronjobs e, por alguma razão, elas levam mais tempo para serem executadas do que o previsto. Isso significa que eventualmente elas começam a se sobrepor e a serem executadas ao mesmo tempo. Se esses são cronjobs que estão atuando sobre os mesmos dados de um banco de dados, isso pode significar corrupção de dados. Se eles estão fazendo um grande processamento de dados, isso poderia significar a carga do servidor está subindo muito. Por causa dessa alta carga, esses cronjobs estão levando mais tempo do que o habitual e, antes que você perceba, há um círculo vicioso em que cronjobs mantêm o lançamento e se sobrepõem uns aos outros.

Obviamente, você não quer isso. A boa notícia é que isso é bem fácil de evitar.

Utilizando flock

O flock é uma ferramenta muito interessante para o gerenciamento de arquivos de bloqueio. Esses arquivos de bloqueio são usados para determinar se um script ou um aplicativo já está em execução (comparável a um arquivo PID que contém a identificação do processo do script sendo executado). Se o bloqueio existe, o cronjob não será iniciado. Se o bloqueio não existe, é seguro lançar o cron.

Veja o seguinte exemplo comum, onde um cron é executado a cada minuto no servidor.

$ crontab -l
* * * * * /usr/bin/php /path/to/cron.php

Se o script leva mais de um minuto para executar, eles começam a se sobrepor. Para evitar isso, você pode mudar com o exemplo flock abaixo.

$ crontab -l
* * * * * /usr/bin/flock -w 0 /path/to/cron.lock /usr/bin/php /path/to/cron.php

O exemplo acima requer que o flock gerencie esses arquivos de bloqueio. Se isso ainda não existe no seu sistema, a instalação deve ser tão simples como um yum install flock ou apt-getinstall flock, dependendo de sua distribuição Linux (mais sobre isso aqui).

No momento em que o flock começa, ele bloqueia o arquivo de bloqueio que você especificar no comando. Você pode ver isso ao solicitar o usuário/script que está tendo o bloqueio sobre esse arquivo.

$ fuser -v /path/to/cron.lock
                     USER        PID ACCESS COMMAND
cron.lock:           root       7836 f.... flock
                     root       7837 f.... php

 

Ele vai mostrar o processo de IDs (PIDs) do script que está mantendo o bloqueio. Se nenhum script estiver mantendo o bloqueio, o comando fuser retornará simplesmente nada.

$ fuser -v /path/to/cron.lock

Então, o flock é uma boa maneira de evitar a sobreposição de cronjobs usando uma ferramenta de linha de comando adicional.

Utilizando pgrep

Outro método, sem o uso de arquivos de bloqueio, é utilizar um liner bash-one bem simples que verifica o arquivo de execução atual e o executa se ele não estiver funcionando. O truque é envolver seu crontask em um nome exclusivo do bash-script, assim:

$ cat /path/to/cron.sh
#!/bin/bash
/usr/bin/php /path/to/cron.php

$ chmod +x /path/to/cron.sh

No seu crontab, ele deve ser listado assim:

$ crontab -l
* * * * * /path/to/cron.sh

O comando acima irá, assim como o primeiro exemplo, executar o nosso script PHP a cada minuto através de um script. Para evitar a sobreposição, ele pode também ser alterado para isto:

$ crontab -l
* * * * * /usr/bin/pgrep -f /path/to/cron.sh > /dev/null 2> /dev/null || /path/to/cron.sh

O comando pgrep irá retornar falso se não encontrar um processo de execução correspondente ao primeiro argumento, /path/to/cron.sh. Se ele retornar falso, vai processar a segunda parte da comparação OR  (a linha vertical dupla, ||). Se o processo de execução foi encontrado, pgrep irá retornar o ID do processo (PID) e Bash não vai continuar para a segunda parte do argumento OR, já que o primeiro já retornou verdadeiro.

O truque aqui é usar nomes de script muito originais. Se o nome é muito genérico (como “cron.sh”), pgrep pode retornar IDs de processo a partir de outras tarefas cron em execução e não executar o cron que você queria.

Utilizando bloqueio de arquivos dentro do script

Se os exemplos acima não estão disponíveis para você, você ainda pode usar o conceito de arquivos de bloqueio em seu aplicativo. Um dos primeiros comandos em seu script poderia ser verificar a existência de um arquivo de bloqueio. Se ele existir, o script poderia simplesmente sair (1) do aplicativo e parar de rodar. Se o bloqueio de arquivo não existir, o script poderia criá-lo e evitar que o próximo trabalho seja executado.

Como último passo no seu script, você remove o arquivo de bloqueio para indicar que o script terminou e permitir que a próxima execução continue.

***

Texto original disponível em http://mattiasgeniar.be/2012/07/24/prevent-cronjobs-from-overlapping-in-linux/