Web & Server/Spring & Spring Boot

Spring에서 스케줄 기능 사용하기 How to use scheduled task in Spring

Simplify - Jonghun 2019. 12. 27. 11:33

스프링 프레임워크 처럼 서버 측 개발을 진행하는 동안, 자연스럽게 스케줄된 작업이 필요한 경우가 있습니다. 물론 여러가지 방법으로 이를 구현할 수 있고, 리눅스 환경에서는 간단한 프로그램이나 쉘(shell) 명령어를 crontab 에 등록해서 사용할 수 있습니다. 

 

별도의 배치 프로그램을 사용하는 경우는, 서로 독립적인 프로세스로 동작하기 때문에 개발로 실행되고 관리될 수 있고, 따라서 프로그램들간에 서로 영향을 주지 않고 실행될 수 있습니다. 하지만 별도로 관리되므로 중복 코드가 발생하고 업데이트 하는 데 있어서 불편한 점 등 당연한 장단점이 존재합니다. 

 

어느 하나의 방법이 좋다 라기 보다는, 어떤 상황이든 각 상황에 맞게 취사선택하여 개발 및 구현하는게 맞습니다. 여기서는 서버 온라인 프로그램 내에 스케줄로 등록하여 배치 실행을 할 수 있는 @Scheduled annotation 에 대해 살펴볼 예정입니다.

 

기본사항

기본적으로 다음 조건들을 만족하고 있어야 합니다.

  • 메소드는 void 리턴을 해야하고,
  • 어떠한 파라메터도 받을 수 없습니다.

이러한 전제조건 때문에 어렵게 느껴질 수도 있겠지만, 특정 상황에서 단독으로 동작하는 기능이기 때문에 이해가 가는 부분입니다.

 

 

스케줄 기능 사용 활성화

@Scheduled annotation 을 사용하려면, 다음과 같이 활성화해주는 작업이 필요합니다. 다음의 두 가지 방법으로 활성화할 수 있습니다.

 

Java 의 enable-style annotation 이용

@Configuration
@EnableScheduling
public class SpringConfig {
    ...
}

xml 설정을 이용

<task:annotation-driven>

사실 저는 xml 을 이용해서 설정하는 것 보다는 annotation 을 이용하는 것을 더 선호합니다. 다른 이유는 없고, code 를 통하는 것이 xml 등의 config 파일을 이용하는 것 보다 컴파일에러나 잘못 설정한 부분 등을 더 명확하게 잡아낼 수 있기 때문입니다. 각자 선호하는 방식으로 하면 될 것 같습니다.

 

일정 간격으로 실행하기

다음과 같은 설정으로 일정 간격으로 실행할 수 있습니다.

@Scheduled(fixedDelay = 1000)
public void scheduleFixedDelayTask() {
    System.out.println(
      "Fixed delay task - " + System.currentTimeMillis() / 1000);
}

여기서 주의해야 할 점이 몇 가지가 있습니다. 

  • 이 '간격' 이라고 하는 'fixed delay' 는 앞선 task 의 종료부터 다음 task 의 시작 까지의 간격을 의미합니다. 따라서 앞의 task 가 오래 걸렸다면, 다음 task 의 실행 역시 늦게 실행될 수 밖에 없습니다. 
  • 위와 같은 이유로, 이 설정으로 실행되는 task 는 동기화된 형태로 실행됩니다. (synchronous)

앞선 동작의 종료가 보장되어야 하는 작업에서 유리합니다. (예. 통계 데이터 생성)

 

일정 주기로 실행하기 

다음 설정으로 일정 주기로 실행하는 것이 가능합니다. 

@Scheduled(fixedRate = 1000)
public void scheduleFixedRateTask() {
    System.out.println(
      "Fixed rate task - " + System.currentTimeMillis() / 1000);
}

이 설정은 각각의 task들이 독립적인(independent) 경우에 사용합니다.

 

여기서 주의해야 할 사항은 각각의 task 들이 병렬(parallel)로 실행되지 않는다는 점 입니다. 따라서 앞선 작업이 종료되지 않으면 앞선 작업의 종료를 기다리게 됩니다. 

 

만약 병룔로 실행 - 즉, Asynchronous 하게 - 실행하고 싶다면, 다음과 같이 @async annotation 을 넣어줍니다. 

@EnableAsync
public class ScheduledFixedRateExample {
    @Async
    @Scheduled(fixedRate = 1000)
    public void scheduleFixedRateTaskAsync() throws InterruptedException {
        System.out.println(
          "Fixed rate task async - " + System.currentTimeMillis() / 1000);
        Thread.sleep(2000);
    }
}

이 task는 매 초마다(1000ms = 1s) 실행되며, 앞선 task 의 종료 여부에 영향을 받지 않습니다.

 

fixedRate vs. fixedDelay

일정 간격으로 실행(fixedDelay) 하는 것은 앞선 task가 종료된 뒤 일정 시간 이후에 실행되므로 얼마나 자주 실행되지 여부를 판단하기 쉽지 않다. 반면에 일정 주기로 실행하는 것은 일정 간격으로 task 가 trigger되므로 메모리 leak 등을 신경쓰지 않아도 됩니다.

하지만 메모리 문제등을 야기할 가능성이 있다면 'out of memory' 에러가 발생할 것이다. 

 

최초 지연시간을 두고 task 실행하기

아래 설정으로 최초에 지연시간을 설정할 수 있습니다.

@Scheduled(fixedDelay = 1000, initialDelay = 1000)
public void scheduleFixedRateWithInitialDelayTask() {
  
    long now = System.currentTimeMillis() / 1000;
    System.out.println(
      "Fixed rate task with one second initial delay - " + now);
}

 

cron expression을 이용한 설정

@Scheduled(cron = "0 15 10 15 * ?")
public void scheduleTaskUsingCronExpression() {
  
    long now = System.currentTimeMillis() / 1000;
    System.out.println(
      "schedule tasks using cron jobs - " + now);
}

 

스케줄 설정을 변수화하기

properties 파일에 설정된 정보를 이용하여 다음과같이 설정할 수 있습니다.

 

고정 지연시간

@Scheduled(fixedDelayString = "${fixedDelay.in.milliseconds}") 

 

일정 간격

@Scheduled(fixedRateString = "${fixedRate.in.milliseconds}") 


cron expression 이용

@Scheduled(cron = "${cron.expression}")

 

xml 파일에 설정하기

다음과 같이 xml 파일에 설정할 수 있습니다.

<!-- Configure the scheduler -->
<task:scheduler id="myScheduler" pool-size="10" />
 
<!-- Configure parameters -->
<task:scheduled-tasks scheduler="myScheduler">
    <task:scheduled ref="beanA" method="methodA"
      fixed-delay="5000" initial-delay="1000" />
    <task:scheduled ref="beanB" method="methodB"
      fixed-rate="5000" />
    <task:scheduled ref="beanC" method="methodC"
      cron="*/5 * * * * MON-FRI" />
</task:scheduled-tasks>

 

출처/참고자료