@Scheduled 스케쥴러 사용

niz w·2024년 11월 19일

Spring

목록 보기
5/17

다른 DB에서 부서 DB 서버로 데이터를 끌어오기로 했다.
서로 방해되지 않도록 새벽에 한 번씩 실행하기로 해서 스케줄러를 시도했다.

카프카를 통해 실시간 DB 변화를 감지해도 되지만... 테스트 단계로 데이터가 많지 않아 스케줄러로 진행했다.




1. Application 설정

package com.[프로젝트];

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@EnableScheduling
@SpringBootApplication
public class Application {

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

}

@Scheduled를 사용하기 위해서는 먼저 Application 클래스에서 @EnableScheduling을 설정해주어야 한다. 그래야 스케줄러 어노테이션이 붙은 설정 파일이 실행된다고 한다.




2. 시간 설정

해당 기능이 작동하는 시간은 추후 변경하기 쉽도록 properties에 해뒀다.
yaml을 쓰는 사람은 해당 파일에 작성해두면 된다.

위에 schedule.cron이 해당 시간을 설정한 것이고,
아래의 schedule.lastWorkTimeHour는 그 후에 작업된 데이터만 불러오기 위해 별도로 설정한 값이다.



2-1. CRON 작성 방법

cronScheduling의 정규 표현식이며, 리눅스의 크론탭 기능과 유사하게 설정할 수 있다고 한다.
(리눅스의 crontab은 특정 시간에 프로그램을 실행시키기 위해 사용하는 것으로, 윈도우의 스케줄러와 비슷하다.)

👉 필드

일단, 설정 유형에는 6가지가 있다. (연도는 생략가능하여 보통 6가지로 많이 쓴다!)

각 필드에 아래 표에 따라 원하는 값 또는 특수문자를 입력하여 시간을 설정하면 된다.

필드 명값의 허용 범위허용된 특수문자
초 (Seconds)0 ~ 59, - * /
분 (Minutes)0 ~ 59, - * /
시 (Hours)0 ~ 23, - * /
일 (Day)1 ~ 31, - * ? / L W
월 (Month)1 ~ 12 or JAN ~ DEC, - * /
요일(Week)0 ~ 6 or SUN ~ SAT, - * ? / L #

👉 특수문자

  • * : 모든 값을 의미
  • ? : 특정한 값이 없음을 의미
  • - : 범위를 나타낼 때 사용
  • , : 특정 값을 여러 개 나열할 때 사용
  • / : <시작 시간/단위> 형태로 쓰며, 0분부터 매 5분은 0/5라고 표시
  • L : 일 또는 요일 필드에서 사용하며, 마지막 일이나 요일(토)을 의미
  • W : 가장 가까운 평일을 의미
  • # : 몇 째 주의 무슨 요일인지 표현하며, <요일#주>의 형태로 '3#2'는 2째주의 수요일을 의미

👉 예시

  • 0 0/5 * * * ? : 매 5분마다 실행
  • 0 0 0/1 * * ? : 매 1시간마다 실행
  • 0 0 12 * * ? : 매일 낮 12시에
  • 0 20 11 ? * * : 매일 오전 11:20분에 실행
  • 0 20 11 * * ? : 매일 오전 11:20분에 실행
  • 0 18 * ? : 매일 오후 6:00에 시작해서 매분마다 실행하고 오후 6:59분에 종료 (해당 한 시간 동안 매분 실행)
  • 0 0/5 14,18 * * ? : 매일 오후 2:00에 시작해서 5분마다 실행되어 오후 2:55에 끝나고, 오후 6:00에 시작하여 5분마다 실행되어 오후 6:55에 종료
  • 0 0-5 18 * * ? : 매일 오후 6:00에 시작하여 매분마다 실행하고 오후 6:05분에 종료
  • 0 10,44 14 ? 3 WED : 3월의 수요일마다 오후 2:10과 2:44 실행
  • 0 15 10 ? * MON-FRI : 주중 오전 10:15분에 실행
  • 0 15 10 15 * ? : 매달 15일 오전 10:15에 실행
  • 0 15 10 L * ? : 매월 말일 오전 10:15에 실행
  • 0 15 10 ? * 6L : 매월 마지막 금요일 오전 10:15에 실행
  • 0 15 10 ? * 6#3 : 매월 3째주 금요일 오전 10:15에 실행


2-2. 그 외 @Scheduled 속성

🎈fixedDelay

작업 수행시간과 상관없이 일정 주기마다 메소드 호출!

@Scheduled(fixedDelay = 1000)
public void run() {
	log.info("Scheduler 실행");
}

miliseconds 단위로 표시하며, 위의 예시는 1초를 의미한다.
이전 Task가 종료되고 1초가 지나면 해당 메소드를 실행하라는 의미가 된다.
fixedDelayString도 있는데... 이건 해당 숫자를 문자열 값으로 표현한다는 의미가 된다.


🎈fixedRate

작업 수행시간을 포함하여 작업을 마친 후부터 주기 타이머가 돌아가도록!

@Scheduled(fixedRate = 1000)
public void run() {
	log.info("Scheduler 실행");
}

단위는 동일하며, 이전 Task의 시작 시점부터 시간을 측정하게 된다.
fixedRateString을 사용하면 해당 시간을 문자열로 표현한다는 의미가 된다.


🎈initDelay

메소드가 등록되지마자 수행하지 않고, 초기 지연시간 설정!

@Scheduled(fixedRate = 5000, initialDelay = 3000)
public void run() {
	log.info("Scheduler 실행");
}

메소드가 등록되고 초기 지연시간을 3초 두는 예시이다.
위의 예시로는 메소드가 등록되면 3초의 대기시간을 가진 후에 5초마다 실행하라는 의미가 된다.
마찬가지로 initialDelayString은 문자열 값이 된다.




3. 스케줄러 구현

Scheduler를 사용할 코드를 작성해보면, config로 두고 해당 메소드를 작성해주었다.

작업을 두 가지 진행해야 하므로, 각각의 메소드로 분리해주고
두 메소드를 통합한 최종 본에만 @Scheduled를 달아주었다.
여기서 크론 값은 properties에 놓은 값을 불러와 사용하도록 했다.

이렇게 작성하면 아래의 코드가 새벽 3시에 작동을 하게 된다.

@Configuration
@RequiredArgsConstructor
@Slf4j
public class SchedulerConfig {

    private final LabService labService;

    @Scheduled(cron = "${schedule.cron}")
    public void copyLabToBm() {
        try {
            log.info("Copy Lab To BM Start!");
            copyAndPasteUser();
            copyAndPasteNewRecord();
            log.info("Scheduled task completed successfully!");
        } catch (Exception e) {
            log.error(e.getMessage());
        }
    }

    /** user 정보 복사 */
    public void copyAndPasteUser() {
        try {
            log.info("Copy User Start!");
            labService.copyAndPasteUser();
            log.info("User Paste Task completed successfully!");
        } catch (Exception e) {
            log.error(e.getMessage());
        }
    }

    /** 활동 기록 정보 복사(전날 새벽 3시 이후의 데이터) */
    public void copyAndPasteNewRecord() {
        try {
            log.info("Copy Record Start!");
            labService.copyAndPasteNewRecords();
            log.info("Record Paste Task completed successfully!");
        } catch (Exception e) {
            log.error(e.getMessage());
        }
    }
}

만약 여기서 user를 먼저 불러온 뒤 기록을 다시 불러오고 싶다면 별개로 cron을 설정할 수도 있다.

@Configuration
@RequiredArgsConstructor
@Slf4j
public class SchedulerConfig {

    private final LabService labService;

    /** user 정보 복사 */
    @Scheduled(cron = "0 0 3 * * ?")
    public void copyAndPasteUser() {
        try {
            log.info("Copy User Start!");
            labService.copyAndPasteUser();
            log.info("User Paste Task completed successfully!");
        } catch (Exception e) {
            log.error(e.getMessage());
        }
    }

    /** 활동 기록 정보 복사(전날 새벽 3시 이후의 데이터) */
    @Scheduled(cron = "3 0 3 * * ?")
    public void copyAndPasteNewRecord() {
        try {
            log.info("Copy Record Start!");
            labService.copyAndPasteNewRecords();
            log.info("Record Paste Task completed successfully!");
        } catch (Exception e) {
            log.error(e.getMessage());
        }
    }
}

두 번째 메소드는 3초뒤에 실행하도록 크론을 다시 설정해주었다.
만약 어노테이션을 하나만 두려면 Thread.sleep(3000);를 써서 한 메소드에서 처리하게 해도 된다.


🚨 처음에 cron은 동일하게 설정하고 initDelay로 지연시간을 주려했지만! 두 가지는 함께 쓸 수 없다고 한다.
cron만 쓰거나, 아니면 나머지 속성들을 조합해서 쓰거나!!

0개의 댓글