- Home »

Примеры использования cron задач в Node.js
Автоматизация – это то, что отличает ленивого админа от… очень ленивого админа. Cron задачи в Node.js – это мощный инструмент для создания периодических задач прямо в вашем JavaScript коде. Забудьте про системный crontab с его криптографической записью времени и неудобным управлением – теперь все можно сделать в рамках одного приложения. Эта статья поможет вам разобраться с библиотеками для создания cron задач, покажет практические примеры и даст готовые решения для самых частых задач.
Как это работает?
Cron задачи в Node.js работают благодаря специальным библиотекам, которые эмулируют поведение системного cron прямо в JavaScript среде. Самые популярные решения:
- node-cron – простая и легковесная библиотека
- cron – более функциональная альтернатива с поддержкой часовых поясов
- node-schedule – гибкая библиотека с человекочитаемым синтаксисом
Принцип работы прост: вы создаете задачу с определенным расписанием, библиотека следит за временем и выполняет функцию в нужный момент. Все это происходит в одном процессе Node.js, что упрощает логирование, обработку ошибок и интеграцию с основным приложением.
Быстрая настройка node-cron
Начнем с самого простого решения – node-cron. Установка и базовая настройка займет буквально минуту:
npm install node-cron
Простейший пример использования:
const cron = require('node-cron');
// Каждую минуту
cron.schedule('* * * * *', () => {
console.log('Задача выполняется каждую минуту');
});
// Каждый день в 9:00
cron.schedule('0 9 * * *', () => {
console.log('Доброе утро! Время для backup');
});
// Каждую пятницу в 18:00
cron.schedule('0 18 * * 5', () => {
console.log('Пятница, время подводить итоги недели');
});
Синтаксис cron выражений:
* * * * *
– минуты, часы, день месяца, месяц, день недели0-59
для минут0-23
для часов1-31
для дня месяца1-12
для месяца0-7
для дня недели (0 и 7 = воскресенье)
Практические примеры и кейсы
Автоматический бэкап базы данных
const cron = require('node-cron');
const { exec } = require('child_process');
const fs = require('fs');
const path = require('path');
// Ежедневный бэкап в 3:00 утра
cron.schedule('0 3 * * *', () => {
const timestamp = new Date().toISOString().slice(0, 10);
const backupPath = `/backups/db_backup_${timestamp}.sql`;
exec(`mysqldump -u root -p'password' mydatabase > ${backupPath}`, (error, stdout, stderr) => {
if (error) {
console.error(`Ошибка бэкапа: ${error}`);
return;
}
console.log(`Бэкап создан: ${backupPath}`);
// Удаляем старые бэкапы (старше 7 дней)
cleanOldBackups();
});
});
function cleanOldBackups() {
const backupDir = '/backups';
const files = fs.readdirSync(backupDir);
const now = Date.now();
files.forEach(file => {
const filePath = path.join(backupDir, file);
const stats = fs.statSync(filePath);
const fileAge = now - stats.mtime.getTime();
if (fileAge > 7 * 24 * 60 * 60 * 1000) { // 7 дней
fs.unlinkSync(filePath);
console.log(`Удален старый бэкап: ${file}`);
}
});
}
Мониторинг состояния сервера
const cron = require('node-cron');
const os = require('os');
const fs = require('fs');
const https = require('https');
// Проверка каждые 5 минут
cron.schedule('*/5 * * * *', () => {
const stats = {
timestamp: new Date().toISOString(),
cpuUsage: os.loadavg(),
memoryUsage: {
total: os.totalmem(),
free: os.freemem(),
used: os.totalmem() - os.freemem()
},
diskUsage: getDiskUsage()
};
// Отправка в Slack/Discord/Telegram при превышении лимитов
if (stats.memoryUsage.used / stats.memoryUsage.total > 0.9) {
sendAlert(`Высокое использование памяти: ${Math.round(stats.memoryUsage.used / stats.memoryUsage.total * 100)}%`);
}
// Логирование
console.log('Server stats:', stats);
});
function getDiskUsage() {
try {
const stats = fs.statSync('/');
return {
total: stats.size,
free: stats.free
};
} catch (error) {
return { error: 'Cannot read disk stats' };
}
}
function sendAlert(message) {
// Здесь код для отправки уведомления
console.log('ALERT:', message);
}
Автоматическая очистка логов
const cron = require('node-cron');
const fs = require('fs');
const path = require('path');
// Очистка логов каждое воскресенье в 2:00
cron.schedule('0 2 * * 0', () => {
const logDir = '/var/log/myapp';
const maxSizeBytes = 100 * 1024 * 1024; // 100MB
fs.readdir(logDir, (err, files) => {
if (err) {
console.error('Ошибка чтения директории логов:', err);
return;
}
files.forEach(file => {
const filePath = path.join(logDir, file);
const stats = fs.statSync(filePath);
if (stats.size > maxSizeBytes) {
// Архивируем большие файлы
const gzip = require('zlib').createGzip();
const input = fs.createReadStream(filePath);
const output = fs.createWriteStream(`${filePath}.gz`);
input.pipe(gzip).pipe(output);
output.on('finish', () => {
fs.unlinkSync(filePath);
console.log(`Заархивирован лог: ${file}`);
});
}
});
});
});
Сравнение популярных библиотек
Библиотека | Размер | Часовые пояса | Человекочитаемый синтаксис | Активность |
---|---|---|---|---|
node-cron | ~50KB | Базовая | Нет | Высокая |
cron | ~200KB | Полная | Нет | Средняя |
node-schedule | ~100KB | Есть | Есть | Высокая |
agenda | ~500KB | Есть | Есть | Средняя |
Продвинутые возможности с node-schedule
node-schedule предоставляет более гибкий API для сложных сценариев:
const schedule = require('node-schedule');
// Человекочитаемый синтаксис
const job = schedule.scheduleJob('0 9 * * 1-5', () => {
console.log('Рабочий день начался!');
});
// Использование объекта расписания
const rule = new schedule.RecurrenceRule();
rule.hour = 14;
rule.minute = 30;
rule.dayOfWeek = [1, 2, 3, 4, 5]; // Пн-Пт
schedule.scheduleJob(rule, () => {
console.log('Время послеобеденного перерыва!');
});
// Однократная задача
schedule.scheduleJob(new Date(Date.now() + 60000), () => {
console.log('Выполнится через минуту');
});
// Отмена задачи
job.cancel();
Интеграция с другими пакетами
Работа с базами данных
const cron = require('node-cron');
const mysql = require('mysql2/promise');
// Очистка старых записей каждый час
cron.schedule('0 * * * *', async () => {
const connection = await mysql.createConnection({
host: 'localhost',
user: 'root',
password: 'password',
database: 'myapp'
});
try {
const [results] = await connection.execute(
'DELETE FROM sessions WHERE created_at < DATE_SUB(NOW(), INTERVAL 24 HOUR)'
);
console.log(`Удалено ${results.affectedRows} старых сессий`);
} catch (error) {
console.error('Ошибка очистки сессий:', error);
} finally {
await connection.end();
}
});
Работа с файловой системой
const cron = require('node-cron');
const chokidar = require('chokidar');
const sharp = require('sharp');
// Обработка изображений каждые 10 минут
cron.schedule('*/10 * * * *', () => {
const watcher = chokidar.watch('/uploads/raw', {
ignored: /^\./,
persistent: false
});
watcher.on('add', async (filePath) => {
if (filePath.match(/\.(jpg|jpeg|png)$/i)) {
try {
const outputPath = filePath.replace('/raw/', '/processed/');
await sharp(filePath)
.resize(800, 600)
.jpeg({ quality: 80 })
.toFile(outputPath);
console.log(`Обработано изображение: ${filePath}`);
} catch (error) {
console.error(`Ошибка обработки ${filePath}:`, error);
}
}
});
});
Обработка ошибок и логирование
const cron = require('node-cron');
const winston = require('winston');
// Настройка логгера
const logger = winston.createLogger({
level: 'info',
format: winston.format.json(),
transports: [
new winston.transports.File({ filename: 'cron-error.log', level: 'error' }),
new winston.transports.File({ filename: 'cron-combined.log' })
]
});
// Обертка для безопасного выполнения cron задач
function safeCronJob(name, cronExpression, taskFunction) {
return cron.schedule(cronExpression, async () => {
const startTime = Date.now();
logger.info(`Запуск задачи: ${name}`);
try {
await taskFunction();
const duration = Date.now() - startTime;
logger.info(`Задача завершена: ${name}, время: ${duration}ms`);
} catch (error) {
logger.error(`Ошибка в задаче ${name}:`, error);
// Отправка уведомления об ошибке
await sendErrorNotification(name, error);
}
});
}
// Использование
safeCronJob('database-backup', '0 3 * * *', async () => {
// Код бэкапа
});
async function sendErrorNotification(taskName, error) {
// Отправка в Slack, email и т.д.
console.error(`CRON ERROR in ${taskName}:`, error.message);
}
Масштабирование и кластеризация
При работе с несколькими серверами важно избежать дублирования задач:
const cron = require('node-cron');
const redis = require('redis');
const cluster = require('cluster');
const client = redis.createClient();
// Выполнение только на мастер-процессе
if (cluster.isMaster) {
cron.schedule('0 * * * *', async () => {
const lockKey = 'cron:hourly-task';
const lockTimeout = 3600; // 1 час
try {
const lock = await client.set(lockKey, process.pid, 'EX', lockTimeout, 'NX');
if (lock) {
console.log('Получена блокировка, выполняем задачу');
await performHourlyTask();
await client.del(lockKey);
} else {
console.log('Задача уже выполняется на другом сервере');
}
} catch (error) {
console.error('Ошибка блокировки:', error);
}
});
}
async function performHourlyTask() {
// Ваша логика
}
Мониторинг и отладка
const cron = require('node-cron');
const express = require('express');
const app = express();
const cronJobs = new Map();
// Регистрация задач с метриками
function registerCronJob(name, expression, handler) {
const job = cron.schedule(expression, async () => {
const start = Date.now();
const jobInfo = cronJobs.get(name);
jobInfo.lastRun = new Date();
jobInfo.running = true;
try {
await handler();
jobInfo.lastSuccess = new Date();
jobInfo.errors = 0;
} catch (error) {
jobInfo.lastError = error.message;
jobInfo.errors++;
} finally {
jobInfo.running = false;
jobInfo.lastDuration = Date.now() - start;
}
});
cronJobs.set(name, {
expression,
job,
running: false,
lastRun: null,
lastSuccess: null,
lastError: null,
lastDuration: null,
errors: 0
});
return job;
}
// API для мониторинга
app.get('/cron/status', (req, res) => {
const status = {};
cronJobs.forEach((info, name) => {
status[name] = {
expression: info.expression,
running: info.running,
lastRun: info.lastRun,
lastSuccess: info.lastSuccess,
lastError: info.lastError,
lastDuration: info.lastDuration,
errors: info.errors
};
});
res.json(status);
});
app.listen(3000);
Нестандартные способы использования
Динамическое создание задач
const cron = require('node-cron');
class DynamicScheduler {
constructor() {
this.jobs = new Map();
}
addJob(id, expression, handler) {
if (this.jobs.has(id)) {
this.jobs.get(id).destroy();
}
const job = cron.schedule(expression, handler, {
scheduled: false
});
this.jobs.set(id, job);
job.start();
return job;
}
removeJob(id) {
if (this.jobs.has(id)) {
this.jobs.get(id).destroy();
this.jobs.delete(id);
}
}
updateJob(id, expression, handler) {
this.removeJob(id);
this.addJob(id, expression, handler);
}
}
const scheduler = new DynamicScheduler();
// Пример использования
scheduler.addJob('backup-user-1', '0 2 * * *', () => {
console.log('Backup for user 1');
});
// Обновление расписания
scheduler.updateJob('backup-user-1', '0 3 * * *', () => {
console.log('Updated backup for user 1');
});
Cron задачи с условиями
const cron = require('node-cron');
// Задача выполняется только в рабочие дни и рабочие часы
cron.schedule('*/15 * * * *', () => {
const now = new Date();
const hour = now.getHours();
const day = now.getDay();
// Пн-Пт, 9-18
if (day >= 1 && day <= 5 && hour >= 9 && hour <= 18) {
console.log('Проверка системы в рабочее время');
checkSystemHealth();
}
});
// Задача с проверкой внешних условий
cron.schedule('0 * * * *', async () => {
const systemLoad = await getSystemLoad();
if (systemLoad < 0.8) { console.log('Система не загружена, запускаем тяжелые задачи'); await runHeavyTasks(); } else { console.log('Система загружена, откладываем задачи'); } });
Развертывание на сервере
Для продакшена рекомендуется использовать PM2 для управления процессами:
# Установка PM2
npm install pm2 -g
# ecosystem.config.js
module.exports = {
apps: [{
name: 'cron-scheduler',
script: './scheduler.js',
instances: 1,
exec_mode: 'fork',
env: {
NODE_ENV: 'production'
},
error_file: './logs/err.log',
out_file: './logs/out.log',
log_file: './logs/combined.log'
}]
};
# Запуск
pm2 start ecosystem.config.js
pm2 save
pm2 startup
Если вам нужен надежный VPS для Node.js приложений или выделенный сервер для высоконагруженных задач, это поможет обеспечить стабильную работу ваших cron задач.
Статистика и производительность
По статистике GitHub, node-cron имеет около 1.5M недельных загрузок, что делает его самым популярным решением. node-schedule немного отстает с 800K загрузками, но предоставляет больше возможностей для сложных сценариев.
Производительность различных библиотек при выполнении 1000 задач:
- node-cron: ~50ms инициализация, 2MB RAM
- node-schedule: ~120ms инициализация, 5MB RAM
- agenda: ~300ms инициализация, 15MB RAM
Полезные ссылки
Заключение и рекомендации
Cron задачи в Node.js - это мощный инструмент автоматизации, который должен быть в арсенале каждого серверного разработчика. Для простых задач используйте node-cron, для сложных сценариев с часовыми поясами - node-schedule. Обязательно добавляйте логирование, обработку ошибок и мониторинг.
Основные рекомендации:
- Всегда оборачивайте cron задачи в try-catch блоки
- Используйте логирование для отслеживания выполнения
- Добавляйте блокировки при работе в кластере
- Тестируйте задачи в development окружении
- Настройте мониторинг для критически важных задач
- Используйте PM2 для управления процессами в продакшене
Помните: хороший админ автоматизирует рутину, а отличный админ автоматизирует автоматизацию. Cron задачи в Node.js дают вам все инструменты для создания надежной системы автоматизации прямо в вашем любимом JavaScript окружении.
В этой статье собрана информация и материалы из различных интернет-источников. Мы признаем и ценим работу всех оригинальных авторов, издателей и веб-сайтов. Несмотря на то, что были приложены все усилия для надлежащего указания исходного материала, любая непреднамеренная оплошность или упущение не являются нарушением авторских прав. Все упомянутые товарные знаки, логотипы и изображения являются собственностью соответствующих владельцев. Если вы считаете, что какой-либо контент, использованный в этой статье, нарушает ваши авторские права, немедленно свяжитесь с нами для рассмотрения и принятия оперативных мер.
Данная статья предназначена исключительно для ознакомительных и образовательных целей и не ущемляет права правообладателей. Если какой-либо материал, защищенный авторским правом, был использован без должного упоминания или с нарушением законов об авторском праве, это непреднамеренно, и мы исправим это незамедлительно после уведомления. Обратите внимание, что переиздание, распространение или воспроизведение части или всего содержимого в любой форме запрещено без письменного разрешения автора и владельца веб-сайта. Для получения разрешений или дополнительных запросов, пожалуйста, свяжитесь с нами.