Spring Framework의 Task Execution and Scheduling
을 부분번역한 포스팅입니다.
public interface TaskScheduler {
Clock getClock();
ScheduledFuture schedule(Runnable task, Trigger trigger);
ScheduledFuture schedule(Runnable task, Instant startTime);
ScheduledFuture scheduleAtFixedRate(Runnable task, Instant startTime, Duration period);
ScheduledFuture scheduleAtFixedRate(Runnable task, Duration period);
ScheduledFuture scheduleWithFixedDelay(Runnable task, Instant startTime, Duration delay);
ScheduledFuture scheduleWithFixedDelay(Runnable task, Duration delay);
Spinrg에서는 특정시점 이후에 작업이 실행되게 스케줄링 할 수 있도록 TaskScheduler
서비스 추상화 인터페이스를 제공한다.
해당 인터페이스에서 가장 간단한 메서드는 Runnable
과 Instant
를 받는 schedule()
다. 이는 지정된 시간이후에 한번 작업이 실행되도록 하며, 나머지 메서드들은 작업의 반복실행 기능을 제공한다.
fixed-rate
와 fixed-delay
는 주기적인 작업실행에 적합하며, Trigger
를 받는 schedule()
은 좀 더 유연한 실행을 지원한다.
이렇게 하나의 SPI를 제공함으로써 스케줄링 로직과 배포환경 간의 의존성을 제거하는 효과가 있다. 각 환경에 걸맞는 구현체를 제공하기 때문에 상황에 맞춰 구현체를 변경할 수 있기 때문이다.
예를들어, 로컬환경에서는
ThreadPoolTaskScheduler
나ConcurrentTaskScheduler
같은 간단한 구현체를 사용할 수 있다. 배포환경에서는 서버가 스레드 풀을 관리하도록DefaultManagedTaskScheduler
로 변경해주면 된다.하나의 최상위 인터페이스를 구현하고있기 때문에 애플리케이션 코드를 변경하지않고 손쉽게 구현체를 변경할 수 있게된다.
public interface Trigger {
Instant nextExecution(TriggerContext triggerContext);
}
Trigger
인터페이스는 하나의 추상메서드를 가지는 단순한 구조를 지닌다. 기본적인 아이디어는 과거의 실행결과 혹은 임의의 조건에 따라 Trigger
가 실행되는 시간이 결정되는 것이다. 이와 관련하여 자세히 살펴봐야할 부분은 매개변수로 받는 TriggerContext
인터페이스다.
public interface TriggerContext {
Clock getClock();
Instant lastScheduledExecution();
Instant lastActualExecution();
Instant lastCompletion();
}
TriggerContext
는 관련된 데이터들을 캡슐화하며, 향후 확장을 위해 개방되어있다. 기본적으로 SimpleTriggerContext
라는 구현체가 사용된다.
추상메서드들의 이름에서 알 수 있듯이, 작업이 마지막 실제 실행시간
, 마지막 예약 실행시간
, 마지막 완료시간
등을 관리한다.
Spring에서는 CronTrigger
와 PeriodicTrigger
라는 2개의 Trigger
구현체를 제공한다.
CronTrigger
는 Cron 표현식 기반으로 스케줄링을 진행한다. 예를들어, 평일 9시부터 5시까지의 업무시간동안 매 시 15분마다 실행될 작업은 아래와 같이 표현할 수 있다.
scheduler.schedule(task, new CronTrigger("0 15 9-17 * * MON-FRI"));
여기에 사용되는 Cron 표현식은 0 15 9-17 * * MON-FRI
부분이며, 아래 구조도로 해석할 수 있다.
┌───────────── second (0-59)
│ ┌───────────── minute (0 - 59)
│ │ ┌───────────── hour (0 - 23)
│ │ │ ┌───────────── day of the month (1 - 31)
│ │ │ │ ┌───────────── month (1 - 12) (or JAN-DEC)
│ │ │ │ │ ┌───────────── day of the week (0 - 7)
│ │ │ │ │ │ (0 or 7 is Sunday, or MON-SUN)
│ │ │ │ │ │
* * * * * *
PeriodicTrigger
는 고정시간, 선택적으로 초기 지연 값, 그리고 해당 기간을 fixed-rate
로 해석할지 fixed-delay
로 해석할지를 표현하는 boolean
값으로 구성된다. 이미 TaskScheduler
는 fixed-rate
와 fixed-delay
로 작업을 스케줄링하는 메서드를 가지고 있기 때문에 일반적으로는 해당하는 메서드들을 사용하는게 적합하다.
PeriodicTrigger
의 가치는 트리거 추상화를 사용하는 컴포넌트에서 트리거들을 전환하며 사용할 수 있다는 데에 있다.
즉, 의존성 주입을 이용해 컴포넌트 외부에서 손쉽게 트리거를 설정하여 동작방식을 수정하거나 확장할 수 있다.
앞서 소개한 TaskScheduler
를 직접적으로 사용하지 않아도 @Scheduled
애노테이션을 활용하면 손쉽게 스케줄링 작업을 설정할 수 있다. 우선 사전작업으로 Configuration 파일에 @EnableScheduling
애노테이션으로 스케줄링을 활성화해줘야 한다.
애노테이션 지원 외에 세부적인 설정이 필요하다면
SchedulingConfigurer
를 추가로 구현하면 된다.
애노테이션을 이용하는 경우, 기본적으로 사용되는 구현체는ThreadPollTaskScheduler
이며 설정을 통해 변경할 수 있다.
@Scheduled(fixedDelay = 5000)
public void doSomething() {
// something that should run periodically
}
애노테이션의 사용방법은 원하는 작업 메서드위에 @Scheduled
를 설정하는 것이다. 위 예시는 fixedDelay
이므로, doSomething()
이라는 메서드를 5초마다 실행한다. 즉, 이전 작업의 완료시간부터 5초 후 실행된다.
기본적으로
fixed-delay
,fixed-rate
를 포함한 딜레이 값의 단위는 모두 밀리세컨드 단위이다.
만약 다른 시간단위를 사용하고 싶다면timeUnit
속성으로 원하는 시간단위를 설정해주면 된다.@Scheduled(fixedDelay = 5, timeUnit = TimeUnit.SECONDS) public void doSomething() { // something that should run periodically }
@Scheduled(fixedRate = 5, timeUnit = TimeUnit.SECONDS)
public void doSomething() {
// something that should run periodically
}
fixedRate
로 설정한 경우에는 매 5초마다 실행된다. fixedDelay
와 다르게 이전 작업의 완료시간과 독립적으로 5초가 지날때마다 작업이 실행된다. initialDelay
값을 설정한다면 첫 작업 실행이전에 지연시간을 설정하는 것도 가능하다.
단순한 주기적인 스케줄링이 부족하다고 판단된다면 Cron식을 이용하면 된다.
Cron식이 사용하기 번거롭다면 Spring에서 제공하는 매크로를 이용해도 된다.
@Scheduled(cron="*/5 * * * * MON-FRI")
public void doSomething() {
// something that should run on weekdays only
}
주의사항으로 반복작업으로 예약할 메서드는 런타임시에 명시적으로 동작하는게 아니므로, 매개변수를 가져서는 안되며
void
반환형을 가져야한다. 만약, 다른 객체와 상호작용해야하는 작업이라면 클래스 단위에서 의존성 주입으로 받아와 사용해야한다.또한, 한 메서드에 여러개 설정도 가능하다.
이 경우에는 각각의 애노테이션들이 독립적으로 동작하기때문에 각 일정들이 겹치지 않게 주의해야한다.