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
- Abrir a ferramenta de expressões Cron
- Inserir a expressão Cron
- 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"
- Ver a lista de próximas execuções (5-10 próximas)
- 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
- Sempre usar fuso horário UTC, a menos que o negócio dependa fortemente do horário local
- Não especificar dia e dia da semana simultaneamente, evitar armadilha da união
- Controle de concorrência: Apenas uma instância executando em um dado momento
- Bloqueio distribuído: Prevenir execução duplicada em implantações de múltiplas instâncias
- Retentativa de falhas + alertas: Não falhar silenciosamente
- Controle de tempo limite: Evitar que tarefas fiquem suspensas indefinidamente
- Design idempotente: Resultados consistentes em execuções repetidas
- Registro de execução: Registrar tempo, duração e status de cada execução
- Verificar expressões com ferramentas: Confirmar semântica correta antes da implantação
#Cron#定时任务#crontab#时区#运维