NestJS定时任务

学习目标

  • 掌握NestJS定时任务模块的使用方法
  • 理解cron表达式的语法和使用场景
  • 学习如何配置和管理间隔任务
  • 了解定时任务的调度策略和最佳实践

核心知识点

1. 定时任务模块简介

NestJS的定时任务模块基于@nestjs/schedule包,它提供了一种声明式的方式来定义和管理定时任务。这个模块支持两种类型的定时任务:

  • Cron任务:基于cron表达式的定时任务,适用于需要在特定时间点执行的任务
  • 间隔任务:基于固定时间间隔的定时任务,适用于需要定期重复执行的任务

2. 安装和配置

首先,我们需要安装定时任务模块:

npm install --save @nestjs/schedule

然后,在应用的根模块中导入并配置定时任务模块:

// src/app.module.ts
import { Module } from '@nestjs/common';
import { ScheduleModule } from '@nestjs/schedule';
import { TasksModule } from './tasks/tasks.module';

@Module({
  imports: [
    ScheduleModule.forRoot(),
    TasksModule,
  ],
})
export class AppModule {}

3. Cron任务

Cron任务使用cron表达式来定义执行时间。cron表达式由5个字段组成,分别表示:

字段 允许值 特殊字符
0-59 , - * /
0-59 , - * /
0-23 , - * /
1-31 , - * / ?
1-12 , - * /
星期 0-7 , - * / ?

特殊字符说明

  • *:匹配任意值
  • ,:指定多个值
  • -:指定范围
  • /:指定步长
  • ?:用于日和星期字段,表示不指定值

示例:

  • 0 0 12 * * ?:每天中午12点执行
  • 0 30 9-17 * * 1-5:每个工作日的9:30到17:30之间,每小时执行一次
  • 0 0/5 * * * ?:每5分钟执行一次

4. 间隔任务

间隔任务使用固定的时间间隔来定义执行频率。NestJS支持以下时间单位:

  • milliseconds:毫秒
  • seconds:秒
  • minutes:分钟
  • hours:小时
  • days:天

5. 任务装饰器

NestJS提供了以下装饰器来定义定时任务:

  • @Cron():定义基于cron表达式的定时任务
  • @Interval():定义基于固定时间间隔的定时任务
  • @Timeout():定义只执行一次的延迟任务

实用案例分析

案例1:实现定时数据同步

需求分析

我们需要实现一个定时任务,每天凌晨3点从外部API同步数据到本地数据库。

实现方案

  1. 创建任务服务:
// src/tasks/tasks.service.ts
import { Injectable, Logger } from '@nestjs/common';
import { Cron, Interval, Timeout } from '@nestjs/schedule';
import { DataSyncService } from '../data-sync/data-sync.service';

@Injectable()
export class TasksService {
  private readonly logger = new Logger(TasksService.name);

  constructor(private dataSyncService: DataSyncService) {}

  // 每天凌晨3点执行数据同步
  @Cron('0 0 3 * * *')
async handleCron() {
    this.logger.debug('执行数据同步任务');
    await this.dataSyncService.syncData();
  }

  // 每10秒检查一次同步状态
  @Interval(10000)
handleInterval() {
    this.logger.debug('检查同步状态');
    // 检查同步状态的逻辑
  }

  // 应用启动后5秒执行一次初始化任务
  @Timeout(5000)
async handleTimeout() {
    this.logger.debug('执行初始化任务');
    await this.dataSyncService.initialize();
  }
}
  1. 创建任务模块:
// src/tasks/tasks.module.ts
import { Module } from '@nestjs/common';
import { TasksService } from './tasks.service';
import { DataSyncModule } from '../data-sync/data-sync.module';

@Module({
  imports: [DataSyncModule],
  providers: [TasksService],
})
export class TasksModule {}
  1. 创建数据同步服务:
// src/data-sync/data-sync.service.ts
import { Injectable, Logger } from '@nestjs/common';

@Injectable()
export class DataSyncService {
  private readonly logger = new Logger(DataSyncService.name);

  async syncData() {
    try {
      this.logger.log('开始同步数据');
      // 模拟数据同步操作
      await new Promise(resolve => setTimeout(resolve, 3000));
      this.logger.log('数据同步完成');
    } catch (error) {
      this.logger.error('数据同步失败', error);
    }
  }

  async initialize() {
    this.logger.log('初始化数据同步服务');
    // 初始化逻辑
  }
}
  1. 创建数据同步模块:
// src/data-sync/data-sync.module.ts
import { Module } from '@nestjs/common';
import { DataSyncService } from './data-sync.service';

@Module({
  providers: [DataSyncService],
  exports: [DataSyncService],
})
export class DataSyncModule {}

案例2:实现定时清理任务

需求分析

我们需要实现一个定时任务,每周日凌晨2点清理系统中的临时文件和过期数据。

实现方案

// src/tasks/cleanup.task.ts
import { Injectable, Logger } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import { FileSystemService } from '../file-system/file-system.service';
import { DatabaseService } from '../database/database.service';

@Injectable()
export class CleanupTask {
  private readonly logger = new Logger(CleanupTask.name);

  constructor(
    private fileSystemService: FileSystemService,
    private databaseService: DatabaseService,
  ) {}

  // 每周日凌晨2点执行清理任务
  @Cron('0 0 2 * * 0')
async handleCleanup() {
    this.logger.log('开始执行清理任务');
    
    try {
      // 清理临时文件
      await this.fileSystemService.cleanupTempFiles();
      
      // 清理过期数据
      await this.databaseService.cleanupExpiredData();
      
      this.logger.log('清理任务执行完成');
    } catch (error) {
      this.logger.error('清理任务执行失败', error);
    }
  }
}

案例3:动态控制定时任务

需求分析

我们需要实现一个可以动态启用和禁用的定时任务,以及可以动态修改执行时间的功能。

实现方案

// src/tasks/dynamic-task.service.ts
import { Injectable, Logger, OnModuleInit } from '@nestjs/common';
import { SchedulerRegistry } from '@nestjs/schedule';
import { CronJob } from 'cron';

@Injectable()
export class DynamicTaskService implements OnModuleInit {
  private readonly logger = new Logger(DynamicTaskService.name);

  constructor(private schedulerRegistry: SchedulerRegistry) {}

  onModuleInit() {
    this.addCronJob();
  }

  addCronJob() {
    const name = 'dynamic-task';
    
    const job = new CronJob('0 * * * * *', () => {
      this.logger.warn(`执行动态任务: ${name}`);
    });

    this.schedulerRegistry.addCronJob(name, job);
    job.start();

    this.logger.log(`已添加定时任务: ${name}`);
  }

  disableTask(name: string) {
    this.schedulerRegistry.getCronJob(name).stop();
    this.logger.warn(`已禁用定时任务: ${name}`);
  }

  enableTask(name: string) {
    this.schedulerRegistry.getCronJob(name).start();
    this.logger.warn(`已启用定时任务: ${name}`);
  }

  removeTask(name: string) {
    this.schedulerRegistry.deleteCronJob(name);
    this.logger.warn(`已删除定时任务: ${name}`);
  }

  getTasks() {
    const jobs = this.schedulerRegistry.getCronJobs();
    jobs.forEach((value, key) => {
      const nextDate = value.nextDate().toDate();
      this.logger.log(`定时任务 ${key} 的下次执行时间: ${nextDate}`);
    });
  }
}

常见问题与解决方案

1. 定时任务不执行

可能原因

  • 定时任务模块未正确导入
  • cron表达式语法错误
  • 任务执行时间设置错误
  • 应用进程被终止

解决方案

  • 确保在根模块中正确导入ScheduleModule.forRoot()
  • 使用在线cron表达式生成器验证表达式正确性
  • 检查系统时间和时区设置
  • 确保应用进程持续运行(使用PM2等进程管理工具)

2. 定时任务执行多次

可能原因

  • 应用启动了多个实例
  • 定时任务被重复注册

解决方案

  • 在分布式环境中使用分布式锁(如Redis)确保任务只在一个实例上执行
  • 检查代码确保任务只被注册一次

3. 定时任务执行超时

可能原因

  • 任务执行时间过长
  • 任务中存在阻塞操作

解决方案

  • 将长时间运行的任务拆分为多个小任务
  • 使用异步操作和非阻塞I/O
  • 设置合理的超时机制

最佳实践

  1. 任务分离:将不同类型的定时任务分离到不同的服务中,提高代码可维护性
  2. 错误处理:为每个定时任务添加完善的错误处理机制,确保任务失败不会影响其他任务
  3. 日志记录:为定时任务添加详细的日志记录,便于排查问题
  4. 任务监控:实现定时任务的执行状态监控,及时发现和处理异常
  5. 任务优先级:对于重要的定时任务,设置合理的优先级和重试机制
  6. 资源管理:注意定时任务的资源消耗,避免影响应用的正常运行
  7. 测试:为定时任务编写单元测试,确保任务逻辑正确

代码优化建议

  1. 使用配置中心:将定时任务的执行时间配置到配置中心,实现动态调整
  2. 任务队列:对于耗时较长的任务,考虑使用消息队列异步处理
  3. 分布式锁:在分布式环境中使用分布式锁确保任务的唯一性
  4. 熔断机制:为定时任务添加熔断机制,避免任务失败导致的级联错误
  5. 优雅关闭:实现定时任务的优雅关闭,确保任务执行完成后再停止

总结

NestJS的定时任务模块提供了一种简洁、高效的方式来实现和管理定时任务。通过本文的学习,你应该已经掌握了:

  • 如何安装和配置定时任务模块
  • 如何使用cron表达式定义复杂的定时任务
  • 如何使用间隔任务实现简单的重复任务
  • 如何动态控制和管理定时任务
  • 定时任务的最佳实践和常见问题解决方案

定时任务在实际应用中非常常见,如数据同步、日志清理、报表生成等场景。合理使用定时任务可以大大提高系统的自动化程度和可靠性。

互动问答

  1. 以下哪个cron表达式表示每天中午12点执行?
    A. 0 0 12 * * ?
    B. 12 0 0 * * ?
    C. 0 12 0 * * ?
    D. * 12 * * *

  2. 如何在NestJS中实现一个每5分钟执行一次的定时任务?

  3. 如何动态禁用一个定时任务?

  4. 在分布式环境中,如何确保定时任务只在一个实例上执行?

  5. 定时任务执行失败时,如何实现自动重试机制?

« 上一篇 22-messaging 下一篇 » NestJS国际化