노약자를 위한 AI 말동무 서비스, POPPET 서비스의 개발 일대기입니다.
스케쥴링을 통해 주기적으로 사용자에게 이메일을 전송하는 기능을 구현했습니다.
👇 이메일 전송 관련 포스트 👇
https://velog.io/@dooo_it_ly/SpringBoot-JavaMailSender을-이용한-비동기-이메일-발송-기능-구현
Spring Scheduler은 Spring Framework의 일부로, 정해진 주기에 태스크를 수행하는 기능을 구현할 때 유용한 기능입니다. Spring Scheduler를 사용하는 방법과 쓰레드풀을 커스텀하는 방법에 대해 알아보겠습니다.
해당 어노테이션을 통해 스케쥴링 기능을 사용하도록 설정합니다.
@SpringBootApplication
@EnableJpaAuditing
@EnableScheduling
public class PoppetApplication {
public static void main(String[] args) {
SpringApplication.run(PoppetApplication.class, args);
}
@Component
annotation을 사용합니다.@Scheduled
annotation을 사용합니다.@Slf4j
@Component
@RequiredArgsConstructor
public class EmailSendScheduler {
private final EmailService emailService;
// 매일 12시에 스케쥴러 수행
@Scheduled(cron = "0 0 12 * * *")
public void sendUnsentEmails() {
emailService.sendEmail();
log.info("[*] {} Email 전송 완료", LocalDateTime.now());
}
}
initDelay
: 스케쥴러 초기화 후 정해진 시간만큼 대기한 후 fixedDelay
에 명시된 밀리초 간격으로 수행합니다.@Scheduled(fixedDelay = 1000, initialDelay = 5000)
public void scheduleWithFixedDelay() throws InterruptedException {
log.info("[*] Success to fixedDelay Scheduler : {}", LocalDateTime.now());
Thread.sleep(1000L);
}
1초 sleep한 후 1초 대기를 반복하며 함수를 수행하는 것을 확인할 수 있습니다.
@Scheduled(fixedRate = 1000)
public void scheduleWithFixedRate() throws InterruptedException {
log.info("[*] Success to fixedRate Scheduler : {}", LocalDateTime.now());
Thread.sleep(1000L);
}
fixedDelay와 다르게, sleep하는 1초와 관계없이 1초마다 함수를 수행하는 것을 확인할 수 있습니다.
@Scheduled(cron = "0/5 * * * * *")
public void scheduleWithCron() throws InterruptedException {
log.info("[*] Success to Cron Scheduler : {}", LocalDateTime.now());
}
표현식을 통해 지정한대로 5초마다 함수를 수행하는 것을 확인할 수 있습니다.
표현식은 다음과 같은 기호로 나타낼 수 있으며, 스케쥴러 주기는 초 단위로 설정할 수 있습니다.
* | * | * | * | * | * |
---|---|---|---|---|---|
초 | 분 | 시 | 일 | 월 | 요일 |
0-59 | 0-59 | 0-23 | 1-31 | 1-12 | 0-7 (SUN-SAT) |
* * * * * * // 매 초마다 실행
0 0 0/1 * * * // 매일 1시간 간격으로 실행
0 0 12 * * * // 매일 12시에 실행
0 0/10 12 * * * // 매일 12시부터 10분 간격으로 실행
0 0 12-14 * * * // 매일 12시, 13시, 14시에 실행
위의 내용으로도 충분히 서비스 단에서 스케쥴러 기능을 구현해낼 수 있습니다.
그러나, 스케쥴러는 단일 쓰레드로 운영됩니다. 아래 테스트로 인해 두 개의 스케쥴러 함수는 scheduling-1 thread에 의해 호출되는 것을 알 수 있습니다.
@Scheduled(fixedDelay = 1000, initialDelay = 5000)
public void scheduleWithFixedDelay() throws InterruptedException {
Thread.sleep(1000L);
log.info("[*] fixedDelay Scheduler's Thread Name : {}", Thread.currentThread().getName());
}
@Scheduled(cron = "0/5 * * * * *")
public void scheduleWithCron() throws InterruptedException {
log.info("[*] Cron Scheduler's Thread Name : {}", Thread.currentThread().getName());
}
스케쥴러 작업이 많아질 경우를 대비하여 쓰레드 풀을 임의로 커스텀해보겠습니다!
spring:
task:
scheduling:
pool:
size: 8
thread-name-prefix: custom-scheduler
위의 설정에 따른 결과는 다음과 같습니다.
기존 scheduling-1 쓰레드로만 수행되던 작업들을 지정한 prefix를 이름으로 하는 쓰레드들이 나누어 수행하는 것을 확인할 수 있습니다.
YML 파일이 아닌 config 파일을 통해 스케쥴러를 커스텀해보겠습니다.
수행 결과는 3-1과 동일합니다.
@Configuration
public class SchedulerConfig {
private final int POOL_SIZE = 8;
@Bean
public TaskScheduler taskScheduler() {
ThreadPoolTaskScheduler threadPoolTaskScheduler = new ThreadPoolTaskScheduler();
threadPoolTaskScheduler.setPoolSize(POOL_SIZE);
threadPoolTaskScheduler.setThreadNamePrefix("custom-scheduler");
return threadPoolTaskScheduler;
}
}
Spring Scheduler을 활용해 간단하게 스케쥴링 기능을 구현할 수 있습니다. 다음 포스트에서는 스케쥴링 주기를 동적으로 변경하는 기능에 대해 다루어보겠습니다.
Reference
https://velog.io/@developer_khj/Spring-Boot-Scheduler-Scheduled
https://www.baeldung.com/spring-scheduled-tasks
https://jeong-pro.tistory.com/186