태스크 스케줄링

김종준·2023년 3월 9일
0

태스크 스케줄링

태스크 스케줄링은 크게 세 부분으로 구성된다.

첫 번째 구성 요소인 태스크는 특정 시간에 또는 정기적으로 실행해야 하는 비즈니스 로직 부분이다.

두 번째 구성 요소인 트리거는 태스크를 실행하는 조건을 지정하는 역할을 한다.

마지막으로 스케줄러는 트리거의 정보를 기반으로 태스크를 실행시킨다.

스프링의 태스크 스케줄링

태스크 스케줄링은 태스크, 스케줄 정의, 태스크 실행과 같이 세 부분으로 구성된다.

스프링 애플리케이션에서 태스크 실행을 트리거하는 방법이 있다.

첫 번째 방법은 애플리케이션 배포 환경 내에 존재하는 별도의 스케줄링 시스템을 통해 외부에서 트리거하는 것이다.

또 다른 방법은 스프링이 제공하는 태스크 스케줄링 기능을 사용하는 것이다.

스프링은 태스크 스케줄링을 수행하는 세 가지 방법을 제공한다.

  • JDK 타이머 지원
  • 쿼츠 연동
  • 스프링 자체의 TaskScheduler 추상화

TaskScheduler 추상화 소개

  • Trigger 인터페이스 :
    org.springframework.scheduling.Trigger 인터페이스는 트리거 메커니즘을 정의하는 기능을 제공한다.
    스프링은 두 개의 Trigger 구현체를 제공한다.
    CronTrigger 클래스는 크론 표현식 기반의 트리거이고, PeriodicTrigger 클래스는 초기 지연 시간과 이후 고정된 시간 간격을 갖는 트리거이다.
  • Task :
    태스크는 스케줄링할 비즈니스 로직이다.
    스프링에서 태스크는 스프링 빈 내의 메서드로 지정한다.
  • TaskScheduler 인터페이스 :
    TaskScheduler 인터페이스는 태스크 스케줄링을 지원한다.
    스프링은 TaskScheuduler 인터페이스를 구현한 세 가지 구현 클래스를 제공한다.
    TimerManagerTaskScheduler 클래스는 웹스피어나 웹로직과 같은 상용 JEE 애플리케이션에서 자주 사용되는 CommonJ의 commonj,timers.TimerManager 인터페이스를 래핑한 클래스이다.
    ConcurrentTaskScheduler와 ThreadPoolTaskScheduler 클래스는 java.util.concurrent.ScheduledThreadPoolExecutor 클래스를 래핑한 클래스이다.
    두 클래스 모두 공유 스레드 풀에서 태스크 실행을 지원한다.

태스크 예제

@Entity
@Table
public class Car {
  ...
}
@Configuration
@EnableJpaRepositories(basePackages = "...")
@ComponentScan(basPackages = "...")
public class DataServiceConfig {
  ...
}
@Service
public class DBInitializer {
  @Autowired
  CarRepository carRepository;
  
  @PostConstruct
  public void initDB() {
    ...
  }
}
public interface CarRepository extends CrudRepository<Car, Long> {}
public interface CarService {
  ...
}

@Service
@Repository
@Transactional
public class CarServiceImple implements CarService {
  ...
	
	@Override
  public void updateCarAgeJob() {
    ...
  }
}

애너테이션을 사용한 태스크 스케줄링

스프링의 TaskScheduler 추상화를 이용해 태스크를 스케줄링하는 또 다른 방법은 애너테이션을 사용하는 방법이다.

스프링은 이를 위해 @Scheduled 애너테이션을 제공한다.

태스크 스케줄링을 위해 애너테이션을 사용하려면 구성 클래스에 @EnableScheduling 애너테이션을 적용해야 한다.

@Configuration
@Import({DataServiceConfig.class})
@EnableScheduling
public class AppConfig {}

@Configuration 애너테이션이 적용된 구성 클래스에서 @Scheduled 애너테이션을 감지할 수 있게 한다.

이때 @Scheduled 애너테이션이 선언된 메서다가 @Configuration 클래스 내에 직접 선언될 수도 있다.

이 애너테이션은 스프링에 관련 스케줄러 정의를 찾게 한다.

대게 컨텍스트 내의 유일한 TaskScheduler 빈이거나, taskScheduler라는 이름의 TaskScheduler 빈 또는 ScheduledExecutorService 빈이다.

아무것도 찾지 못하면 로컨 단일 스레드 기본 스케줄러가 생성되어 등록자 내에서 사용된다.

스프링 빈 내의 특정 메서드를 스케줄링하려면 메서드에 @Scheduled 애너테이션을 선언하고 스케줄링 요구사항을 전달해야 한다.

아래 코드는 @Configuration 클래스 내에 @Scheduled 애너테이션이 선언돼 메서드가 직접 선언된 예시와 메서드에 @Scheduled 애너테이션을 선언하는 예시다.

@Configuration
@Import({DataServiceConfig.class})
@EnableScheduling
public class AppConfig {
  @Bean
  TaskScheduler carScheduler() {
    ...
  }
}

@Service
@Repository
@Transactional
public class CarServiceImple implements CarService {
  ...
	
	@Override
  @Scheduled(fixedDelay=10000)
  public void updateCarAgeJob() {
    ...
  }
}

스프링의 비동기 태스크 실행

이 기능을 사용하려면 @Async 애너테이션을 메서드에 적용하면 된다.

@Async 애너테이션은 스프링의 비동기 메서드 실행 기능을 활성화하면 사용할 수 있는데, 자바 구성 클래스에 @EnableAsync 애너테이션을 선언하는 것만으로 수행할 수 있다.

@Configuration
@EnableAsync
@ComponetScan(basePackages = "...")
public class AppConfig {}

스프링에서 태스크 실행

스프링 2.0 버전 이후부터 프레임워크는 TaskExecutor 인터페이스를 통해 태스크를 샐행하기 위한 추상화를 제공해왔다.

TaskExecutor는 이름에서 드러내듯이 자바 Runnable 구현체인 태스크를 실행시킨다.

스프링은 TaskExecutor 인터페이스를 바로 사용할 수 있도록 용도별 TaskExecutor 구현체 몇 가지를 제공한다.

  • SimpleAsyncTaskExecutor :
    호출될 때마다 새로운 스레드를 생성한다. 기존 스레드를 재사용하지 않는다.
  • SyncTaskExecutor :
    비동기로 실행하지 않는다. 호출한 스레드 내에서 생성된다.
  • SimpleThreadPoolTaskExecutor :
    Quartz의 SimpleTrheadPool의 서브클래스다.
  • ThreadPoolTaskExecutor :
    빈 프로퍼티를 통해 ThreadPoolExecutor를 구성하고 스프링 TaskExecutor로 노출하는 기능을 제공하는 TaskExecutor 구현체다.
@Componet
public class TaskToExecute {
  @Autowired
  private TaskExecutor taskExecutor;
  
  public void executeTask() {
    for (int i=0; i < 10; i++) {
      taskExecutor.execute(() ->
                          logger.info(Thread.currentThread().getName));
    }
  }
}

TaskToExecute 클래스는 TaskExecutor 의존성을 주입받아야 하는 일반적인 빈이며 executeTask() 메서드를 정의한다.

executeTask() 메서드는 주입받은 TaskExecutor의 execute 메서드를 호출하며, 이 execute 메서드를 호출할 때 이 태스크가 실행하고자 하는 로직이 담긴 새 Runnable 인스턴스를 생성해 넘겨준다.

위의 예제에서는 Runnalbe 인스턴스 생성에 람다식을 사용하였다.

구성에서는 추가로 TaskExecutor 빈에 대해 선언하였다.

@Configuration
@EnableAsync
@ComponetScan(basePackages = "...")
public class AppConfig {
  @Bean
  TaskExecutor taskExecutor() {
    return new SimpleAsyncTaskExecutor();
  }
}

taskExecutor라는 이름을 가진 간단한 SimpleAsyncTaskExecutor 타입의 빈이 정의되었다.

스프링 IoC 컨테이너는 TaskToExecute 빈에 taskExecutor 빈을 주입한다.

0개의 댓글