-
Spring Scheduler🌱 spring 2022. 10. 24. 17:08
📌 Spring Scheduler
@Scheduled
Spring Scheduler는 @Scheduled 어노테이션을 명시해 사용할 수 있다.
보통 실행하고자 하는 메소드명 위에 명시
Scheduler가 정상 작동하기 위해서는 스프링 어플리케이션에서 Scheduling을 활성화 시켜주어야 한다.
- @EnableScheduling 어노테이션을 클래스 위에 명시해 활성화 시킨다.
- 사용하고자 하는 클래스 위에 명시할 수 도 있고
- @SpringBootApplication 이 위치한 클래스 위에 명시해도 된다.
동작
Spring Scheduler는 동기적으로 스케줄러를 실행한다.
옵션
- fixedRate
- fixedDelay
- Cron
- fixedRate
- 작업의 시작부터 시간을 카운트한다.
@Scheduled(fixedRate=1000) // 단위: ms public void fixedRateScheduler() { System.out.println("작업이 시작하고 1000ms 마다 실행"); }
- fixedDelay
- 작업이 끝난 시점부터 시간을 카운트
@Schedueld(fixedDelay=1000) // 단위: ms public void fixedDelayScheduler() { System.out.println("작업이 끝나고 나서 다시 1000ms 후에 실행"); }
- cron
- 개발자가 초, 분, 시, 일, 월, 주, (년)을 지정해 스케줄러를 동작 시킨다.
- 정확히 지정한 시간에서만 실행됨을 보장한다.
@Scheduled(cron="0/60 * * * * ?") public void cronScheduler() { System.out.println("시스템 시간을 기준으로 1분 마다 주기적으로 실행"); }
스레드 풀 설정
기본적으로 모든 @Scheduled 작업은 Spring에 의해 생성된 한 개의 스레드 풀에서 실행된다.
따라서, 하나의 Scheduled가 돌고 있다면 그 작업이 다 끝나야만 다음 Scheduled가 실행된다.
@Component public class TestScheduler { @Scheduled(fixedDelay = 1000) public void testFixedDelay() throws InterruptedException { Thread.sleep(5000); System.out.println("fixedDelay Scheduler with : " + Thread.currentThread().getName()); } @Scheduled(fixedDelay = 1000) public void testFixedDelay2() { System.out.println("fixedDelay Scheduler2 with : " + Thread.currentThread().getName()); } }
- 위 코드에서 testFixedDelay 메서드와 별개로 testFixedDelay2() 가 1초마다 수행되길 바랬지만 실제 결과는 그렇지 않다.
fixedDelay Scheduler with : scheduling - 1 fixedDelay Scheduler2 with : scheduling - 1 fixedDelay Scheduler with : scheduling - 1 fixedDelay Scheduler2 with : scheduling - 1 fixedDelay Scheduler with : scheduling - 1 fixedDelay Scheduler2 with : scheduling - 1 fixedDelay Scheduler with : scheduling - 1 fixedDelay Scheduler2 with : scheduling - 1 ...
- 두 task가 같은 스레드에서 처리되기 때문! → 한 test가 끝나야 다른 test가 실행되는 구조이다.
해결 방법
서로 다른 thread를 사용하면 기다리지 않고 요청을 수행할 수 있다.
따라서, SchedulingConfigurer를 통해 thread pool size를 설정
- 스레드 풀에서 스레드를 가져와 테스크를 처리
❓ Thread pool
- thread pool은 작업처리에 사용되는 스레드를 제한된 개수만큼 정해 놓고 작업 큐(queue)에 들어오는 작업들을 하나씩 스레드가 맡아 처리하게 된다.
- 작업 처리 요청이 폭증해도 스레드의 전체 개수가 늘어나지 않아 시스템 성능이 급격하게 저하되지 않는다.
- 작업 처리가 끝난 스레드는 다시 작업 큐에서 새로운 작업을 가져와 처리한다.
- 따라서, 설정된 크기의 스레드를 만들어 놓고 해당 스레드들을 계속해서 재사용한다.
Thread Pool의 적정 사이즈
- 스레드를 불 필요하게 많이 만들면 메모리 낭비가 심해진다.
- 너무 적은 수를 만들면 효율성이 떨어진다.
- 따라서, 서비스가 실행되는 CPU의 코어 개수에 따라 유동적으로 생성될 수 있도록 하는 것이 좋다.
- CPU 처리가 많은 경우
- CPU의 코어를 N개라 하면 N + 1 만큼의 스레드를 생성해주면 최적에 가까운 성능을 낼 수 있다고 본다.
- I / O 작업이 많은 경우
- N * 2 만큼의 스레드를 생성해주기도 한다.
- CPU 처리가 많은 경우
thread pool 설정
@Configuration public class SchedulerConfig implements AsyncConfigurer, SchedulingConfigurer { public ThreadPoolTaskScheduler threadPoolTaskScheduler() { ThreadPoolTaskScheduler scheduler = new ThreadPoolTaskScheduler(); scheduler.setPoolSize(Runtime.getRuntime().availableProcessors() * 2); scheduler.initialize(); return scheduler; } }
⇒ 설정후 실행 결과
fixedDelay Scheduler2 with : ThreadPoolTaskScheduler-2 fixedDelay Scheduler2 with : ThreadPoolTaskScheduler-3 fixedDelay Scheduler2 with : ThreadPoolTaskScheduler-2 fixedDelay Scheduler2 with : ThreadPoolTaskScheduler-4 fixedDelay Scheduler with : ThreadPoolTaskScheduler-1 fixedDelay Scheduler2 with : ThreadPoolTaskScheduler-3 fixedDelay Scheduler2 with : ThreadPoolTaskScheduler-2 fixedDelay Scheduler2 with : ThreadPoolTaskScheduler-6 fixedDelay Scheduler2 with : ThreadPoolTaskScheduler-4 fixedDelay Scheduler2 with : ThreadPoolTaskScheduler-7
@Async가 필요한 이유?
스레드 풀을 설정했으니 스케줄러가 잘 동작하는지 확인
test1 만 실행하는 경우
@Component public class TestScheduler { @Scheduled(fixedDelay = 1000) public void testFixedDelay() throws InterruptedException { System.out.println("fixedDelay Scheduler start with : " + Thread.currentThread().getName()); Thread.sleep(5000); System.out.println("fixedDelay Scheduler end with : " + Thread.currentThread().getName()); } }
⇒ 원하는 것은 1초마다 스케줄러가 작동하는 것이지만 결과는 그렇지 않았다.
fixedDelay Scheduler end with : ThreadPoolTaskScheduler-1 fixedDelay Scheduler start with : ThreadPoolTaskScheduler-1 fixedDelay Scheduler end with : ThreadPoolTaskScheduler-1 fixedDelay Scheduler start with : ThreadPoolTaskScheduler-2 fixedDelay Scheduler end with : ThreadPoolTaskScheduler-2 fixedDelay Scheduler start with : ThreadPoolTaskScheduler-1 fixedDelay Scheduler end with : ThreadPoolTaskScheduler-1 fixedDelay Scheduler start with : ThreadPoolTaskScheduler-3 fixedDelay Scheduler end with : ThreadPoolTaskScheduler-3
스레드 풀 설정을 마쳐도 그저 다른 스레드로 처리할 뿐 각 작업은 동기적으로 처리한다.
따라서, 작업 수행에 걸리는 시간과 상관없이 원하는 시간마다 작업 수행을 시작하고 싶다면 비동기로 처리를 해야 한다.
@Async를 활용하면 다음 스케줄러가 이전 스케줄러의 작업이 끝날 때까지 기다리지 않고 자신의 작업을 처리할 수 있게 된다.
- 해당 어노테이션을 붙이면 각기 다른 스레드로 실행이 된다.
@EnableAsync로 활성화를 시켜주어야 한다.
- @EnableAsync 가 붙어 있는 Configuration 클래스가 필요하다.
@EnableAsync 어노테이션을 사용하면 SimpleAsyncTaskExecutor 를 사용하도록 설정되어 있다.
- SimpleAsyncTaskExecutor는 매번 스레드를 생성하는 방식이다.
- 따라서, 설정을 오버라이딩해서 사용하는 것이 좋다.
- @EnableAsync 를 Application 클래스가 아닌 Async 설정 클래스에 붙인다.
⇒ AsyncConfigurerSupport를 상속받는 Class를 직접 작성
@Configuration @EnableAsync public class AsyncConfig extends AsyncConfigurerSupport { @Override public Executor getAsyncExecutor() { ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); executor.setCorePoolSize(5); executor.setMaxPoolSize(30); executor.setQueueCapacity(50); executor.setThreadNamePrefix("Beomsic-"); executor.initialize(); return executor; } }
- CorePoolSize : 기본 실행 대기하는 thread 수
- MaxPoolSize : 동시 동작하는 최대 thread 수
- QueueCapacity
- MaxPoolSize 초과 요청에서 thread 생성 요청시, 해당 요청을 queue에 저장하는데, 이때 최대 수용 가능한 queue 수
- ThreadNamePrefix : 생성되는 thread 접두사
위 처럼 설정한 후 비동기 방식 사용을 원하는 method에 @Async 어노테이션을 지정해준다.
@Async @Scheduled(fixedDelay = 1000) public void testFixedDelay() throws InterruptedException { System.out.println("fixedDelay Scheduler start with : " + Thread.currentThread().getName()); Thread.sleep(5000); System.out.println("fixedDelay Scheduler end with : " + Thread.currentThread().getName()); }
결과
fixedDelay Scheduler start with : Beomsic-1 fixedDelay Scheduler start with : Beomsic-2 fixedDelay Scheduler start with : Beomsic-3 fixedDelay Scheduler start with : Beomsic-4 fixedDelay Scheduler start with : Beomsic-5 fixedDelay Scheduler end with : Beomsic-1 fixedDelay Scheduler start with : Beomsic-1 fixedDelay Scheduler end with : Beomsic-2 fixedDelay Scheduler start with : Beomsic-2
@Async 어노테이션의 2가지 제약조건
- public 메서드여야 한다.
- @Async의 동작은 AOP가 적용되어 Spring Context에서 등록된 Bean Object의 method가 호출될 시에, Spring이 확인하고 @Async가 적용된 method인 경우 Spring이 method를 가로채 다른 thread에서 실행 시켜주는 방식이다.
- 따라서, Spring이 해당 method를 가로챈 후, 다른 class에서 호출이 가능해야 하므로 private method는 사용할 수 없다.
- 스스로 호출하여 동작시킬 수 없다.(self-invocation)
- Spring Context에 등록된 Bean의 method 호출이어야 Proxy 적용이 가능하므로, inner method의 호출은 Proxy 영향을 받지 않기 때문에 self-invocation이 불가능하다.
참고 자료
https://cheershennah.tistory.com/170
https://ecsimsw.tistory.com/entry/Scheduler-적용-배경과-구조-Spring-Scheduler
https://bepoz-study-diary.tistory.com/399
https://velog.io/@gillog/Spring-Async-Annotation비동기-메소드-사용하기
'🌱 spring' 카테고리의 다른 글
JPA - @ElementCollection (0) 2022.11.02 JPA - Embedded Type (0) 2022.11.02 Filter, Interceptor (0) 2022.10.11 OSIV 🧐 (0) 2022.10.11 JPA - N + 1 문제 (0) 2022.10.11 - @EnableScheduling 어노테이션을 클래스 위에 명시해 활성화 시킨다.