Expressions Cron : pièges et tâches planifiées en production
Utilitaires赵婉清
Expressions Cron : 5 champs pour maîtriser le temps
┌───────── Minute (0-59)
│ ┌───────── Heure (0-23)
│ │ ┌───────── Jour (1-31)
│ │ │ ┌───────── Mois (1-12)
│ │ │ │ ┌───────── Jour de la semaine (0-7, 0 et 7 sont dimanche)
│ │ │ │ │
* * * * *
I. Aide-mémoire de syntaxe
| Expression | Signification | Fréquence d'exécution |
|---|---|---|
* * * * * |
Chaque minute | 1440 fois/jour |
*/5 * * * * |
Toutes les 5 minutes | 288 fois/jour |
0 * * * * |
Chaque heure pile | 24 fois/jour |
0 9 * * * |
Tous les jours à 9:00 | 1 fois/jour |
0 9 * * 1-5 |
Jours ouvrés à 9:00 | 5 fois/semaine |
30 2 1 * * |
Le 1er de chaque mois à 2:30 | 1 fois/mois |
0 0 1 1 * |
Le 1er janvier à 0:00 | 1 fois/an |
Caractères spéciaux
| Caractère | Signification | Exemple |
|---|---|---|
* |
Toute valeur | * * * * * |
, |
Énumération | 0 9,12,18 * * * (9h/12h/18h) |
- |
Plage | 0 9-17 * * 1-5 (jours ouvrés 9h-17h) |
/ |
Pas | */15 * * * * (toutes les 15 minutes) |
L |
Dernier | 0 0 L * * (dernier jour du mois) |
W |
Jour ouvré le plus proche | 0 0 1W * * (jour ouvré le plus proche du 1er) |
# |
N-ième jour de la semaine | 0 0 * * 5#3 (3e vendredi) |
II. Pièges courants
Piège 1 : Spécifier simultanément le jour et le jour de la semaine
# Compréhension erronée : le 15 de chaque mois ET un vendredi
0 0 15 * 5
# Signification réelle : le 15 de chaque mois OU chaque vendredi (union !)
# L'écriture correcte nécessite une logique supplémentaire
Règle : Lorsque le jour et le jour de la semaine sont spécifiés simultanément, l'union est prise et non l'intersection.
Piège 2 : Problèmes de fuseau horaire
# Exécution attendue à 9:00 heure de Pékin
0 9 * * *
# Serveur en fuseau UTC → exécution réelle à 17:00 heure de Pékin !
# Solution : spécifier explicitement le fuseau horaire
// node-cron spécifier le fuseau horaire
cron.schedule('0 9 * * *', task, {
scheduled: true,
timezone: 'Asia/Shanghai',
});
Piège 3 : Erreur d'écriture de 60 secondes
# Incorrect : le champ des minutes n'a pas 60
0 0 60 * * *
# Correct : exécuter chaque heure
0 * * * *
Piège 4 : 30 février
# Février n'a pas de 30, cette expression ne s'exécutera pas en février
0 0 30 2 *
Piège 5 : Heure d'été
Lors du passage à l'heure d'été, certaines heures peuvent ne pas exister ou apparaître deux fois :
Printemps avancer : 2:00 → 3:00 (2:30 n'existe pas)
Automne reculer : 2:00 → 1:00 (1:30 exécuté deux fois)
Recommandation : Utiliser le fuseau horaire UTC pour éviter les problèmes d'heure d'été.
III. Vérifier les expressions Cron avec ToolsKu
Étapes de fonctionnement
- Ouvrir l'outil d'expressions Cron
- Saisir l'expression Cron
- Consulter l'interprétation en français :
0 9 * * 1-5→ "Lundi au vendredi à 09:00"*/30 8-18 * * *→ "Toutes les 30 minutes de 08:00 à 18:00 chaque jour"
- Consulter la liste des prochaines exécutions (5-10 prochaines)
- Vérifier si l'expression est valide
IV. Design de tâches planifiées de niveau production
Contrôle de concurrence
import { CronJob } from 'cron';
let isRunning = false;
const job = new CronJob('0 */5 * * * *', async () => {
if (isRunning) {
console.log("Le tour précédent n'est pas encore terminé, passage de cette exécution");
return;
}
isRunning = true;
try {
await processData();
} finally {
isRunning = false;
}
});
Verrou distribué (déploiement multi-instances)
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("Une autre instance a déjà acquis le verrou, passage");
return;
}
try {
await generateDailyReport();
} finally {
await redis.del(lockKey);
}
});
Nouvelles tentatives après échec
async function runWithRetry(task, maxRetries = 3, delay = 5000) {
for (let attempt = 1; attempt <= maxRetries; attempt++) {
try {
return await task();
} catch (error) {
console.error(`Tentative ${attempt} échouée :`, error.message);
if (attempt < maxRetries) {
await sleep(delay * attempt); // Délai incrémentiel
} else {
throw error;
}
}
}
}
const job = new CronJob('0 */10 * * * *', () => {
runWithRetry(syncData, 3, 5000);
});
Contrôle du délai d'attente
async function runWithTimeout(task, timeoutMs = 60000) {
return Promise.race([
task(),
new Promise((_, reject) =>
setTimeout(() => reject(new Error("La tâche a dépassé le délai d'attente")), timeoutMs)
),
]);
}
V. Surveillance des tâches
Journal d'exécution
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); // Alerte
} finally {
context.duration = Date.now() - startTime;
await logExecution(context); // Enregistrer le journal
}
});
Vérification de santé
// Enregistrer l'heure de la dernière exécution réussie
await redis.set('cron:last-success:daily-sync', Date.now());
// Vérification de santé : si pas d'exécution depuis plus de 25 heures, déclencher une alerte
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. Modèles courants de tâches planifiées
| Besoin | Expression Cron | Description |
|---|---|---|
| Synchronisation horaire des données | 0 * * * * |
À l'heure pile |
| Rapport matinal des jours ouvrés | 0 9 * * 1-5 |
Lundi au vendredi à 9:00 |
| Sauvegarde quotidienne | 0 2 * * * |
À 2:00 du matin |
| Nettoyage hebdomadaire | 0 3 * * 0 |
Dimanche à 3:00 |
| Rapprochement mensuel | 0 6 1 * * |
Le 1er de chaque mois à 6:00 |
| Heartbeat toutes les 15 minutes | */15 * * * * |
Vérification haute fréquence |
| Rapport trimestriel | 0 8 1 1,4,7,10 * |
Premier jour du mois du trimestre |
VII. Liste des meilleures pratiques
- Toujours utiliser le fuseau horaire UTC, sauf si le métier dépend fortement de l'heure locale
- Ne pas spécifier le jour et le jour de la semaine simultanément, éviter le piège de l'union
- Contrôle de concurrence : une seule instance en cours d'exécution à un moment donné
- Verrou distribué : prévenir l'exécution dupliquée lors de déploiements multi-instances
- Nouvelles tentatives + alertes après échec : ne pas échouer silencieusement
- Contrôle du délai d'attente : empêcher les tâches de se bloquer indéfiniment
- Design idempotent : les exécutions répétées donnent des résultats identiques
- Journal d'exécution : enregistrer l'heure, la durée et le statut de chaque exécution
- Vérifier les expressions avec des outils : confirmer la sémantique correcte avant le déploiement
#Cron#定时任务#crontab#时区#运维