express定时任务:Cron作业和调度系统
引言:为什么需要定时任务?
在现代Web应用开发中,定时任务(Scheduled Tasks)已成为不可或缺的功能组件。无论是数据备份、缓存清理、报表生成,还是消息推送、状态监控,都需要可靠的任务调度机制。Express作为Node.js最流行的Web框架,虽然本身不内置定时任务功能,但通过与强大的第三方库集成,可以构建出高效稳定的定时任务系统。
本文将深入探讨如何在Express应用中实现专业的定时任务管理,涵盖从基础定时器到企业级分布式调度的完整解决方案。
基础定时器:Node.js原生方案
setInterval与setTimeout
Node.js提供了原生的定时器功能,适合简单的定时需求:
// 简单的间隔执行
const interval = setInterval(() => {
console.log('每5秒执行一次任务');
// 执行你的业务逻辑
}, 5000);
// 延迟执行
setTimeout(() => {
console.log('10秒后执行一次');
clearInterval(interval); // 清理定时器
}, 10000);
Express集成示例
const express = require('express');
const app = express();
// 启动定时任务
function startScheduledTasks() {
// 每小时清理临时文件
setInterval(() => {
console.log('执行每小时清理任务:', new Date().toISOString());
// 清理逻辑...
}, 60 * 60 * 1000);
// 每天凌晨执行数据备份
const now = new Date();
const midnight = new Date(now);
midnight.setHours(24, 0, 0, 0);
const timeUntilMidnight = midnight - now;
setTimeout(() => {
console.log('执行每日数据备份');
// 备份逻辑...
// 设置下一次执行
setInterval(() => {
console.log('执行每日数据备份');
}, 24 * 60 * 60 * 1000);
}, timeUntilMidnight);
}
app.listen(3000, () => {
console.log('Server started on port 3000');
startScheduledTasks();
});
Cron表达式:专业定时任务的基础
Cron语法详解
Cron表达式由6或7个字段组成,格式为:秒 分 时 日 月 周 年(可选)
| 字段 | 允许值 | 允许的特殊字符 |
|---|---|---|
| 秒 | 0-59 | , - * / |
| 分 | 0-59 | , - * / |
| 时 | 0-23 | , - * / |
| 日 | 1-31 | , - * ? / L W |
| 月 | 1-12或JAN-DEC | , - * / |
| 周 | 1-7或SUN-SAT | , - * ? / L # |
| 年 | 1970-2099 | , - * / |
常用Cron模式示例
// 常见Cron模式示例
const cronPatterns = {
everyMinute: '* * * * *', // 每分钟
everyHour: '0 * * * *', // 每小时
daily: '0 0 * * *', // 每天午夜
weekly: '0 0 * * 0', // 每周日午夜
monthly: '0 0 1 * *', // 每月1号午夜
workdays9am: '0 9 * * 1-5', // 工作日早上9点
every30Seconds: '*/30 * * * * *' // 每30秒
};
node-cron:轻量级Cron调度库
安装与基本使用
npm install node-cron
const cron = require('node-cron');
const express = require('express');
const app = express();
// 基本Cron任务
cron.schedule('*/5 * * * *', () => {
console.log('每5分钟执行一次任务');
});
// 带参数的任务
cron.schedule('0 2 * * *', () => {
performNightlyBackup();
}, {
scheduled: true,
timezone: "Asia/Shanghai"
});
function performNightlyBackup() {
console.log('执行夜间备份任务:', new Date().toLocaleString('zh-CN'));
// 备份数据库、文件等
}
app.get('/tasks', (req, res) => {
const tasks = cron.getTasks();
res.json({
activeTasks: Object.keys(tasks).length,
tasks: Object.keys(tasks)
});
});
app.listen(3000);
任务管理功能
// 任务管理示例
const task1 = cron.schedule('*/10 * * * * *', () => {
console.log('任务1执行:', new Date().toISOString());
});
const task2 = cron.schedule('*/15 * * * * *', () => {
console.log('任务2执行:', new Date().toISOString());
});
// 动态控制任务
setTimeout(() => {
task1.stop();
console.log('任务1已停止');
}, 30000);
setTimeout(() => {
task2.start();
console.log('任务2重新启动');
}, 45000);
Bull:基于Redis的队列调度
安装与配置
npm install bull
const Queue = require('bull');
const express = require('express');
const app = express();
// 创建任务队列
const taskQueue = new Queue('scheduled-tasks', {
redis: {
host: 'localhost',
port: 6379
}
});
// 定义任务处理器
taskQueue.process('backup', async (job) => {
console.log('处理备份任务:', job.data);
// 执行备份逻辑
return { status: 'completed', timestamp: new Date() };
});
taskQueue.process('cleanup', async (job) => {
console.log('处理清理任务:', job.data);
// 执行清理逻辑
return { status: 'completed', timestamp: new Date() };
});
// 添加定时任务
cron.schedule('0 3 * * *', async () => {
await taskQueue.add('backup', {
type: 'full',
database: 'production'
});
});
cron.schedule('0 */6 * * *', async () => {
await taskQueue.add('cleanup', {
type: 'temp_files',
retention: '7d'
});
});
// 监控任务状态
taskQueue.on('completed', (job, result) => {
console.log(`任务 ${job.id} 完成:`, result);
});
taskQueue.on('failed', (job, err) => {
console.error(`任务 ${job.id} 失败:`, err.message);
});
Agenda:功能丰富的任务调度
安装与基础配置
npm install agenda
const Agenda = require('agenda');
const express = require('express');
const app = express();
// 创建Agenda实例
const agenda = new Agenda({
db: { address: 'mongodb://localhost:27017/agenda' }
});
// 定义任务
agenda.define('generate report', async (job) => {
const { userId, reportType } = job.attrs.data;
console.log(`为用户 ${userId} 生成 ${reportType} 报表`);
// 报表生成逻辑
});
agenda.define('send newsletter', async (job) => {
const { template, recipientList } = job.attrs.data;
console.log(`发送新闻邮件给 ${recipientList.length} 个用户`);
// 邮件发送逻辑
});
// 定时调度
agenda.every('0 9 * * 1-5', 'generate report', {
userId: 'system',
reportType: 'daily'
});
agenda.every('0 0 1 * *', 'send newsletter', {
template: 'monthly',
recipientList: ['user1@example.com', 'user2@example.com']
});
// 启动Agenda
agenda.start();
// Express路由集成
app.get('/schedule/once', async (req, res) => {
const job = await agenda.schedule('in 2 minutes', 'generate report', {
userId: 'admin',
reportType: 'on-demand'
});
res.json({ jobId: job.attrs._id, scheduled: job.attrs.nextRunAt });
});
高级特性与最佳实践
错误处理与重试机制
// 健壮的错误处理
agenda.define('critical task', async (job) => {
try {
// 可能失败的操作
await someRiskyOperation();
} catch (error) {
console.error('任务执行失败:', error);
// 根据错误类型决定是否重试
if (error.retryable) {
throw error; // Agenda会自动重试
}
// 记录到监控系统
await logToMonitoringSystem(error);
}
});
// 配置重试策略
agenda.define('email delivery', {
concurrency: 5,
lockLimit: 10,
priority: 'high'
}, async (job) => {
// 邮件发送逻辑
});
任务监控与管理界面
// 简单的任务监控API
app.get('/api/tasks/status', async (req, res) => {
const jobs = await agenda.jobs({
nextRunAt: { $exists: true, $ne: null }
});
const status = jobs.map(job => ({
name: job.attrs.name,
nextRun: job.attrs.nextRunAt,
lastRun: job.attrs.lastRunAt,
failCount: job.attrs.failCount,
disabled: job.attrs.disabled
}));
res.json(status);
});
// 任务控制端点
app.post('/api/tasks/:jobId/disable', async (req, res) => {
const jobs = await agenda.jobs({ _id: req.params.jobId });
if (jobs.length > 0) {
await jobs[0].disable();
res.json({ message: '任务已禁用' });
} else {
res.status(404).json({ error: '任务未找到' });
}
});
分布式环境下的考虑
// 分布式锁机制
const Redlock = require('redlock');
const redis = require('redis');
const redisClient = redis.createClient();
const redlock = new Redlock([redisClient], {
driftFactor: 0.01,
retryCount: 10,
retryDelay: 200
});
agenda.define('distributed task', async (job) => {
const resource = 'lock:distributed-task';
const ttl = 30000; // 30秒锁有效期
try {
const lock = await redlock.lock(resource, ttl);
try {
// 执行需要分布式协调的任务
await executeDistributedOperation();
} finally {
await lock.unlock();
}
} catch (error) {
console.error('获取分布式锁失败:', error);
}
});
性能优化与资源管理
内存与连接管理
// 资源清理策略
const cleanupTasks = {
memoryCleanup: cron.schedule('0 */1 * * *', async () => {
if (global.gc) {
global.gc(); // 手动触发垃圾回收(如果启用)
}
// 清理缓存
await clearExpiredCache();
}),
connectionCleanup: cron.schedule('0 0 * * *', async () => {
// 清理空闲数据库连接
await cleanupIdleConnections();
})
};
// 监控资源使用
const monitorResources = cron.schedule('*/30 * * * * *', () => {
const memoryUsage = process.memoryUsage();
console.log('内存使用情况:', {
rss: `${(memoryUsage.rss / 1024 / 1024).toFixed(2)}MB`,
heapTotal: `${(memoryUsage.heapTotal / 1024 / 1024).toFixed(2)}MB`,
heapUsed: `${(memoryUsage.heapUsed / 1024 / 1024).toFixed(2)}MB`
});
});
安全考虑与实践
任务权限控制
// 基于角色的任务访问控制
function requireRole(role) {
return (req, res, next) => {
if (req.user && req.user.roles.includes(role)) {
next();
} else {
res.status(403).json({ error: '权限不足' });
}
};
}
app.post('/api/tasks', requireRole('admin'), async (req, res) => {
// 只有管理员可以创建任务
const { schedule, taskName, data } = req.body;
const job = await agenda.create(taskName, data);
await job.schedule(schedule).save();
res.json({ jobId: job.attrs._id });
});
// 输入验证
const Joi = require('joi');
const taskSchema = Joi.object({
schedule: Joi.string().pattern(/^(\*|([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])|\*\/([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])) (\*|([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])|\*\/([0-9]|1[0-9]|2[0-9]|3[0-9]|4[0-9]|5[0-9])) (\*|([0-9]|1[0-9]|2[0-3])|\*\/([0-9]|1[0-9]|2[0-3])) (\*|([1-9]|1[0-9]|2[0-9]|3[0-1])|\*\/([1-9]|1[0-9]|2[0-9]|3[0-1])) (\*|([1-9]|1[0-2])|\*\/([1-9]|1[0-2])) (\*|([0-7])|\*\/([0-7]))$/),
taskName: Joi.string().valid('backup', 'cleanup', 'report'),
data: Joi.object()
});
完整示例:电商系统定时任务
const express = require('express');
const cron = require('node-cron');
const Agenda = require('agenda');
const app = express();
// 初始化Agenda
const agenda = new Agenda({
db: { address: 'mongodb://localhost:27017/ecommerce-tasks' }
});
// 定义电商相关任务
agenda.define('process abandoned carts', async (job) => {
const { thresholdHours = 24 } = job.attrs.data;
console.log(`处理废弃超过 ${thresholdHours} 小时的购物车`);
// 查询并处理废弃购物车逻辑
});
agenda.define('generate sales report', async (job) => {
const { period, format } = job.attrs.data;
console.log(`生成 ${period} 销售报表,格式: ${format}`);
// 报表生成逻辑
});
agenda.define('update product prices', async (job) => {
const { supplier } = job.attrs.data;
console.log(`更新 ${supplier} 的商品价格`);
// 价格更新逻辑
});
// 定时任务配置
agenda.every('0 */4 * * *', 'process abandoned carts', {
thresholdHours: 4
});
agenda.every('0 0 * * *', 'generate sales report', {
period: 'daily',
format: 'csv'
});
agenda.every('0 2 * * 1', 'update product prices', {
supplier: 'all'
});
// 特殊促销任务
agenda.define('start flash sale', async (job) => {
const { saleId, discount } = job.attrs.data;
console.log(`启动限时抢购 ${saleId}, 折扣: ${discount}%`);
// 启动抢购逻辑
});
agenda.define('end flash sale', async (job) => {
const { saleId } = job.attrs.data;
console.log(`结束限时抢购 ${saleId}`);
// 结束抢购逻辑
});
// 启动Agenda
agenda.start();
// Express API端点
app.post('/api/flash-sales', async (req, res) => {
const { startTime, endTime, discount, saleId } = req.body;
// 安排抢购开始
await agenda.schedule(startTime, 'start flash sale', {
saleId,
discount
});
// 安排抢购结束
await agenda.schedule(endTime, 'end flash sale', {
saleId
});
res.json({ message: '限时抢购已安排' });
});
app.listen(3000, () => {
console.log('电商定时任务系统启动成功');
});
总结与建议
Express定时任务系统的选择应该基于具体需求:
- 简单场景:使用
node-cron,轻量易用 - 队列需求:选择
Bull,基于Redis,支持重试和优先级 - 复杂调度:使用
Agenda,功能丰富,支持MongoDB持久化 - 分布式环境:结合分布式锁和监控系统
关键最佳实践
- 始终实现适当的错误处理和日志记录
- 在分布式环境中使用分布式锁避免冲突
- 监控任务执行情况和系统资源使用
- 实现任务权限控制和输入验证
- 定期审查和优化任务调度策略
通过合理选择和配置定时任务系统,你可以为Express应用添加强大的自动化能力,提升系统效率和可靠性。
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考



