스케쥴링은 특정 시간에 실행이 되는 일련의 작업이다. 스프링에서는 @Scheduled
어노테이션을 통해서 예약 작업을 구성할 수 있다.
@Scheduled
가 붙은 메소드는 다음과 같은 규칙이 있다.
- 일반적으로 리턴 타입이 없다. (있다면 무시될 것이다)
- 어떤 파라미터도 받아서는 안된다.
스케쥴링 사용
스프링에서 스케쥴 작업과 @Scheduled 어노테이션을 사용하기 위해서 아래와 같은 방법으로 @EnableScheduling
을 추가한다.
1. Spring Boot에 추가하는 방법
@SpringBootApplication
@EnableScheduling
public class SpringSchedulerDemo {
public static void main(String[] args) {
SpringApplication.run(SpringSchedulerDemo.class);
}
}
2. Configuration에 추가하는 방법
@Configuration
@EnableScheduling
public class SchedulerConfig {
...
}
Fixed Delay 사용
이 방식은 작업 실행 시 정해진 시간동안 대기하는 방식이다.
@Scheduled(fixedDelay = 1000)
public void fixedDelay() {
log.info("fixed delay");
}
여기서 fixedDelay는 마지막 실행
과 다음 실행
사이의 간격이 고정이라는 의미이다. 작업은 항상 이전 작업이 끝날때 까지 대기한다. 이 작업은 이전 작업이 다음 실행 전에 끝나야 할 경우에 사용된다.
실행 결과
2023-08-20 19:37:58.333 INFO 3907 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed delay
2023-08-20 19:37:59.335 INFO 3907 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed delay
2023-08-20 19:38:00.337 INFO 3907 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed delay
2023-08-20 19:38:01.341 INFO 3907 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed delay
2023-08-20 19:38:02.344 INFO 3907 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed delay
2023-08-20 19:38:03.349 INFO 3907 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed delay
2023-08-20 19:38:04.350 INFO 3907 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed delay
2023-08-20 19:38:05.351 INFO 3907 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed delay
시간이 정확하게 1초 단위는 아니지만 거의 1초 간격으로 실행이 된다. 하지만 실행되는 시간이 점점 증가되는 증상이 있다.
(xx.333에서 실행해서 나중에는 xx.351에서 실행이 된다. 점점 시간이 증가되는 현상)
Fixed Rate 사용
이 방식은 특정 고정된 시간 간격으로 작업을 실행하는 방식이다. 이 옵션은 각 작업의 실행이 무관할 때 사용된다.
작업은 기본적으로 병렬로 실행되지 않는다는 것을 주의하자. 그래서 fixedRate를 사용할 지라도, 다음 작업은 이전 작업이 완료될 때까지 실행되지 않을 것이다.
@Scheduled(fixedRate = 1000)
public void fixedRate() {
log.info("fixed rate");
}
실행결과
2023-08-20 19:40:18.709 INFO 4857 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate
2023-08-20 19:40:19.705 INFO 4857 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate
2023-08-20 19:40:20.711 INFO 4857 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate
2023-08-20 19:40:21.710 INFO 4857 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate
2023-08-20 19:40:22.709 INFO 4857 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate
2023-08-20 19:40:23.708 INFO 4857 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate
2023-08-20 19:40:24.706 INFO 4857 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate
2023-08-20 19:40:25.709 INFO 4857 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate
2023-08-20 19:40:26.707 INFO 4857 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate
2023-08-20 19:40:27.709 INFO 4857 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate
이 방식 또한 시간이 정확하게 1초 단위는 아니지만 거의 1초 간격으로 실행이 된다. 하지만 시간이 지속적으로 증가되지 않고 일시적으로 증가하더라도 다시 처음 시작했던 시간으로 내려와서 실행되는 현상이 있다.
Fixed Delay vs Fixed Rate 비교
Fixed Delay와 Fixed Rate를 좀 더 정확한 차이를 비교하기 위해 0.5초 간 대기를 걸어주면 더 명확히 알 수가 있다.
fixedDelay + 500ms(delay)
@Scheduled(fixedDelay = 1000)
public void fixedDelay() {
log.info("fixed delay");
sleep(500);
}
실행결과
2023-08-20 19:54:12.537 INFO 7229 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed delay
2023-08-20 19:54:14.042 INFO 7229 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed delay
2023-08-20 19:54:15.549 INFO 7229 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed delay
2023-08-20 19:54:17.053 INFO 7229 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed delay
2023-08-20 19:54:18.558 INFO 7229 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed delay
2023-08-20 19:54:20.067 INFO 7229 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed delay
2023-08-20 19:54:21.572 INFO 7229 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed delay
2023-08-20 19:54:23.074 INFO 7229 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed delay
거의 1.5초 마다 실행이 된다. 즉, 마지막 실행이 완료되고 1초 후에 실행이 된다.
fixedRate + 500ms(delay)
@Scheduled(fixedRate = 1000)
public void fixedRate() {
log.info("fixed rate");
sleep(500);
}
실행결과
2023-08-20 19:55:41.032 INFO 8086 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate
2023-08-20 19:55:42.038 INFO 8086 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate
2023-08-20 19:55:43.034 INFO 8086 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate
2023-08-20 19:55:44.030 INFO 8086 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate
2023-08-20 19:55:45.031 INFO 8086 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate
2023-08-20 19:55:46.032 INFO 8086 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate
2023-08-20 19:55:47.033 INFO 8086 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate
2023-08-20 19:55:48.034 INFO 8086 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate
2023-08-20 19:55:49.035 INFO 8086 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate
거의 1초 마다 실행이 된다.
Fixed Delay vs Fixed Rate의 실행시간이 긴 경우
1초 마다 실행이 되는데 2초의 delay가 있는 경우
fixedDelay + 2000ms(delay)
@Scheduled(fixedDelay = 1000)
public void fixedDelayWithLongDelay() {
log.info("fixed delay");
sleep(2000);
}
실행결과
2023-08-20 19:59:20.563 INFO 8647 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed delay
2023-08-20 19:59:23.569 INFO 8647 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed delay
2023-08-20 19:59:26.574 INFO 8647 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed delay
2023-08-20 19:59:29.577 INFO 8647 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed delay
2023-08-20 19:59:32.582 INFO 8647 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed delay
2023-08-20 19:59:35.584 INFO 8647 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed delay
2023-08-20 19:59:38.591 INFO 8647 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed delay
2023-08-20 19:59:41.599 INFO 8647 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed delay
3초 마다 실행이 된다. 실행이 1초 간격 + 2초 대기이므로 3초 마다 실행이 된다.
fixedRate + 2000ms(delay)
@Scheduled(fixedRate = 1000)
public void fixedRateWithLongDelay() {
log.info("fixed rate");
sleep(2000);
}
실행결과
2023-08-20 20:02:10.165 INFO 9251 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate
2023-08-20 20:02:12.168 INFO 9251 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate
2023-08-20 20:02:14.172 INFO 9251 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate
2023-08-20 20:02:16.176 INFO 9251 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate
2023-08-20 20:02:18.177 INFO 9251 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate
2023-08-20 20:02:20.182 INFO 9251 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate
2023-08-20 20:02:22.183 INFO 9251 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate
2초마다 실행이 된다. 1초 간격 + 2초 대기인데 1초마다 실행이 되어야 하지만 2초 대기가 되니 2초마다 실행이 되는 것이다.
이는 싱글쓰레드에서 실행이 되므로 발생하는 문제이다. 쓰레드 이름을 찍어보면 동일한 쓰레드 이름이 표시되는 것을 확인할 수 있다.
@Scheduled(fixedRate = 1000)
public void fixedRateWithLongDelay() {
log.info("fixed rate " + Thread.currentThread().getName());
sleep(2000);
}
실행결과
2023-08-20 20:21:21.336 INFO 13298 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate scheduling-1
2023-08-20 20:21:23.341 INFO 13298 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate scheduling-1
2023-08-20 20:21:25.343 INFO 13298 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate scheduling-1
2023-08-20 20:21:27.346 INFO 13298 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate scheduling-1
2023-08-20 20:21:29.348 INFO 13298 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate scheduling-1
2023-08-20 20:21:31.351 INFO 13298 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate scheduling-1
ThreadPool 사용하기
스프링 스케쥴러는 기본적으로 싱글쓰레드로 실행이 된다. 그래서 멀티쓰레드로 구성하기 위해서는 아래와 같은 설정이 필요하다.
@Configuration
public class SchedulerConfig implements SchedulingConfigurer {
private final static int POOL_SIZE = 10;
@Override
public void configureTasks(ScheduledTaskRegistrar taskRegistrar) {
final ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.setPoolSize(POOL_SIZE);
threadPoolTaskScheduler.setThreadNamePrefix("multi-scheduler-");
threadPoolTaskScheduler.initialize();
taskRegistrar.setTaskScheduler(threadPoolTaskScheduler);
}
}
10개의 쓰레드로 실행을 해보자.
fixedDelay + 2000ms(delay)
@Scheduled(fixedDelay = 1000)
public void fixedDelayWithLongDelay() {
log.info("fixed delay " + Thread.currentThread().getName());
sleep(2000);
}
실행결과
2023-08-20 20:35:41.685 INFO 16463 --- [lti-scheduler-1] c.e.scheduler.service.SchedulerService : fixed delay multi-scheduler-1
2023-08-20 20:35:44.688 INFO 16463 --- [lti-scheduler-2] c.e.scheduler.service.SchedulerService : fixed delay multi-scheduler-2
2023-08-20 20:35:47.696 INFO 16463 --- [lti-scheduler-1] c.e.scheduler.service.SchedulerService : fixed delay multi-scheduler-1
2023-08-20 20:35:50.696 INFO 16463 --- [lti-scheduler-3] c.e.scheduler.service.SchedulerService : fixed delay multi-scheduler-3
2023-08-20 20:35:53.699 INFO 16463 --- [lti-scheduler-2] c.e.scheduler.service.SchedulerService : fixed delay multi-scheduler-2
2023-08-20 20:35:56.704 INFO 16463 --- [lti-scheduler-4] c.e.scheduler.service.SchedulerService : fixed delay multi-scheduler-4
2023-08-20 20:35:59.711 INFO 16463 --- [lti-scheduler-1] c.e.scheduler.service.SchedulerService : fixed delay multi-scheduler-1
3초 마다 실행된다. 여러개의 쓰레드로 실행이 되어도 3초 마다 실행이 된다.
fixedRate + 2000ms(delay)
@Scheduled(fixedRate = 1000)
public void fixedRateWithLongDelay() {
log.info("fixed rate " + Thread.currentThread().getName());
sleep(2000);
}
실행결과
2023-08-20 20:37:28.246 INFO 16779 --- [lti-scheduler-3] c.e.scheduler.service.SchedulerService : fixed rate multi-scheduler-3
2023-08-20 20:37:30.248 INFO 16779 --- [lti-scheduler-3] c.e.scheduler.service.SchedulerService : fixed rate multi-scheduler-3
2023-08-20 20:37:32.253 INFO 16779 --- [lti-scheduler-3] c.e.scheduler.service.SchedulerService : fixed rate multi-scheduler-3
2023-08-20 20:37:34.258 INFO 16779 --- [lti-scheduler-3] c.e.scheduler.service.SchedulerService : fixed rate multi-scheduler-3
2023-08-20 20:37:36.261 INFO 16779 --- [lti-scheduler-3] c.e.scheduler.service.SchedulerService : fixed rate multi-scheduler-3
2023-08-20 20:37:38.262 INFO 16779 --- [lti-scheduler-3] c.e.scheduler.service.SchedulerService : fixed rate multi-scheduler-3
2023-08-20 20:37:40.262 INFO 16779 --- [lti-scheduler-3] c.e.scheduler.service.SchedulerService : fixed rate multi-scheduler-3
2023-08-20 20:37:42.267 INFO 16779 --- [lti-scheduler-3] c.e.scheduler.service.SchedulerService : fixed rate multi-scheduler-3
2초 마다 실행이 된다.
Async 사용하기
ThreadPool을 사용하면 여러 개의 쓰레드가 동시에 실행되어 정상적으로 동작할 것으로 생각이 되었지만 그렇지 않았다. (fixedRate=1000로 설정이 되었지만 1초마다 설정이 되지 않았다)
작업을 정해진 시간에 병렬 실행을 하기 위해서는 @Async
를 사용하면 된다.
Async는 비동기로 실행하게 해주는 기능이며 @EnableAsync
를 추가해야 정상작동한다.
@SpringBootApplication
@EnableScheduling
@EnableAsync
public class SpringSchedulerDemo {
public static void main(String[] args) {
SpringApplication.run(SpringSchedulerDemo.class);
}
}
fixedDelay + 2000ms(delay)
@Scheduled(fixedRate = 1000)
@Async
public void fixedRateWithAsync() {
log.info("fixed rate " + Thread.currentThread().getName());
sleep(2000);
}
실행결과
2023-08-21 06:19:49.572 INFO 97668 --- [ task-1] c.e.scheduler.service.SchedulerService : fixed delay task-1
2023-08-21 06:19:50.573 INFO 97668 --- [ task-2] c.e.scheduler.service.SchedulerService : fixed delay task-2
2023-08-21 06:19:51.575 INFO 97668 --- [ task-3] c.e.scheduler.service.SchedulerService : fixed delay task-3
2023-08-21 06:19:52.578 INFO 97668 --- [ task-4] c.e.scheduler.service.SchedulerService : fixed delay task-4
2023-08-21 06:19:53.581 INFO 97668 --- [ task-5] c.e.scheduler.service.SchedulerService : fixed delay task-5
2023-08-21 06:19:54.584 INFO 97668 --- [ task-6] c.e.scheduler.service.SchedulerService : fixed delay task-6
2023-08-21 06:19:55.585 INFO 97668 --- [ task-7] c.e.scheduler.service.SchedulerService : fixed delay task-7
1초 마다 실행이 된다. 즉, 2초의 대기가 있어도 여러 개의 쓰레드가 비동기로 실행이 되어 매 1초 마다 실행이 된다.
fixedRate + 2000ms(delay)
@Scheduled(fixedRate = 1000)
@Async
public void fixedRateWithAsync() {
log.info("fixed rate " + Thread.currentThread().getName());
sleep(2000);
}
실행결과
2023-08-21 06:20:39.605 INFO 97684 --- [ task-1] c.e.scheduler.service.SchedulerService : fixed rate task-1
2023-08-21 06:20:40.605 INFO 97684 --- [ task-2] c.e.scheduler.service.SchedulerService : fixed rate task-2
2023-08-21 06:20:41.600 INFO 97684 --- [ task-3] c.e.scheduler.service.SchedulerService : fixed rate task-3
2023-08-21 06:20:42.601 INFO 97684 --- [ task-4] c.e.scheduler.service.SchedulerService : fixed rate task-4
2023-08-21 06:20:43.603 INFO 97684 --- [ task-5] c.e.scheduler.service.SchedulerService : fixed rate task-5
2023-08-21 06:20:44.607 INFO 97684 --- [ task-6] c.e.scheduler.service.SchedulerService : fixed rate task-6
2023-08-21 06:20:45.602 INFO 97684 --- [ task-7] c.e.scheduler.service.SchedulerService : fixed rate task-7
2023-08-21 06:20:46.602 INFO 97684 --- [ task-8] c.e.scheduler.service.SchedulerService : fixed rate task-8
1초 마다 실행이 된다. 이 경우도 동일하다. 2초의 대기가 있어도 여러 개의 쓰레드가 비동기로 실행이 되어 1초 마다 실행이 된다.
Initial Delay로 작업 스케쥴링
@Scheduled(initialDelay = 5000, fixedRate = 1000)
public void fixedRateWithInitialDelayLongDelay() {
log.info("fixed rate " + Thread.currentThread().getName());
}
이 작업은 initialDelay값 후에 최초 실행이 될 것이다.
Cron 표현식 사용
@Scheduled(cron = "0 15 10 15 * ?")
public void cron() {
log.info("cron " + Thread.currentThread().getName());
}
이 작업은 매달 15일, 10:15 AM에 실행된다.
스케쥴링 파라미터화 하기
스프링 표현식을 외부 파라미터화할 수 있다.
// fixedDelay
@Scheduled(fixedDelayString = "${fixedDelay.in.milliseconds}")
// fixedRate
@Scheduled(fixedRateString = "${fixedRate.in.milliseconds}")
Interval-in-cron=0 * * * * *
┌───────────── 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)
│ │ │ │ │ │ (or MON-SUN -- 0 or 7 is Sunday)
│ │ │ │ │ │
* * * * * *
병렬 실행
기본적으로, 스프링은 작업을 실행하기 위해 싱글쓰레드를 사용한다. 여러 개의 @Scheduled
메소드를 사용하더라도 이전 작업이 완료될 때까지 대기해야 한다.
@Scheduled(fixedDelay = 1000)
public void fixedDelayWithLongDelay() {
log.info("fixed delay " + Thread.currentThread().getName());
sleep(2000);
}
@Scheduled(fixedRate = 1000)
public void fixedRateWithLongDelay() {
log.info("fixed rate " + Thread.currentThread().getName());
sleep(2000);
}
위의 2개의 작업이 하나의 쓰레드로 동작하면 정상적으로 동작하지 않는다.
실행결과
2023-08-21 06:53:46.561 INFO 98994 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate scheduling-1
2023-08-21 06:53:48.563 INFO 98994 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed delay scheduling-1
2023-08-21 06:53:50.564 INFO 98994 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate scheduling-1
2023-08-21 06:53:52.566 INFO 98994 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate scheduling-1
2023-08-21 06:53:54.572 INFO 98994 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate scheduling-1
2023-08-21 06:53:56.576 INFO 98994 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate scheduling-1
2023-08-21 06:53:58.582 INFO 98994 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate scheduling-1
2023-08-21 06:54:00.587 INFO 98994 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed delay scheduling-1
2023-08-21 06:54:02.592 INFO 98994 --- [ scheduling-1] c.e.scheduler.service.SchedulerService : fixed rate scheduling-1
여러 개의 쓰레드를 사용하여 동작시켜 보자.
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.setPoolSize(5);
threadPoolTaskScheduler.setThreadNamePrefix("multi-scheduler-");
return threadPoolTaskScheduler;
}
실행결과
2023-08-21 06:57:38.173 INFO 99125 --- [lti-scheduler-1] c.e.scheduler.service.SchedulerService : fixed rate multi-scheduler-1
2023-08-21 06:57:38.173 INFO 99125 --- [lti-scheduler-2] c.e.scheduler.service.SchedulerService : fixed delay multi-scheduler-2
2023-08-21 06:57:40.174 INFO 99125 --- [lti-scheduler-2] c.e.scheduler.service.SchedulerService : fixed rate multi-scheduler-2
2023-08-21 06:57:41.179 INFO 99125 --- [lti-scheduler-3] c.e.scheduler.service.SchedulerService : fixed delay multi-scheduler-3
2023-08-21 06:57:42.179 INFO 99125 --- [lti-scheduler-1] c.e.scheduler.service.SchedulerService : fixed rate multi-scheduler-1
2023-08-21 06:57:44.180 INFO 99125 --- [lti-scheduler-2] c.e.scheduler.service.SchedulerService : fixed rate multi-scheduler-2
2023-08-21 06:57:44.185 INFO 99125 --- [lti-scheduler-5] c.e.scheduler.service.SchedulerService : fixed delay multi-scheduler-5
2023-08-21 06:57:46.182 INFO 99125 --- [lti-scheduler-3] c.e.scheduler.service.SchedulerService : fixed rate multi-scheduler-3
멀티 쓰레드가 각 작업별로 실행이 되어 예상한 시간대로 동작을 한다.
스프링부트 설정
스케쥴러의 풀 사이즈를 증가시키기 위해서 아래 설정을 추가하면 된다.
spring:
task.scheduling.pool.size: 5
ShedLock으로 분산환경에서 스케쥴링 인스턴스 사용
@Scheduler
어노테이션으로 스케쥴 잡을 사용하기는 쉬우나 분산 환경에서는 여러 인스턴스의 스케쥴링 동기화를 처리하기 어렵다.
ShedLock
은 여러 인스턴스(분산 환경)에서 동시에 최대 한번만 실행되게 해주는 라이브러리다. 이 방식은 작업이 실행될 때 다른 작업이 실행되지 못하게 락(Lock) 매커니즘을 사용한다.
ShedLock은 여러 인스턴스에 외부 데이터 저장소를 사용한다. (Mongo, JDBC, Redis, Hazelcast, Zookeeper 등)
의존성 추가
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-spring</artifactId>
<version>4.27.0</version>
</dependency>
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-jdbc-template</artifactId>
<version>4.27.0</version>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
lockProvider 추가
@Configuration
@EnableSchedulerLock(defaultLockAtLeastFor = "PT1S", defaultLockAtMostFor = "PT5S")
public class SchedulerLockJdbcConfig {
@Bean
public LockProvider lockProvider(DataSource dataSource) {
return new JdbcTemplateLockProvider(
JdbcTemplateLockProvider.Configuration.builder()
.withJdbcTemplate(new JdbcTemplate(dataSource))
.usingDbTime() // Works on Postgres, MySQL, MariaDb, MS SQL, Oracle, DB2, HSQL and H2
.build()
);
}
}
defaultLockAtLeastFor
은 메소드 간의 시간의 차이를 나타낸다. PT1S
는 최초 1초간은 락을 걸겠다는 의미.
defaultLockAtMostFor
은 실행하다가 노드가 죽었을 때 락을 얼마나 길게 유지할 것인지를 나타내는 시간. PT5S
는 5초후에 락을 풀겠다는 의미
테이블 생성
DROP TABLE IF EXISTS shedlock;
CREATE TABLE shedlock(
name VARCHAR(64) NOT NULL,
lock_until TIMESTAMP(3) NOT NULL,
locked_at TIMESTAMP(3) NOT NULL DEFAULT CURRENT_TIMESTAMP(3),
locked_by VARCHAR(255) NOT NULL,
PRIMARY KEY (name)
);
@SchedulerLock 어노테이션을 적용함으로써 스케쥴링을 할 수 있다.
@Scheduled(fixedDelay = 3000)
@SchedulerLock(name = "myscheduledTask")
public void fixedRateWithShedLock() {
log.info("shedLock " + Thread.currentThread().getName());
}
두 개의 Spring Application을 실행해보자.
[1번 서버]
# 없음
[2번 서버]
2023-08-21 07:48:18.833 INFO 1516 --- [lti-scheduler-1] c.e.scheduler.service.SchedulerService : shedLock multi-scheduler-1
2023-08-21 07:48:21.844 INFO 1516 --- [lti-scheduler-1] c.e.scheduler.service.SchedulerService : shedLock multi-scheduler-1
2023-08-21 07:48:24.848 INFO 1516 --- [lti-scheduler-2] c.e.scheduler.service.SchedulerService : shedLock multi-scheduler-2
2023-08-21 07:51:37.222 INFO 1516 --- [ti-scheduler-10] c.e.scheduler.service.SchedulerService : shedLock multi-scheduler-10
2023-08-21 07:51:40.225 INFO 1516 --- [ti-scheduler-10] c.e.scheduler.service.SchedulerService : shedLock multi-scheduler-10
2023-08-21 07:51:43.227 INFO 1516 --- [ti-scheduler-10] c.e.scheduler.service.SchedulerService : shedLock multi-scheduler-10
2023-08-21 07:51:46.229 INFO 1516 --- [ti-scheduler-10] c.e.scheduler.service.SchedulerService : shedLock multi-scheduler-10
2번 서버에서만 3초마다 실행이 된다. 2번 서버를 죽이면 1번 서버에서 실행이 다시 된다.
참고
https://www.baeldung.com/spring-scheduled-tasks