跳到主要内容

定时任务与后台作业

问题

服务端如何实现定时任务和后台作业?有哪些常见方案?

答案

定时任务方案

方案适用场景优缺点
node-cron单实例定时简单,不支持分布式
BullMQ Repeat分布式定时基于 Redis,支持集群
@nestjs/scheduleNestJS 项目装饰器语法,优雅
系统 CrontabLinux 定时与应用解耦
云服务Serverless 定时免运维

Cron 表达式

┌────────── 秒 (0-59)  [可选]
│ ┌──────── 分 (0-59)
│ │ ┌────── 时 (0-23)
│ │ │ ┌──── 日 (1-31)
│ │ │ │ ┌── 月 (1-12)
│ │ │ │ │ ┌ 周 (0-7, 0和7都是周日)
│ │ │ │ │ │
* * * * * *

常用示例:

  • 0 * * * * — 每小时整点
  • 0 0 * * * — 每天零点
  • 0 9 * * 1-5 — 工作日早 9 点
  • */5 * * * * — 每 5 分钟

BullMQ 定时任务

scheduled-jobs.ts
import { Queue, Worker } from 'bullmq';

const taskQueue = new Queue('scheduled-tasks', { connection: redis });

// 1. 可重复任务
await taskQueue.add('daily-report', { type: 'daily' }, {
repeat: { pattern: '0 9 * * *' }, // 每天 9 点
});

await taskQueue.add('cleanup', { type: 'cleanup' }, {
repeat: { every: 60 * 60 * 1000 }, // 每小时
});

// 2. 延迟任务
await taskQueue.add('send-reminder', { userId: '123' }, {
delay: 24 * 60 * 60 * 1000, // 24 小时后执行
});

// 3. 消费者
const worker = new Worker('scheduled-tasks', async (job) => {
switch (job.name) {
case 'daily-report':
await generateDailyReport();
break;
case 'cleanup':
await cleanupExpiredData();
break;
case 'send-reminder':
await sendReminder(job.data.userId);
break;
}
}, { connection: redis });

分布式定时任务注意事项

避免重复执行

多个服务实例同时运行时,定时任务可能被重复执行。需要确保:

  1. 使用基于 Redis 的任务队列(BullMQ 自动处理)
  2. 或使用分布式锁保证只有一个实例执行
distributed-cron.ts
// 用分布式锁防止重复执行
async function runDailyReport() {
const lock = await redis.set('lock:daily-report', '1', 'EX', 3600, 'NX');
if (!lock) return; // 其他实例已在执行

try {
await generateDailyReport();
} finally {
await redis.del('lock:daily-report');
}
}

常见面试问题

Q1: 定时任务和消息队列的区别?

答案

  • 定时任务:基于时间触发,如每天凌晨清理数据
  • 消息队列:基于事件触发,如用户下单后发送通知

两者经常配合使用:定时任务触发 → 消息队列执行。

Q2: 如何保证定时任务的可靠性?

答案

  1. 持久化:任务信息持久化到 Redis/数据库
  2. 失败重试:自动重试 + 指数退避
  3. 监控告警:任务未按时执行时告警
  4. 幂等性:重复执行不产生副作用

Q3: 前端有哪些"定时"场景?

答案

  • setInterval 轮询
  • requestAnimationFrame 动画
  • Web Worker 后台定时
  • Service Worker 后台同步
  • cron 触发 ISR 重新构建

相关链接