본문 바로가기
프로그래밍/Spring

[Spring] Scheduler 어떤걸 사용해야 할까 ? - Spring Scheduler와 Spring Quartz

by 사바라다 2020. 9. 15.

일정한 시간간격 또는 일정한 시각에 특정 로직을 돌리기 위해서 사용하는 것을 Scheduler라고 합니다. 이런 Scheduler는 Spring에서 Spring Scheduler와 Spring Quartz라는 2가지 방식으로 제공됩니다. 1가지 일에 2가지 방식을 Spring에서 제공하다니... 어떤걸 쓰면 좋을까요? 오늘은 2가지의 공통점과 차이점 그리고 내 프로젝트에서는 어떤 Library를 사용하면 좋을지 알아보는 시간을 가져보도록 하겠습니다.

사용법

먼저 각 라이브러리를 어떻게 사용할 수 있는지 확인해보겠습니다. 먼저 말씀 드리자면 Spring Scheduler를 사용하는 것보도 Spring Quartz를 사용하는 것이 좀 더 복잡하며 어렵습니다.

Spring Scheduler

Spring Scheduler는 별도의 추가적인 의존성이 필요하지 않습니다. Spring Boot starter에 기본적인 의존성으로 제공됩니다. 사용하기 위해서는 @EnableScheduling 어노테이션을 붙여주면 됩니다.

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

이런 설정을 잡아 준 후 실제 사용은 아래와 같이 사용할 수 있습니다. @Scheduled 어노테이션 메서드에 붙여 주기만 하면 됩니다. 해당 어노테이션의 내부 값으로 scheduler가 실행 될 타이밍을 정할 수 있습니다. scheduling을 할 메서드는 2개의 조건을 가집니다.

  1. method는 void의 return 타입을 가져야합니다.
  2. method는 파라미터를 가질 수 없습니다.

그리고 아래처럼 사용할 수 있습니다. 내용은 각 주석을 봐 주시기 바랍니다.

public class Scheculer() {

    @Scheduled(fixedDelay = 1000) // scheduler 끝나는 시간 기준으로 1000 간격으로 실행
    public void scheduleFixedDelayTask() {
        System.out.println(
        "Fixed delay task - " + System.currentTimeMillis() / 1000);
    }

    @Scheduled(fixedRate = 1000) // scheduler 시작하는 시간 기준으로 1000 간격으로 실행
    public void scheduleFixedRateTask() {
        System.out.println(
        "Fixed rate task - " + System.currentTimeMillis() / 1000);
    }

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

    @Scheduled(cron = "0 15 10 15 * ?", zone = "Europe/Paris") // cron에 TimeZone 설정 추가
}

Scheduler는 기본적으로 thread 1개를 이용하여 동기 형식으로 진행됩니다. 즉 1번 스케줄이 끝나지 않으면 2번 스케줄이 시작시간이 되었다고 하더라도 시작되지 않습니다. 비동기 형식으로 진행하시고 싶으시다면 @EnableAsync 어노테이션을 이용할 수 있습니다. 해당 내용은 추후 별도의 포스팅으로 찾아뵙겠습니다.

Spring Quartz

이번에는 Spring Quartz 사용법에 대해서 알아보도록 하겠습니다. quartz를 사용하기 위해서는 먼저 해당 라이브러리의 의존성을 추가해야합니다.

implementation "org.springframework.boot:spring-boot-starter-quartz"

Quartz에는 몇가지 필수적으로 구현해야할 요소와 옵셔널하게 선택적으로 구현할 수 있는 요소가 몇가지 있습니다. 오늘은 최대한 간단하게 필수적인 요소들만으로 돌아갈 수 있는 요소에 대해서 알아보고 나머지 요소는 아래의 부가기능에서 짧게 설명하는 식으로 다루도록 하겠습니다. Quartz의 Job의 필수적인 요소는 Job, JobDetail, Trigger로 3가지입니다.

  • Job : 실제로 실행되는 로직이 있는 곳입니다. Quartz에서 interface로 제공하며 해당 interface를 구현하면됩니다.
@Configuration
public class CollectJob implements Job {

  private final CollectService collectService;

  public CollectJob(CollectService collectService) {
    this.collectService = collectService;
  }

  @Override
  public void execute(JobExecutionContext context) throws JobExecutionException {
    System.out.println("[Collect] collect Job Start...");
  }
}
  • JobDetail : Job을 실행시키기 위한 구체적인 정보를 가지고 있는 인스턴스입니다. JobBuilder API를 통해 만들 수 있습니다. Job에 대한 설명 Job의 ID 등을 설정할 수 있습니다.
@Bean
public JobDetail tistoryJobDetail() {
return JobBuilder.newJob().ofType(CollectJob.class)
    .storeDurably()
    .withIdentity("job_detail")
    .withDescription("Invoke Tistory Job service...")
    .build();
}
  • Trigger : Trigger는 Job이 실행되는 실행 조건을 가지고 있는 인스턴스입니다. TriggerBuilder API를 통해 만들 수 있습니다. 조건으로 단순히 특정 시간 간격으로 할 수 있으며 Cron으로도 작성할 수 있습니다.
@Bean
public Trigger tistoryTrigger(@Qualifier("tistoryJobDetail") JobDetail job) {
return TriggerBuilder.newTrigger().forJob(job)
    .withIdentity("tistory_job_trigger")
    .withSchedule(cronSchedule("0 0 9 * * ?")
        .inTimeZone(TimeZone.getTimeZone("Asia/Seoul")))
    .build();
}
spring:
  quartz:
    job-store-type: memory

Quartz를 사용하면 좋을 때

위의 구현을 보니 어떠신가요? Spring Scheduler에 비해서 Quartz가 훨씬 배우기 어렵다는 사실이 느껴지셨나요? 아래는 Quartz의 내부 구조도입니다. 이와 같이 Quartz는 Scheduler에서 일어날 수 있는 다양한 기능을 제공해줍니다. 따라서 구조도 일반 Scheduler에 비해서는 조금 복잡하다라는 것을 알 수 있습니다.

Quartz가 제공해주는 기능은 아래와 같습니다.

Scheduler간의 Clustering 기능

여러 Service 노드가 있을 때 해당 노드들의 Scheduler간의 Clustering을 책임져 줄 수 있습니다. JobStore 를 이용하며 Memory 방식, DB 방식을 지원합니다. 클러스터링 방식은 랜덤 선택입니다.

Scheculer 실패에 대해서 후처리 기능

Misfire Instructions 기능을 제공합니다. Misfire Instructions 기능이란 만약 스케줄러가실패했을 때나 thread pool에서 사용할 수 있는 thread가 없는 경우에 발생할 수 있습니다.

기타

  • JVM 종료 이벤트를 캐치하여 스케줄러에게 종료를 알려주는 기능도 제공합니다.
  • 여러가지 기본 Plug-in을 제공합니다.
  • Job, Trigger 실행에 대해서 이벤트 처리를 할 수 있습니다.

마무리

결과적으로 만약 단순한 Scheduling에 따른 작업이 필요하시다면 단연코 Spring Scheduler를 추천합니다. Spring Quartz는 좀 더 Scheduling의 세밀한 제어가 필요할 때, 그리고 만들어야하는 Scheduling 서비스 노드가 멀티이기 때문에 클러스터링이 필요할 때 여러분이 만들고자 하는 서비스에 도움이 될 것입니다.

기능을 구현하기 위해 많은 라이브러리가 나와 있으며 우리는 그 중 우리에게 적합한 라이브러리를 잘 선택하여 프로젝트를 진행하여야 합니다. 그렇지 않다면 프로젝트를 마무리 하고자 했던 시간에 맞추지 못할 수 있고 또는 퍼포먼스적으로 기대에 못 미칠 수 있기 때문입니다.

오늘은 여기까지 하도록 하겠습니다.

감사합니다.

참조

baeldung_spring-scheduled_tasks

stackoverflow_Spring Scheduling: @Scheduled vs Quartz

stackoverflow_what-different-between-fixed-rate-and-fixed-delay-in-schedule-spring

javarticles_quartz-scheduler-model

댓글