반응형
스프링 TaskExecutor추상화
Executors
는 스레드 풀 개념에 대한 JDK 명칭이다.
Executor
라고 이름을 정한 이유는 기본 구현이 실제로 스레드 풀이라는 보장이 없기 때문에 발생한다.
Executor
는 단일 스레드일 수도 있고 동기식일 수도 있다. 스프링의 추상화는 구현 세부 사항을 숨긴다.
- Spring의
TaskExecutor
인터페이스는java.util.concurrent.Executor
인터페이스와 동일하다.- 이 인터페이스에는 스레드 풀의 의미 및 구성을 기반으로 실행할 작업을 허용하는 단일 메서드(execute(Runnable task))를 가지고 있다.
TaskExecutor
는 원래 필요한 경우 다른 Spring 구성 요소에 스레드 풀링에 대한 추상화를 제공하기 위해 만들어졌다.ApplicationEventMulticaster
, JMS의AbstractMessageListenerContainer
및Quartz 통합
과 같은 구성 요소는 모두TaskExecutor
추상화를 사용하여 스레드를 풀링한다.
- 그러나 Bean에 스레드 풀링 동작이 필요한 경우 필요에 따라 이 추상화를 사용하여 비동기 처리를 할 수도 있다.
스프링이 제공하는 TaskExecutor
구현체 유형
SyncTaskExecutor
- 이 구현은 호출을 비동기적으로 실행하지 않는다. 대신 각 호출은 호출 스레드에서 발생하고 간단한 테스트 케이스처럼 멀티스레딩이 필요하지 않은 상황에서 주로 사용된다.
SimpleAsyncTaskExecutor
- 이 구현은 스레드를 재사용하지 않는다. 오히려 호출할 때마다 새 스레드를 시작한다. 그러나 슬롯이 확보될 때까지 제한을 초과하는 모든 호출을 차단하는 동시성 제한을 지원한다.
- 스프링은 기본적으로
@Async
애노테이션에Executor
가 명시적으로 지정되지 않으면 비동기 메서드를 실행하기 위해서SimpleAsyncTaskExecutor
방식으로 동작한다.
ConcurrentTaskExecutor
- 이 구현은
java.util.concurrent.Executor
인스턴스에 대한 어댑터이다.
Executor
구성 매개변수를 Bean으로 정의하여 구성하는 대안은ThreadPoolTaskExecutor
이 있다.
ConcurrentTaskExecutor
를 직접 사용할 필요는 거의 없지만ThreadPoolTaskExecutor
이 요구 사항에 비해 충분히 유연하지 않은 경우에는ConcurrentTaskExecutor
가 대안이 될 수 있다.
- 이 구현은
ThreadPoolTaskExecutor
- 이 구현은 가장 일반적으로 사용되는 방식이다.
java.util.concurrent.ThreadPoolExecutor
를 구성하기 위한 Bean 속성을 노출하고 이를TaskExecutor
로 감싼다.
- 다른 종류의
java.util.concurrent.Executor
에 적응해야 하는 경우 대신ConcurrentTaskExecutor
를 사용하는 것이 좋다.
- 이 구현은 가장 일반적으로 사용되는 방식이다.
DefaultManagedTaskExecutor
- 이 구현은 JSR-236 호환 런타임 환경(예: Jakarta EE 애플리케이션 서버)에서 JNDI에서 얻은
ManagedExecutorService
를 사용하여 해당 목적으로CommonJ WorkManager
를 대체한다.
- 이 구현은 JSR-236 호환 런타임 환경(예: Jakarta EE 애플리케이션 서버)에서 JNDI에서 얻은
스프링 프레임워크 6.1부터는ThreadPoolTaskExecutor
는 Spring의 라이프사이클 관리를 통해 일시정지/재개 기능과 우아한 종료 기능을 제공한다.
또한SimpleAsyncTaskExecutor
에는 JDK 21의 가상 스레드와 일치하는 새로운"virtualThreads"
옵션이 있으며SimpleAsyncTaskExecutor
에 대한 정상적인 종료 기능도 가지고 있다.
스프링 TaskScheduler 추상화
- 스프링은
TaskExecutor
추상화 외에도Spring에는 미래의 특정 시점에 실행될 작업을 예약하기 위한
다양한 방법을 갖춘TaskScheduler SPI
가 있다.
TaskScheduler 인터페이스 정의
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);
Trigger 인터페이스
- Trigger 인터페이스는 JSR-236 스펙과 관련있다.
- Trigger의 기본 아이디어는 실행 시간이 과거 실행 결과 또는 임의의 조건에 따라 결정될 수 있다는 것이다.
- 이러한 결정이 이전 실행의 결과를 고려하는 경우 해당 정보는
TriggerContext
내에서 사용할 수 있다.
Trigger 인터페이스 정의
public interface Trigger { Instant nextExecution(TriggerContext triggerContext); }
TriggerContext
가 가장 중요하고 이는 모든 관련 데이터를 캡슐화하며 필요한 경우 향후 확장이 가능하다. (TriggerContext
는 인터페이스는 기본적으로SimpleTriggerContext
구현이 사용된다.)
TriggerContext 인터페이스 정의
public interface TriggerContext { Clock getClock(); Instant lastScheduledExecution(); Instant lastActualExecution(); Instant lastCompletion(); }
Trigger 구현
- 스프링은
Trigger 인터페이스
의 두 가지 구현을 제공한다.CronTrigger
: cron 표현식을 기반으로 작업 일정을 계획할 수 있다.
PeriodicTrigger
: 고정 기간, 선택적 초기 지연 값 및 기간을 고정 비율 또는 고정 지연으로 해석해야 하는지 여부 등으로 작업 일정을 계획한다.TaskScheduler
인터페이스는 이미 고정 속도 또는 고정 지연으로 작업을 예약하는 방법을 정의하므로 가능하면 이러한 방법을 직접 사용해야 한다.
PeriodicTrigger
구현의 가치는Trigger
추상화에 의존하는 구성 요소 내에서 사용할 수 있다는 것이다. 예를 들어,PeriodicTrigger
,CronTrigger
, 심지어 사용자 정의 트리거 구현까지 서로 바꿔서 사용할 수 있도록 허용하는 것이 편리할 수 있다. 이러한 구성 요소는 종속성 주입을 활용하여 해당 트리거를 외부에서 구성할 수 있으므로 쉽게 수정하거나 확장할 수 있다.
TaskScheduler 구현
- 스프링의
TaskExecutor
추상화와 마찬가지로TaskScheduler
배열의 주요 이점은 애플리케이션의 스케줄링 요구가 배포 환경에서 분리된다는 것이다.- 이 추상화 수준은 애플리케이션 자체에서 스레드를 직접 생성해서는 안 되는 애플리케이션 서버 환경에 배포할 때 특히 관련이 있다.
- 이러한 시나리오의 경우 Spring은 Jakarta EE 환경의 JSR-236 ManagedScheduledExecutorService에 위임하는 DefaultManagedTaskScheduler를 제공한다.
- 외부 스레드 관리가 필요하지 않을 때마다 더 간단한 대안은 애플리케이션 내의 로컬
ScheduledExecutorService
설정이며 이는 스프링의ConcurrentTaskScheduler
를 통해 조정할 수 있다.
- 편의상 스프링은
ThreadPoolTaskExecutor
라인을 따라 공통 빈 스타일 구성을 제공하기 위해ScheduledExecutorService
에 내부적으로 위임하는ThreadPoolTaskScheduler
도 제공한다. 이러한 점은 애플리케이션 서버 환경, 특히 Tomcat 및 Jetty의 로컬 임베디드 스레드 풀 설정에서 완벽하게 작동한다.
- 스프링 프레임워크 6.1부터는ThreadPoolTaskScheduler
는 스프링의 라이프사이클 관리를 통해 일시 중지/재개 기능과 우아한 종료 기능을 제공한다.
또한 단일 스케줄러 스레드를 사용하지만 모든 예약된 작업 실행에 대해 새 스레드를 실행하는 JDK 21의 가상 스레드와 일치하는SimpleAsyncTaskScheduler
라는 새로운 옵션도 있다.
(모두 단일 스케줄러 스레드에서 작동하는 고정 지연 작업을 제외하고 이 가상 스레드 정렬 옵션의 경우 fixed rates 및 cron 트리거가 권장된다.)
스케줄링 및 비동기 실행을 위한 애노테이션 지원
- 스프링은 작업 스케줄링과 비동기 메소드 실행 모두에 대한 애노테이션을 제공한다.
@Scheduled
및@Async
주석에 대한 지원을 활성화하려면 아래와 같이@Configuration
클래스 중 하나에@EnableScheduling
및@EnableAsync
를 추가할 수 있다.@Configuration @EnableAsync //비동기 애노테이션 활성화 @Async @EnableScheduling //스케줄링 애노테이션 활성화 @Scheduled public class AppConfig { ... }
@Async
주석 처리를 위한 기본 AdviceMode는 “proxy” 이다.- 프록시를 통해서만 호출을 가로채는 것을 허용
같은 클래스 내의 로컬 호출은 차단될 수 없다. 보다 진보된 가로채기 모드를 위해서는 컴파일 타임 또는 로드 타임 위빙과 결합하여 Aspectj 모드로 전환하는 것을 고려해보자.
@Scheduled 애노테이션
- 예약할 메서드에는 void 반환이 있어야 하며 인수를 허용해서는 안된다.
- 동일한 메서드에서 여러 개의 예약된 선언이 발견되면 각 선언은 독립적으로 처리되며 각 선언에 대해 별도의 트리거가 실행된다.
- 결과적으로, 그러한 공동 배치 일정은 병렬로 또는 즉시 연속적으로 여러 번 중복되고 실행될 수 있어서 지정한 크론 표현식 등이 실수로 겹치지 않는지 확인해야한다.
//고정된 지연 시간을 두고 5초(5000밀리초)마다 호출. 즉, 기간은 각 이전 호출의 완료 시간부터 측정된다.
@Scheduled(fixedDelay = 5000)
public void doSomething() {
// 주기적으로 실행되어야 하는 작업
}
//고정된 지연 시간을 두고 5초(5000밀리초)마다 호출. 즉, 기간은 각 이전 호출의 완료 시간부터 측정된다.
@Scheduled(fixedDelay = 5, timeUnit = TimeUnit.SECONDS)
public void doSomething() {
// 주기적으로 실행되어야 하는 작업
}
//5초마다 호출
@Scheduled(fixedRate = 5, timeUnit = TimeUnit.SECONDS)
public void doSomething() {
// 주기적으로 실행되어야 하는 작업
}
//고정 지연 및 고정 속도 작업의 경우 다음 고정 속도 예제에 표시된 대로 메서드를 처음 실행하기 전에 대기할 시간을 표시하여 초기 지연을 지정할 수 있다.
@Scheduled(initialDelay = 1000, fixedRate = 5000)
public void doSomething() {
// 주기적으로 실행되어야 하는 작업
}
//cron 표현식을 이용하여 작업 스케줄링할 수 있다. 평일에만 실행
//zone 속성을 사용하여 cron 표현식이 해석되는 시간대를 지정할 수도 있다.
@Scheduled(cron="*/5 * * * * MON-FRI")
public void doSomething() {
// 주기적으로 실행되어야 하는 작업
}
각 인스턴스에 대한 콜백을 예약하려는 경우가 아니면 런타임 시 동일한 @Scheduled 주석 클래스의 여러 인스턴스를 초기화하지 않는지 확인해야한다.@Scheduled
애노테이션을 추가하고 컨테이너에 일반 스프링 빈으로 등록되는 Bean 클래스에@Configurable
을 사용하지 않는지 확인해야한다.
그렇지 않으면 두 번 초기화(컨테이너를 통해 한 번,@Configurable
측면을 통해 한 번)를 받게 되며 결과적으로 각@Scheduled
메서드가 두 번 호출된다.
@Async 애노테이션
- 해당 메서드의 호출이 비동기적으로 발생하도록 메서드에
@Async
애노테이션을 사용할 수 있다.- 호출자는 호출 즉시 반환하지만 메서드의 실제 실행은 스프링
TaskExecutor
에 제출된 작업에서 발생한다.
- 호출자는 호출 즉시 반환하지만 메서드의 실제 실행은 스프링
@Scheduled
주석이 달린 메서드와 달리 이러한 메서드는 컨테이너가 관리하는 예약된 작업이 아닌 런타임 시 호출자가 "일반적인" 방식으로 호출하기 때문에 인수를 허용할 수 있다.@Async void doSomething() { // 비동기로 실행 } @Async void doSomething(String s) { // 비동기로 실행 } @Async Future<String> returnSomething(int i) { // 비동기로 실행 }
@Async
는@PostConstruct
과 같이 빈 수명주기 콜백과 함께 사용할 수 없다.- 스프링 빈을 비동기식으로 초기화하려면 아래 예제와 같이 대상에서 주석이 달린 메서드를 호출하는 별도의 초기화 Spring Bean을 사용해야 한다.
public class SampleBeanImpl implements SampleBean { @Async void doSomething() { // ... } } public class SampleBeanInitializer { private final SampleBean bean; public SampleBeanInitializer(SampleBean bean) { this.bean = bean; } @PostConstruct public void initialize() { bean.doSomething(); } }
- 스프링 빈을 비동기식으로 초기화하려면 아래 예제와 같이 대상에서 주석이 달린 메서드를 호출하는 별도의 초기화 Spring Bean을 사용해야 한다.
- @Async 에 Executor 지정(스레드풀 지정)
@Configuration @EnableAsync public class SpringAsyncConfig { @Bean(name = "threadPoolTaskExecutor") public Executor threadPoolTaskExecutor() { ThreadPoolTaskExecutor taskExecutor = new ThreadPoolTaskExecutor(); taskExecutor.setCorePoolSize(3); // 기본 스레드 수 taskExecutor.setMaxPoolSize(20); // 최대 스레드 수 taskExecutor.setQueueCapacity(100); // Queue 사이즈 taskExecutor.setThreadNamePrefix("threadPoolTaskExecutor-"); return taskExecutor; } }
@Async("threadPoolTaskExecutor") void doSomething(String s) { // threadPoolTaskExecutor에 의해 비동기 실행 }
크론 표현식(Cron Expressions)
┌───────────── 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)
│ │ │ │ │ │
* * * * * *
참고
반응형
'Java > Spring Framework' 카테고리의 다른 글
[스프링] 스프링 이벤트(ApplicationEventPublisher, @EventListener, @TransactionalEventListener) (1) | 2024.03.19 |
---|---|
[스프링 핵심 원리 - 기본편] 웹 스코프 (0) | 2021.09.04 |
[스프링 핵심 원리 - 기본편] 빈 스코프 (0) | 2021.08.31 |
[스프링 핵심 원리 - 기본편] 빈 생명주기 콜백 (0) | 2021.08.30 |
[스프링 핵심 원리 - 기본편] 같은 타입의 빈 여러 개 조회 문제 (0) | 2021.08.29 |