express定时任务:Cron作业和调度系统

express定时任务:Cron作业和调度系统

【免费下载链接】express Fast, unopinionated, minimalist web framework for node. 【免费下载链接】express 项目地址: https://gitcode.com/GitHub_Trending/ex/express

引言:为什么需要定时任务?

在现代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定时任务系统的选择应该基于具体需求:

  1. 简单场景:使用node-cron,轻量易用
  2. 队列需求:选择Bull,基于Redis,支持重试和优先级
  3. 复杂调度:使用Agenda,功能丰富,支持MongoDB持久化
  4. 分布式环境:结合分布式锁和监控系统

关键最佳实践

  • 始终实现适当的错误处理和日志记录
  • 在分布式环境中使用分布式锁避免冲突
  • 监控任务执行情况和系统资源使用
  • 实现任务权限控制和输入验证
  • 定期审查和优化任务调度策略

通过合理选择和配置定时任务系统,你可以为Express应用添加强大的自动化能力,提升系统效率和可靠性。

【免费下载链接】express Fast, unopinionated, minimalist web framework for node. 【免费下载链接】express 项目地址: https://gitcode.com/GitHub_Trending/ex/express

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值