You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何在NestJS中避免PM2集群模式下定时任务的数据库锁问题

解决PM2集群模式下NestJS定时任务重复执行的问题

PM2的cluster模式会启动多个独立的应用进程,每个进程都会初始化NestJS应用实例,包括注册定时任务,因此多个集群节点会同时触发同一定时任务,进而导致数据库锁冲突这类问题。下面提供几种可行的解决思路:

方法一:仅让PM2主进程执行定时任务

PM2在cluster模式下会给每个进程设置NODE_APP_INSTANCE环境变量,主进程的该值为0,其他子进程为1、2...。利用这个变量可以控制只有主进程才执行定时任务。

方式1:在任务方法中判断

import { Injectable } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';

@Injectable()
export class TasksService {
  private readonly isMainInstance = process.env.NODE_APP_INSTANCE === '0';

  @Cron('0 0 * * *') // 示例:每天凌晨执行
  async handleDailyTask() {
    // 非主进程直接跳过
    if (!this.isMainInstance) return;

    // 你的定时任务逻辑
    console.log('主进程执行定时任务');
  }
}

方式2:全局禁用非主进程的定时任务

在ScheduleModule初始化时直接控制启用状态:

import { Module } from '@nestjs/common';
import { ScheduleModule } from '@nestjs/schedule';
import { TasksService } from './tasks.service';

@Module({
  imports: [
    ScheduleModule.forRoot({
      enabled: process.env.NODE_APP_INSTANCE === '0',
    }),
  ],
  providers: [TasksService],
})
export class TasksModule {}

方法二:使用分布式锁实现任务互斥

如果担心主进程宕机导致定时任务中断,可以用分布式锁(基于Redis、数据库等)确保同一时间只有一个进程能执行任务,适合高可用场景。

以Redis为例的实现:

import { Injectable } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import { Redis } from 'ioredis'; // 或你使用的Redis客户端

@Injectable()
export class TasksService {
  private readonly redisClient = new Redis({ /* 你的Redis配置 */ });

  @Cron('0 0 * * *')
  async handleDailyTask() {
    const lockKey = 'task:daily-job:lock';
    const lockValue = Date.now().toString();
    const lockExpireMs = 60 * 1000; // 锁过期时间,防止死锁

    // 尝试获取排他锁
    const lockAcquired = await this.redisClient.set(lockKey, lockValue, 'NX', 'PX', lockExpireMs);
    if (!lockAcquired) {
      console.log('已有进程在执行任务,本次跳过');
      return;
    }

    try {
      // 执行你的任务逻辑
      console.log('获取到锁,执行定时任务');
    } finally {
      // 仅释放自己获取的锁,避免误删其他进程的锁
      const currentLockValue = await this.redisClient.get(lockKey);
      if (currentLockValue === lockValue) {
        await this.redisClient.del(lockKey);
      }
    }
  }
}

方法三:分离业务进程与定时任务进程

将定时任务单独部署为一个PM2实例,用fork模式运行(单进程),业务服务继续用cluster模式,通过环境变量控制是否启用定时任务。

修改你的ecosystem.js配置:

module.exports = {
  apps: [
    {
      name: 'my_app',
      script: 'dist/main.js',
      instances: 0,
      exec_mode: 'cluster',
      listen_timeout: 10000,
      kill_timeout: 1000,
      env: {
        DISABLE_SCHEDULE: 'true', // 业务进程禁用定时任务
      },
    },
    {
      name: 'my_app_schedule',
      script: 'dist/main.js',
      instances: 1,
      exec_mode: 'fork',
      listen_timeout: 10000,
      kill_timeout: 1000,
      env: {
        DISABLE_SCHEDULE: 'false', // 定时任务进程启用任务
      },
    },
  ],
};

然后在NestJS中根据环境变量控制定时任务的启用:

ScheduleModule.forRoot({
  enabled: process.env.DISABLE_SCHEDULE !== 'true',
})

内容的提问来源于stack exchange,提问作者user10874312

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 02:35:21