본문 바로가기

웹/Nest

[Nest] Nestjs에서 microService를 이용할 때 cron이 여러번 나타는 경우

😮 문제상황

nest에서 cron을 통해서 스케줄링 작업을 하려하는데 한 스케줄러가 여러번 실행되었습니다. 추적을 해보니 microService객체도 스케줄링을 실행하고 있었습니다. 저의 경우에는 mqtt와 redis를 microservice로 쓰고 있었는데요. cron작업을 했을 때 3개가 돌아갔습니다. cron작업을 1개만 실행하기 위한 해결책을 공유하기 위한 글입니다.

이를 해결하기 위한 방법을 찾아보니 여러개가 있었습니다.

  1. 스케줄러만 동작시키는 분리된 nest 프로세스를 만들기
  2. cron job을 백그라운드로 돌리는 api를 만들기
  3. Buil Queue(?)라는 것을 사용해서 프로세서를 핸들링하기
  4. db를 locking 해서 스케줄러의 영향을 줄이기

 

 

저는 1번을 선택해서 스케줄러만 동작시키는 nest 컨테이너를 만들어보겠습니다.

main.ts

import process from "process";

async function bootstrap() {
    // 1. http서버로 사용
    const app = await NestFactory.create(AppModule);
    // 2. mqtt서버로 사용
    console.log(process.env.MQTT_HOST, process.env.MQTT_PORT);
    const mqttApp = await NestFactory.createMicroservice<MicroserviceOptions>(
        AppModule,
        {
            transport: Transport.MQTT,
            options: {
                url: `mqtt://${process.env.MQTT_HOST}:${process.env.MQTT_PORT}`,
            },
        },
    );

    // 3. redis서버로 사용
    const redisApp = await NestFactory.createMicroservice<MicroserviceOptions>(
        AppModule,
        {
            transport: Transport.REDIS,
            options: {
                host: `${process.env.REDIS_HOST}`,
                port: +process.env.REDIS_PORT,
            },
        },
    );

    const port = process.env.PORT || 3000;

    // cors 설정
    app.enableCors();
    await mqttApp.listen();
    await redisApp.listen();
    await app.listen(port);
}

기존의 저의 main.ts 파일입니다.

worker라는 스케줄러만 돌리는 모듈을 만들고 worker모듈로 새로운 nestFactory를 만들것입니다.

 

🟩 스케줄러 생성하기

worker.module.ts

import { Module } from '@nestjs/common';
import { WorkerService } from './worker.service';
import { TypeOrmModule } from '@nestjs/typeorm';
import { Amr } from '../amr/entities/amr.entity';
import { AmrCharger } from '../amr-charger/entities/amr-charger.entity';
import { AmrChargeHistory } from '../amr-charge-history/entities/amr-charge-history.entity';
import { Hacs } from '../hacs/entities/hacs.entity';
import { MqttModule } from '../mqtt.module';
import { ConfigModule } from '@nestjs/config';
import { mssqlConfig, postgresConfig } from '../config/db.config';
import { ScheduleModule } from '@nestjs/schedule';
import { HacsModule } from '../hacs/hacs.module';
import { AmrService } from '../amr/amr.service';

@Module({
  imports: [
    //env 파일 사용
    ConfigModule.forRoot({
      isGlobal: true, // 전역으로 사용하기
    }),
    // // DB 연결
    // PostgreSQL 연결 설정
    TypeOrmModule.forRootAsync({
      useFactory: async () => {
        return postgresConfig;
      },
    }),
    // MSSQL 연결 설정
    TypeOrmModule.forRootAsync({
      name: 'mssqlDB',
      useFactory: async () => {
        return mssqlConfig;
      },
    }),

    // mqtt 모듈설정
    MqttModule,

    // schedule 모듈 설정
    ScheduleModule.forRoot(),

    HModule,
    WorkerModule,
    TypeOrmModule.forFeature([A, B, C]),
    TypeOrmModule.forFeature([H], 'mssqlDB'),
  ],
  providers: [WorkerService, AService],
})
export class WorkerModule {}

app.module.ts에서와 마찬가디로 db연결을 합니다. 그리고 다른 microService모듈들도 import 합니다. scheduler 모듈도 import가 필요합니다.

그리고 worder.service.ts에서 사용할 모듈들도 import 합니다. 스케줄러에서 동작할 service들을 가지고 오는 것입니다.

 

worker.service.ts

import { Injectable } from '@nestjs/common';
import { Cron } from '@nestjs/schedule';
import { AService } from '../a/a.service';

@Injectable()
export class WorkerService {
  constructor(private readonly aService: AService) {}
  @Cron('*/10 * * * * *', {
    name: 'aCronJobTest',
    timeZone: 'Asia/Seoul',
  })
  InitialScheduler() {
    const min = 5;
    const max = 10;
    const randomInRange = Math.random() * (max - min) + min; // min 이상 max 미만의 난수를 반환
    console.log(randomInRange0)
    // this.aService.createAByMssql(); [원하는 service]
  }
}

예시로 10초마다 동작하는 cron을 만들어봤습니다. aService의 createAByMssql이라는 것을 동작태스트 하기 전에 random 변수를 출력시키는 것이 있습니다. 태스트를 해보고 원하는 service로 교채하면 됩니다.

 

🟩 스케줄러 적용하기

main.ts

import { NestFactory } from '@nestjs/core';
import { AppModule } from './app.module';
import { DocumentBuilder, SwaggerModule } from '@nestjs/swagger';
import { ResponseInterceptor } from './lib/interceptor/response.interceptor';
import { HttpExceptionFilter } from './lib/filter/httpExceptionFilter';
import { TypeOrmExceptionFilter } from './lib/filter/typeOrmException.filter';
import { ValidationPipe } from '@nestjs/common';
import { MicroserviceOptions, Transport } from '@nestjs/microservices';
import * as process from 'process';
import { WorkerModule } from './worker/worker.module';

declare const module: any;

async function bootstrap() {
    // 1. http서버로 사용
    const app = await NestFactory.create(AppModule);
    // 2. mqtt서버로 사용
    const mqttApp = await NestFactory.createMicroservice<MicroserviceOptions>(
        AppModule,
        {
            transport: Transport.MQTT,
            // options: { host: 'localhost', port: 1833, url: 'mqtt://localhost:1883' },
            options: {
                url: `mqtt://${process.env.MQTT_HOST}:${process.env.MQTT_PORT}`,
            },
        },
    );

    // 스케줄러 process 생성
    const sheduler = await NestFactory.create(WorkerModule);

    // 3. redis서버로 사용
    const redisApp = await NestFactory.createMicroservice<MicroserviceOptions>(
        AppModule,
        {
            transport: Transport.REDIS,
            options: {
                host: `${process.env.REDIS_HOST}`,
                port: +process.env.REDIS_PORT,
            },
        },
    );

    const port = process.env.PORT || 3000;
    // cors 설정
    app.enableCors();
    await mqttApp.listen();
    await redisApp.listen();
    await app.listen(port);
    await sheduler.init(); // 스케줄러 process 적용
}

bootstrap();
const sheduler = await NestFactory.create(WorkerModule); // 스케줄러 process 생성
await sheduler.init(); // 스케줄러 process 적용

스케줄러를 NestFactory.create()로 생성합니다. 아예 새로운 Nest 객체를 만드는 것입니다. 이 객체는 schedule동작만 실행합니다.

 

결론

여러가지 방법이 있겠지만 저는 그나마 간단한 방법으로 해결했습니다. 그런데 아직 왜 microService를 사용했을 때 cron이 여러번 발생되는지 원인은 모르겠습니다.

 

참고

728x90