Buenas prácticas de Cron: errores y tareas en producción
Utilidades赵婉清
Expresiones Cron: 5 campos para controlar el tiempo
┌───────── Minuto (0-59)
│ ┌───────── Hora (0-23)
│ │ ┌───────── Día (1-31)
│ │ │ ┌───────── Mes (1-12)
│ │ │ │ ┌───────── Día de la semana (0-7, 0 y 7 son domingo)
│ │ │ │ │
* * * * *
I. Referencia rápida de sintaxis
| Expresión | Significado | Frecuencia de ejecución |
|---|---|---|
* * * * * |
Cada minuto | 1440 veces/día |
*/5 * * * * |
Cada 5 minutos | 288 veces/día |
0 * * * * |
Cada hora en punto | 24 veces/día |
0 9 * * * |
Todos los días a las 9:00 | 1 vez/día |
0 9 * * 1-5 |
Días laborables a las 9:00 | 5 veces/semana |
30 2 1 * * |
Día 1 de cada mes a las 2:30 | 1 vez/mes |
0 0 1 1 * |
1 de enero a las 0:00 | 1 vez/año |
Caracteres especiales
| Carácter | Significado | Ejemplo |
|---|---|---|
* |
Cualquier valor | * * * * * |
, |
Enumeración | 0 9,12,18 * * * (9/12/18 horas) |
- |
Rango | 0 9-17 * * 1-5 (días laborables 9-17 horas) |
/ |
Paso | */15 * * * * (cada 15 minutos) |
L |
Último | 0 0 L * * (último día de cada mes) |
W |
Día laborable más cercano | 0 0 1W * * (día laborable más cercano al 1) |
# |
Enésimo día de la semana | 0 0 * * 5#3 (tercer viernes) |
II. Trampas comunes
Trampa 1: Especificar día y día de la semana simultáneamente
# Comprensión errónea: el día 15 de cada mes Y que sea viernes
0 0 15 * 5
# Significado real: el día 15 de cada mes O cada viernes (¡unión!)
# La forma correcta requiere lógica adicional
Regla: Cuando se especifican día y día de la semana simultáneamente, se toma la unión en lugar de la intersección.
Trampa 2: Problemas de zona horaria
# Se espera ejecución a las 9:00 hora de Beijing
0 9 * * *
# Servidor en zona UTC → ¡ejecución real a las 17:00 hora de Beijing!
# Solución: especificar la zona horaria explícitamente
// node-cron especificar zona horaria
cron.schedule('0 9 * * *', task, {
scheduled: true,
timezone: 'Asia/Shanghai',
});
Trampa 3: Error de escritura de 60 segundos
# Incorrecto: el campo de minutos no tiene 60
0 0 60 * * *
# Correcto: ejecutar cada hora
0 * * * *
Trampa 4: 30 de febrero
# Febrero no tiene día 30, esta expresión no se ejecutará en febrero
0 0 30 2 *
Trampa 5: Horario de verano
Durante el cambio de horario de verano, ciertas horas pueden no existir o repetirse:
Primavera adelantar: 2:00 → 3:00 (2:30 no existe)
Otoño retrasar: 2:00 → 1:00 (1:30 se ejecuta dos veces)
Recomendación: Usar zona horaria UTC para evitar problemas de horario de verano.
III. Verificar expresiones Cron con ToolsKu
Pasos de operación
- Abrir la herramienta de expresiones Cron
- Ingresar la expresión Cron
- Ver la interpretación en español:
0 9 * * 1-5→ "Lunes a viernes a las 09:00"*/30 8-18 * * *→ "Cada 30 minutos de 08:00 a 18:00 cada día"
- Ver la lista de próximas ejecuciones (5-10 próximas)
- Verificar si la expresión es válida
IV. Diseño de tareas programadas de nivel productivo
Control de concurrencia
import { CronJob } from 'cron';
let isRunning = false;
const job = new CronJob('0 */5 * * * *', async () => {
if (isRunning) {
console.log('La ronda anterior aún no ha terminado, se omite esta ejecución');
return;
}
isRunning = true;
try {
await processData();
} finally {
isRunning = false;
}
});
Bloqueo distribuido (despliegue de múltiples instancias)
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('Otra instancia ya adquirió el bloqueo, se omite');
return;
}
try {
await generateDailyReport();
} finally {
await redis.del(lockKey);
}
});
Reintento ante fallos
async function runWithRetry(task, maxRetries = 3, delay = 5000) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await task();
} catch (error) {
console.error(`Intento ${attempt} fallido:`, error.message);
if (attempt < maxRetries) {
await sleep(delay * attempt); // Retraso incremental
} else {
throw error;
}
}
}
}
const job = new CronJob('0 */10 * * * *', () => {
runWithRetry(syncData, 3, 5000);
});
Control de tiempo de espera
async function runWithTimeout(task, timeoutMs = 60000) {
return Promise.race([
task(),
new Promise((_, reject) =>
setTimeout(() => reject(new Error('Tarea excedió el tiempo de espera')), timeoutMs)
),
]);
}
V. Monitoreo de tareas
Registro de ejecución
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
}
});
Verificación de salud
// Registrar el último tiempo de ejecución exitosa
await redis.set('cron:last-success:daily-sync', Date.now());
// Verificación de salud: si no se ha ejecutado en más de 25 horas, activar 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. Patrones comunes de tareas programadas
| Necesidad | Expresión Cron | Descripción |
|---|---|---|
| Sincronización de datos cada hora | 0 * * * * |
En punto |
| Reporte matutino de días laborables | 0 9 * * 1-5 |
Lunes a viernes a las 9:00 |
| Respaldo diario | 0 2 * * * |
A las 2:00 a.m. |
| Limpieza semanal | 0 3 * * 0 |
Domingos a las 3:00 |
| Conciliación mensual | 0 6 1 * * |
Día 1 de cada mes a las 6:00 |
| Heartbeat cada 15 minutos | */15 * * * * |
Verificación de alta frecuencia |
| Reporte trimestral | 0 8 1 1,4,7,10 * |
Primer día del primer mes del trimestre |
VII. Lista de mejores prácticas
- Siempre usar zona horaria UTC, a menos que el negocio dependa fuertemente de la hora local
- No especificar día y día de la semana simultáneamente, evitar la trampa de la unión
- Control de concurrencia: Solo una instancia ejecutándose en un momento dado
- Bloqueo distribuido: Prevenir ejecución duplicada en despliegues de múltiples instancias
- Reintento ante fallos + alertas: No fallar silenciosamente
- Control de tiempo de espera: Evitar que las tareas se cuelguen indefinidamente
- Diseño idempotente: Resultados consistentes en ejecuciones repetidas
- Registro de ejecución: Registrar tiempo, duración y estado de cada ejecución
- Verificar expresiones con herramientas: Confirmar la semántica correcta antes del despliegue
#Cron#定时任务#crontab#时区#运维