定时任务
@nestjs/schedule 基于 cron 表达式调度任务。
NestJS 的 @nestjs/schedule 模块,是一个基于装饰器和 cron(kelektiv/node-cron,npm 包名为 cron,注意与另一个同名不同源的 node-cron 包区分)库的轻量级任务调度工具。
它通过声明式装饰器和动态调度 API,为 Cron、Interval 和 Timeout 三类任务提供了开箱即用的支持。
一、声明式任务:@Cron, @Interval, @Timeout
在使用前,需要在根模块(通常是 AppModule)中导入并初始化 ScheduleModule:
import { Module } from "@nestjs/common";
import { ScheduleModule } from "@nestjs/schedule";
@Module({
imports: [ScheduleModule.forRoot()],
// ...
})
export class AppModule {}
初始化完成后,便可通过以下三种装饰器来定义任务:
1. @Cron:Cron 表达式任务
这是最常用、最强大的任务调度方式,通过标准的 Cron 表达式定义执行周期。在下面的例子中,任务每 10 秒触发一次:
import { Injectable, Logger } from "@nestjs/common";
import { Cron } from "@nestjs/schedule";
@Injectable()
export class TasksService {
private readonly logger = new Logger(TasksService.name);
@Cron("*/10 * * * * *") // Cron 表达式
handleCron() {
this.logger.debug("Cron 任务执行");
}
}
- Cron 表达式参考:
* * * * * *:每一秒45 * * * * *:每分钟的第 45 秒0 10 * * * *:每小时的第 10 分钟0 */30 9-17 * * *:每天 9 点到 17 点之间,每 30 分钟
此外,还支持 Date 对象和内置枚举 CronExpression(如 CronExpression.EVERY_DAY_AT_10AM)。
2. @Interval:固定间隔任务
适用于不需要精确到秒的、固定周期执行的任务。下面的代码会每 10 秒执行一次。
import { Injectable, Logger } from "@nestjs/common";
import { Interval } from "@nestjs/schedule";
@Injectable()
export class TasksService {
private readonly logger = new Logger(TasksService.name);
@Interval(10000) // 单位:毫秒,即 10 秒
handleInterval() {
this.logger.debug("固定间隔任务执行");
}
}
3. @Timeout:一次性延迟任务
它只会在应用启动后的指定时间后运行一次。
import { Injectable, Logger } from "@nestjs/common";
import { Timeout } from "@nestjs/schedule";
@Injectable()
export class TasksService {
private readonly logger = new Logger(TasksService.name);
@Timeout(5000) // 5 秒后执行一次
handleTimeout() {
this.logger.debug("一次性任务执行");
}
}
🎮 二、动态调度:SchedulerRegistry
ScheduleModule 的核心在于它的 SchedulerRegistry,它不仅管理着由装饰器创建的任务,还能让你在程序运行时动态地创建、启动、停止或删除任务。
下面的例子演示了如何动态添加一个 Cron 任务。
import { Injectable, OnModuleInit } from "@nestjs/common";
import { SchedulerRegistry } from "@nestjs/schedule";
import { CronJob } from "cron";
@Injectable()
export class DynamicTaskService {
constructor(private schedulerRegistry: SchedulerRegistry) {}
addDynamicCronJob(cronId: string, seconds: string) {
// 1. 创建 CronJob 实例
const job = new CronJob(`${seconds} * * * * *`, () => {
console.log(`动态任务 ${cronId} 执行!`);
});
// 2. 添加到注册表
this.schedulerRegistry.addCronJob(cronId, job);
job.start();
console.log(`动态任务 ${cronId} 已添加并启动。`);
}
getCronJob(cronId: string) {
return this.schedulerRegistry.getCronJob(cronId);
}
deleteCronJob(cronId: string) {
this.schedulerRegistry.deleteCronJob(cronId);
console.log(`动态任务 ${cronId} 已删除。`);
}
}
类似地,通过 addInterval、addTimeout 等方法也能动态添加对应的任务。
🌐 三、分布式锁:防止多实例重复执行
@nestjs/schedule 模块本身在 PM2 等集群模式下不提供防止任务重复执行的机制。因此,当应用同时运行多个实例时,同一个定时任务会在所有实例上被触发。
为了解决这个问题,实践中通常采用分布式锁(Redis 分布式锁)的方案。
🛠️ 方案一:集成 @nestjs/bull 与 bull
这是一种非常健壮的方案。它将任务逻辑拆分为两个部分:
- 任务调度器:使用
@Cron装饰器,但内部只是将任务信息添加到队列中。 - 队列处理器:由
Bull队列负责,而Bull基于 Redis,天然就具备分布式特性,能保证一个任务只被一个消费者进程处理。
// cron-job.service.ts (调度器)
import { Injectable } from "@nestjs/common";
import { Cron } from "@nestjs/schedule";
import { InjectQueue } from "@nestjs/bull";
import { Queue } from "bull";
@Injectable()
export class CronJobService {
constructor(@InjectQueue("my-queue") private myQueue: Queue) {}
@Cron("*/5 * * * * *") // 定时触发
async scheduleJob() {
await this.myQueue.add("my-job", {
/* 任务数据 */
}); // 将任务加入队列
}
}
// my-queue.processor.ts (处理器)
import { Processor, Process } from "@nestjs/bull";
import { Job } from "bull";
@Processor("my-queue")
export class MyQueueProcessor {
@Process("my-job")
async handleCronJob(job: Job) {
console.log("开始处理队列中的任务...", job.id);
// 在这里执行真正的业务逻辑
}
}
⚡ 方案二:使用 @RedisLock() 装饰器
对于不需要用到任务队列的场景,可以使用 nestjs-simple-redis-lock 这个第三方模块。它能以简洁的装饰器形式,为方法添加分布式锁的能力(具体参数以其官方文档为准,不同第三方库的 API 差异较大,接入前请仔细核对版本说明)。
import { Injectable } from "@nestjs/common";
import { RedisLock, RedisLockService } from "nestjs-simple-redis-lock";
@Injectable()
export class MyService {
constructor(private readonly lockService: RedisLockService) {}
// 参数依次为:锁名称、过期时间(毫秒,默认 60000)、重试间隔(毫秒)、最大重试次数
@RedisLock("critical-task", 10000)
async criticalTask() {
console.log("执行关键任务,此过程被分布式锁保护");
// ... 任务逻辑
}
}
总而言之,@nestjs/schedule 是一款功能全面、设计优雅的任务调度模块。对于绝大多数单实例的应用,用其提供的声明式 API 就足够了;而当面对多实例部署的挑战时,Bull 和 nestjs-simple-redis-lock 等分布式方案也提供了成熟的应对思路。
参考文献
以下链接在编写时均可正常访问:
| 资料 | 说明 |
|---|---|
| NestJS 文档 | 官方 |
| Task scheduling | 本章主题 |
| Request lifecycle | 执行顺序 |
相关文章
认证与授权
认证(Authentication):确认「你是谁」(如 JWT、Session)。 - 授权(Authorization):确认「你能做什么」(如 RBAC、策略检查)。
缓存
@nestjs/cache-manager 统一缓存 API,存储实现可插拔。
提供者与服务
Service 是最常见的 Provider,封装业务逻辑。见 module。
守卫 (Guards) 与授权
守卫决定是否放行请求,常用于认证与授权。见 auth。
配置管理(Config 模块)
@nestjs/config 加载 .env 并提供 ConfigService。见 工程化 env。
数据库集成(以 TypeORM 为例)
Nest 通过 @nestjs/typeorm 等包集成 ORM;生产环境用 migration,慎用 synchronize。亦可选用 Prisma、MikroORM 等(见 官方 Database)。