Boas práticas de Cron: armadilhas e tarefas em produção

Utilitários

Expressões Cron: 5 campos para controlar o tempo

┌───────── Minuto (0-59)
│ ┌───────── Hora (0-23)
│ │ ┌───────── Dia (1-31)
│ │ │ ┌───────── Mês (1-12)
│ │ │ │ ┌───────── Dia da semana (0-7, 0 e 7 são domingo)
│ │ │ │ │
* * * * *

I. Referência rápida de sintaxe

Expressão Significado Frequência de execução
* * * * * Cada minuto 1440 vezes/dia
*/5 * * * * A cada 5 minutos 288 vezes/dia
0 * * * * Toda hora em ponto 24 vezes/dia
0 9 * * * Todos os dias às 9:00 1 vez/dia
0 9 * * 1-5 Dias úteis às 9:00 5 vezes/semana
30 2 1 * * Dia 1 de cada mês às 2:30 1 vez/mês
0 0 1 1 * 1º de janeiro às 0:00 1 vez/ano

Caracteres especiais

Caractere Significado Exemplo
* Qualquer valor * * * * *
, Enumeração 0 9,12,18 * * * (9/12/18 horas)
- Intervalo 0 9-17 * * 1-5 (dias úteis 9-17 horas)
/ Passo */15 * * * * (a cada 15 minutos)
L Último 0 0 L * * (último dia de cada mês)
W Dia útil mais próximo 0 0 1W * * (dia útil mais próximo ao dia 1)
# Enésimo dia da semana 0 0 * * 5#3 (terceira sexta-feira)

II. Armadilhas comuns

Armadilha 1: Especificar dia e dia da semana simultaneamente

# Compreensão errada: dia 15 de cada mês E que seja sexta-feira
0 0 15 * 5

# Significado real: dia 15 de cada mês OU toda sexta-feira (união!)
# A forma correta requer lógica adicional

Regra: Quando dia e dia da semana são especificados simultaneamente, a união é considerada em vez da interseção.

Armadilha 2: Problemas de fuso horário

# Espera-se execução às 9:00 horário de Pequim
0 9 * * *

# Servidor em fuso UTC → execução real às 17:00 horário de Pequim!
# Solução: especificar o fuso horário explicitamente
// node-cron especificar fuso horário
cron.schedule('0 9 * * *', task, {
  scheduled: true,
  timezone: 'Asia/Shanghai',
});

Armadilha 3: Erro de escrita de 60 segundos

# Incorreto: o campo de minutos não tem 60
0 0 60 * * *

# Correto: executar a cada hora
0 * * * *

Armadilha 4: 30 de fevereiro

# Fevereiro não tem dia 30, esta expressão não será executada em fevereiro
0 0 30 2 *

Armadilha 5: Horário de verão

Durante a mudança de horário de verão, certos horários podem não existir ou se repetir:

Primavera adiantar: 2:00 → 3:00 (2:30 não existe)
Outono atrasar: 2:00 → 1:00 (1:30 executado duas vezes)

Recomendação: Usar fuso horário UTC para evitar problemas de horário de verão.


III. Verificar expressões Cron com ToolsKu

Passos de operação

  1. Abrir a ferramenta de expressões Cron
  2. Inserir a expressão Cron
  3. Ver a interpretação em português:
    • 0 9 * * 1-5 → "Segunda a sexta às 09:00"
    • */30 8-18 * * * → "A cada 30 minutos das 08:00 às 18:00 todos os dias"
  4. Ver a lista de próximas execuções (5-10 próximas)
  5. Verificar se a expressão é válida

IV. Design de tarefas agendadas de nível produtivo

Controle de concorrência

import { CronJob } from 'cron';

let isRunning = false;

const job = new CronJob('0 */5 * * * *', async () => {
  if (isRunning) {
    console.log('A rodada anterior ainda não terminou, pulando esta execução');
    return;
  }

  isRunning = true;
  try {
    await processData();
  } finally {
    isRunning = false;
  }
});

Bloqueio distribuído (implantação de múltiplas instâncias)

import { Redis } from 'ioredis';

const redis = new Redis();

async function acquireLock(key, ttl = 300) {
  const acquired = await redis.set(key, '1', 'EX', ttl, 'NX');
  return acquired === 'OK';
}

const job = new CronJob('0 2 * * *', async () => {
  const lockKey = `cron:daily-report:${new Date().toISOString().slice(0, 10)}`;

  if (!(await acquireLock(lockKey))) {
    console.log('Outra instância já adquiriu o bloqueio, pulando');
    return;
  }

  try {
    await generateDailyReport();
  } finally {
    await redis.del(lockKey);
  }
});

Retentativa de falhas

async function runWithRetry(task, maxRetries = 3, delay = 5000) {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await task();
    } catch (error) {
      console.error(`Tentativa ${attempt} falhou:`, error.message);
      if (attempt < maxRetries) {
        await sleep(delay * attempt); // Atraso incremental
      } else {
        throw error;
      }
    }
  }
}

const job = new CronJob('0 */10 * * * *', () => {
  runWithRetry(syncData, 3, 5000);
});

Controle de tempo limite

async function runWithTimeout(task, timeoutMs = 60000) {
  return Promise.race([
    task(),
    new Promise((_, reject) =>
      setTimeout(() => reject(new Error('Tarefa excedeu o tempo limite')), timeoutMs)
    ),
  ]);
}

V. Monitoramento de tarefas

Registro de execução

const job = new CronJob('0 2 * * *', async () => {
  const startTime = Date.now();
  const context = { job: 'daily-sync', startedAt: new Date().toISOString() };

  try {
    await runWithRetry(syncData);
    context.status = 'success';
  } catch (error) {
    context.status = 'failed';
    context.error = error.message;
    await sendAlert(context); // Alerta
  } finally {
    context.duration = Date.now() - startTime;
    await logExecution(context); // Registrar log
  }
});

Verificação de saúde

// Registrar o último tempo de execução bem-sucedida
await redis.set('cron:last-success:daily-sync', Date.now());

// Verificação de saúde: se não foi executado há mais de 25 horas, acionar alerta
const lastSuccess = await redis.get('cron:last-success:daily-sync');
if (Date.now() - lastSuccess > 25 * 3600 * 1000) {
  await sendAlert({ type: 'cron_stale', job: 'daily-sync' });
}

VI. Padrões comuns de tarefas agendadas

Necessidade Expressão Cron Descrição
Sincronização de dados a cada hora 0 * * * * Em ponto
Relatório matutino de dias úteis 0 9 * * 1-5 Segunda a sexta às 9:00
Backup diário 0 2 * * * Às 2:00 da madrugada
Limpeza semanal 0 3 * * 0 Domingos às 3:00
Conciliação mensal 0 6 1 * * Dia 1 de cada mês às 6:00
Heartbeat a cada 15 minutos */15 * * * * Verificação de alta frequência
Relatório trimestral 0 8 1 1,4,7,10 * Primeiro dia do primeiro mês do trimestre

VII. Lista de melhores práticas

  1. Sempre usar fuso horário UTC, a menos que o negócio dependa fortemente do horário local
  2. Não especificar dia e dia da semana simultaneamente, evitar armadilha da união
  3. Controle de concorrência: Apenas uma instância executando em um dado momento
  4. Bloqueio distribuído: Prevenir execução duplicada em implantações de múltiplas instâncias
  5. Retentativa de falhas + alertas: Não falhar silenciosamente
  6. Controle de tempo limite: Evitar que tarefas fiquem suspensas indefinidamente
  7. Design idempotente: Resultados consistentes em execuções repetidas
  8. Registro de execução: Registrar tempo, duração e status de cada execução
  9. Verificar expressões com ferramentas: Confirmar semântica correta antes da implantação
#Cron#定时任务#crontab#时区#运维