NestJS Cron任务异常:单次调用却重复执行6次
NestJS Cron任务重复执行6次的解决办法
可能的原因
- 应用启动了6个进程/实例:比如用PM2开启了6个进程、集群模式下未处理单实例Cron逻辑,或是本地不小心重复启动了服务。NestJS的Cron任务会随每个实例启动而触发,多实例自然会导致重复执行。
- 包含Cron的模块被重复导入:如果在多个模块的
imports数组中都导入了带有watcher方法的模块,Nest会创建多个模块实例,每个实例都会触发Cron任务。
针对性解决步骤
1. 检查应用实例数量
- 本地开发:查看终端是否开启了多个服务进程,关闭多余进程即可。
- 生产环境(如PM2):修改配置确保仅启动1个实例执行Cron;若必须用多进程,给Cron任务加分布式锁来限制单实例执行。
2. 避免模块重复导入
检查AppModule及其他模块的imports数组,确保带有Cron任务的模块只被导入一次,不要在多个模块中重复导入。
3. 多进程场景用分布式锁确保单实例执行
如果业务需要多进程运行,可借助Redis实现分布式锁,保证同一时间只有一个实例执行Cron任务:
import * as Redis from 'ioredis'; import { Cron, CronExpression } from '@nestjs/schedule'; // 初始化Redis客户端 const redis = new Redis({ host: '你的Redis地址' }); @Injectable() export class YourService { @Cron(CronExpression.EVERY_MINUTE) async watcher() { const lockKey = 'cron:watcher:lock'; const lockValue = Date.now().toString(); const lockExpire = 50; // 锁过期时间(单位:秒),根据任务实际执行时长调整 // 尝试获取锁 const acquired = await redis.set(lockKey, lockValue, 'EX', lockExpire, 'NX'); if (!acquired) { console.log('已有实例执行该Cron任务,本次跳过'); return; } try { // 原有业务逻辑 const now = new Date(); const openTravelRequests = await this.getOpenTravelRequestsByDate(now); // 修复异步遍历问题:map不会等待异步操作完成,改用for...of或Promise.all if (openTravelRequests.length) { // 顺序执行(适合有依赖的场景) for (const otr of openTravelRequests) { await this.assignNewBoatToOpenTravelRequests(otr._id); } // 或并行执行(适合无依赖的场景) // await Promise.all(openTravelRequests.map(otr => this.assignNewBoatToOpenTravelRequests(otr._id))); } } finally { // 释放锁,避免死锁 const currentValue = await redis.get(lockKey); if (currentValue === lockValue) { await redis.del(lockKey); } } } }
4. 修复代码中的异步遍历问题
你原本使用map(async)的方式不会等待所有异步操作完成,任务会直接结束,可能引发隐藏问题,建议改成for...of(顺序执行)或Promise.all(并行执行),上述代码已包含该修复。
额外排查点
检查@Module()装饰器的providers数组,确认该服务是否被重复注册,若多个模块都注册了该服务,也会导致多实例触发Cron任务。
内容的提问来源于stack exchange,提问作者Oğuzhan KAYA
相关产品推荐
相关产品推荐

