[스프링] 다양한 작업 실행 전략 이야기 - 스케줄링 및 비동기 실행

웹 개발/스프링 프레임워크

2021. 12. 14.

1. 작업 스케줄링

특정 시간에 정기적으로 수행해야 하는 비즈니스 작업

작업 스케줄링의 구성요소

작업 스케줄링에 등장하는 3가지 요소가 있습니다. 작업, 트리거, 그리고 스케줄러입니다.

작업: 메서드

스케줄에 맞추어 실행되는 비즈니스 로직입니다.

스프링에서 작업은 메서드로 정의됩니다.

트리거: Trigger 인터페이스

작업은 일정한 시간 간격을 갖고 실행되어야 합니다. 작업의 실행 간격을 관장하는 정책이 트리거입니다.

아래 두 가지는 대표적인 트리거 유형입니다.

1)고정 간격을 지키며 실행

2)cron 표현식에 맞추어 실행

스프링은 Trigger 인터페이스 및 구현체 PeriodicTigger, CronTrigger 클래스를 제공합니다!

스케줄러: TaskScheduler 인터페이스

스케줄러는 작업을 트리거에 맞추어 실행하되 고유의 실행 전략을 갖습니다.

1) 타이머를 사용한 스케줄링
TimerManagerTaskSchedulercommonj.timers.TimerManager를 래핑한 클래스입니다.
Deprecated입니다.

2) 공유 스레드 풀에서 예약 작업을 실행
ConcurrentTaskSchedulerThreadPoolTaskSchedulerjava.util.concurrent.ScheduledThreadPoolExecutor를 래핑한 클래스입니다.

스프링에서 작업 스케줄링 구성하기

XML 구성

task 네임스페이스

이 네임스페이스 하위 태그를 사용하여

  • 작업 스케줄러 빈
  • 작업 메서드 & 트리거 정의가 가능합니다.
<beans
    ...
    xmlns:task="http://www.springframework.org/shema/task"
    xsi:schemaLocation=..
        http://www.springframework.org/schema/task/spring-task.xsd" />

task:scheduler 태그

<task:scheduler id="스케줄러 빈 아이디" pool-size="스레드 풀 사이즈(정수)" />

ThreaedPoolTaskScheduler 스케줄러 빈을 등록합니다.

  • id: 생성할 스케줄러 빈의 아이디입니다.
  • pool-size: ScheduledThreadPoolExecutor가 이용하는 스레드 풀 크기입니다.

task:scheduled-tasks 태그

작업 스케줄러가 담당할 작업들을 열거합니다.

task:scheduled 태그

메서드를 예약 작업으로 정의합니다. 트리거 정책도 설정합니다.

<task:scheduled-tasks schduler="스케줄러 빈 참조">
    <task:scheduled ref="서비스 빈 참조" method="메서드명" fixed-delay="10000" />
</task:scheduled-tasks>
  • ref: 작업 메서드를 갖고 있는 빈을 지정. 필수 값.
  • method: 실행할 메서드 이름. 필수 값.
  • trigger: 트리거 인터페이스를 구현하는 빈의 참조. 옵셔널.
  • initial-delay: 첫 실행이 일어나기 전 까지 대기할 시간. 단위는 ms. 옵셔널.
  • fixed-delay: 한 작업이 끝난 후 지정한 시간만큼 대기하여 다음 작업을 트리거 함. 단위는 ms. 옵셔널.
  • fixed-rate: 한 작업을 트리거 한 후 지정한 시간만큼 대기하고 다음 작업을 트리거 함. 따라서 작업들이 큐에 쌓일 수 있고 동시 실행되는 상황도 가능함. 단위는 ms. 옵셔널.
  • cron: 크론 표현식을 사용하여 트리거를 정의함. 옵셔널.

어노테이션 구성

@EnableScheduling

Config 클래스에 붙여주면, 스프링은 작업 스케줄러 빈을 색인합니다.

  • TaskScheduler 혹은 ScheduledExecutorService 타입으로 유일한 빈
  • taskScheulder로 식별되는 빈
  • 아무것도 찾지 못하면
    • 로컬 단일 스레드를 사용하는 스케줄러를 스스로 생성하여 사용함.

이제 우리가 할 일은 예약 작업에 어노테이션을 붙여 주는 것 뿐입니다.

@Scheduled 어노테이션

예약 작업인 메서드에 붙여주는 어노테이션입니다. <task:scheduled> 태그 용법을 준용하면 됩니다.

@Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Repeatable(Schedules.class)
public @interface Scheduled {
    String CRON_DISABLED = "-";

    String cron() default "";
    String zone() default "";

    long fixedDelay() default -1;
    String fixedDelayString() default "";

    long fixedRate() default -1;
    String fixedRateString() default "";

    long initialDelay() default -1;
    String initialDelayString() default "";
}

중점 사항

  • 스프링은ThreadPoolTaskScheduler를 기본적인 스케줄러로 활용하는 것 같습니다.

2. 비동기 작업 선언

@EnableAsync

Config 클래스에 @EnableAsync를 붙이면 비동기 작업 설정이 가능해집니다.

메서드 비동기 호출 및 반환

메서드에 @Async 어노테이션이 붙은 메서드는 비동기 실행됩니다.

비동기 작업의 반환으로 Future<V> 타입을 내놓을 수 있습니다.

연산된 값을 얻으려면 퓨처의 get() 을 호출하고 실제 값이 가용할 때까지 대기합니다.

public class AsyncService {

    @Async
    public void asyncTask() {..}

    @Async
    public Funture<String> asyncTaskWIthReturn() {..}
}

public class Application {
    public static void main(String... args) {
        AsyncService service = ctx.getBean(AsyncService.class);
        service.asyncTask();
        Future<String> result = service.asyncTaskWithReturn();
        String actualResult = result.get();
    }
}

3. 작업 실행

앞서 살펴본 스프링 작업 스케줄링은 TaskScheulder 빈이, @Scheduled 작업들을 주기적으로 실행해 주는 기능이었습니다.

이와 달리, 작업을 단순히 한 번 실행하되 스레드 정책을 적용하고 싶다면 TaskExecutor를 사용합니다.

상용구 코드

작업 실행자는 Runnable 구현체를 실행합니다. 결국 Task는 람다식으로 간단히 표현할 수 있습니다.

TaskExecutor taskExecutor = ctx.getBean(TaskExecutor.class);
taskExecutor.executeTask(()-> System.out.println("Task executed"));

작업 실행자 유형

  • SimpleAsyncTaskExecutor: 새 스레드에서 작업을 실행함
  • SyncTaskExecutor: 작업을 비동기로 실행하지 않고, 호출 스레드에서 실행함
  • SimpleThreadPoolTaskExecutor: Quartz와 Quartz가 아닌 컴포넌트가 스레드 풀을 공유하고 싶을 때??
  • ThreadPoolTaskExectuor: 스레드 풀을 빌려 작업을 실행할 때.