티스토리 뷰
Spring 스케줄러 (ThreadPoolTaskScheduler)를 이용한 동적 스케줄 관리 적용기 (1)
광돌ol 2024. 10. 16. 01:40
들어가기전
마음 맞는 사람들과 사이드 프로젝트를 시작해서 현재 취미로 사이트 운영중에 있는 와중에 스케줄러를 여러번 시간대를 변경하는 일이 발생하였습니다. 그럴 때마다 서버를 재시작해야하는 비효율적인 부분들이 있었습니다. 이렇게 스케줄러의 시간대를 변경하는 것이 자주 있는다면 스케줄러를 관리하는 시스템을 추가하는것이 나을것이라 생각했습니다. 동적으로 관리하게 된다면 아무래도 서버를 재시작할 필요 없이 스케줄러만 재시작하면 되기 때문에 편리할 거라 판단했었습니다.
이를 통해 구현했던 동적 스케줄러 관리에 대한 실습 적용했던 내용을 블로그로 글을 남기고자 작성하게 되었습니다.
동적 스케줄러를 적용하기 위해 어떻게 해야할까?
우선 동적 스케줄러를 위해 고민을 했습니다. Spring에서 제공하는 기술인 Spring 스케줄러 (ThreadPoolTaskScheduler) 아니면 Spring에서 제공하는 Quartz 이 두가지 중에 고민을 했었습니다.
처음에는 두가지의 차이점을 잘 알지 못했습니다. 결론적으로는 둘다 동적으로 스케줄러를 관리할 수 있다는 것은 틀림없으니까요. 그러나 조금씩 공부해보면서 어느 정도 차이가 있었고 각각의 장단점이 있었습니다.
간단하게 이 둘의 차이점에 대해 알아보겠습니다.
Quartz 스케줄러
Quartz는 매우 강력하고 유연한 스케줄러로서 복잡한 작업을 처리할 수 있는 다양한 기능을 제공합니다.
- 복잡한 작업 처리: Quartz는 매우 정교한 스케줄링을 지원하며, 여러 Job을 그룹으로 묶거나 복잡한 작업 체계를 구성할 수 있습니다.
- Job Persistence: Quartz는 데이터베이스에 Job의 상태 및 실행 기록을 저장할 수 있습니다. 즉, 서버 재시작 후에도 작업을 다시 복원할 수 있습니다. 이 기능은 작업이 장시간 실행되거나 작업 실패 후 복구가 필요한 경우에 매우 유용하게 적용됩니다.
- 분산 클러스터링 : Quartz는 클러스터링을 지원하여 여러 서버에서 스케줄을 분산하여 실행할 수 있습니다. 고가용성 및 스케줄링 작업의 신뢰성이 중요한 경우에 유리합니다.
- 다양한 트리거 지원: Quartz는 다양한 트리거(Trigger)를 제공하며, 복잡한 크론 표현식, 반복 주기 등을 사용할 수 있습니다.
- 고급 스케줄 관리 기능 : Job의 시작, 중지, 재시작 , 일시정지 등을 세부적으로 제어할 수 있습니다.
Quartz를 적용하기 좋은 경우
- 작업이 매우 복잡하고 상태 관리가 필요한 경우
- 작업 실패 시 복구가 필요한 경우
- 분산 환경에서 여러 서버간 작업 스케줄링을 해야하는 경우
- 작업 이력이 중요한 경우, 예를 들어 실행 기록, 오류 발생 내역등을 관리해야하는 경우
Spring 스케줄러(ThreadPoolTaskScheduler)
Spring의 ThreadPoolTaskScheduler는 더 가볍고 간단한 스케줄링을 처리하는 데 적합합니다.
Spring의 스케줄링 기능은 주로 @Scheduled 어노테이션을 사용하거나 TaskScheduler를 통해 트리거를 설정합니다.
- 경량성: Spring 스케줄러는 매우 가볍고 간단한 스케줄링을 수행할 수 있습니다. 서버 내에서 간단한 주기적인 작업을 처리하는 데 적합합니다.
- Spring 통합: Spring의 다른 컴포넌트와 매우 잘 통합되어 있으며, 단순한 작업에 매우 적합합니다.
- 크론 표현식 : Spring 스케줄러도 크론 표현식을 제공합니다. 주기적인 작업을 쉽게 설정할 수 있습니다.
- 단순성 : 복잡한 설정이 필요 없고, Spring 프로젝트에 이미 내장되어 있어 추가 의존성 없이 사용 가능합니다.
- 실시간 작업 관리 : 간단한 작업 실행 주기 설정, 정지/재시작 등의 관리가 용이합니다.
Spring 스케줄러를 사용하기 좋은 경우
- 작업이 비교적 단순하고 , 간단한 반복 작업이나 정해진 시간에 실행되는 작업만 필요한 경우
- 상테 저장이 필요하지 않고, 서버 재시작 시 작업 복원이 필요 없는 경우
- 단일 서버에서 작동하는 간단한 스케줄링이 필요한 경우
- Spring 프로젝트 내에서 경량 스케줄러를 사용하고 싶은 경우
스케줄러 방법 선택
저는 위 두가지에 대해 고민을 하다 Spring 스케줄러에서 제공하는 ThreadPoolTaskScheduler를 선택했습니다. 사실 이미 회사에서 서버가 두개로 운영되는 시스템에 Quartz를 적용하여 동적 스케줄러 관리를 개발한 적이 있었습니다. 그때 Quartz를 선택하게 된 결정적 이유는 단일 서버가 아니라는 점이었습니다. 현재 취미로 운영중인 사이트는 단일 서버로 운영 중에 있고 스케줄 또한 단순히 시간에 맞게 스케줄링이 발생만하면 되는 구조였습니다. 그저 스케줄러를 직접 켰다가 껐다가 또는 시간을 재설정 등만 적용하기 위해 여러 조건이 필요로한 Quartz를 적용하기에는 상당히 무거운 기술이라고 생각이 들어 위와 같이 Spring 스케줄러를 선택하게 되었습니다.
Spring 스케줄러 적용하기
1. Schedule을 관리할 테이블을 생성하기
현 프로젝트는 SpringBoot3 이고 JPA를 이용하고 있습니다. 그렇기 때문에 Schedule을 관리할 엔티티를 작성해야합니다.
@Entity
public class CmsScheduleConfig {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idx;
@Comment("제목")
private String title;
@Comment("스케줄 시간 설정")
private String crontab;
@Comment("컨테이너 정보")
private String contName;
@Comment("bean 정보")
private String beanName;
@Comment("메소드 정보")
private String methodName;
@Comment("사용여부")
@ColumnDefault(value = "Y")
@Column(length = 1)
private String useYn;
@Comment("등록일자")
@CreatedDate
private LocalDateTime createDateTime;
@Comment("수정일자")
@LastModifiedDate
private LocalDateTime modifyDateTime;
}
간단하게 위와 같이 스케줄러를 적용하기 위한 여러 조건들을 담은 테이블을 생성합니다.
추가적으로 Spring 스케줄러로 돌리지만 스케줄이 실행됬다는 기록은 디비에 로그로 남기면 좋을 것 같아 Schedule log 테이블을 하나더 추가하였습니다. (해당 부분은 너무 길어지니 넣지 않았습니다.)
그리고 간단하게 스케줄러를 관리하는데 사용할 Controller API 부분도 생성해줍니다.
@Tag(name = "CMS 스케줄러 관리 컨트롤러")
@RestController
@RequiredArgsConstructor
public class CmsSchedulerController
2. ThreadPoolTaskScheduler를 적용한 Service 만들기
아래 코드는 ThreadPoolTaskScheduler 외에 스케줄러를 bean 과 메소드로 관리할 것이기 때문에 ApplicationContext도 서언해 줍니다.
@Service
public class CmsSchedulerService {
private final ApplicationContext applicationContext;
private final ThreadPoolTaskScheduler taskScheduler;
private final SchedulerConfigRepository schedulerConfigRepository;
private final Map<Long, ScheduledFuture<?>> scheduledTasks = new HashMap<>();
public CmsSchedulerService(ApplicationContext applicationContext, ThreadPoolTaskScheduler taskScheduler, SchedulerConfigRepository schedulerConfigRepository) {
this.applicationContext = applicationContext;
this.taskScheduler = taskScheduler;
this.schedulerConfigRepository = schedulerConfigRepository;
}
3. ThreadPoolTaskScheduler를 실행할 startTask와 scheduleTask를 생성
/**
* 시작 task
*/
public void startTask() throws Exception {
List<CmsScheduleConfig> activeSchedulers = schedulerConfigRepository.findByUseYn("Y");
for(CmsScheduleConfig config : activeSchedulers) {
scheduleTask(config);
}
}
/**
* 실행 스케줄러
* @param scheduleConfig
*/
public void scheduleTask(CmsScheduleConfig scheduleConfig) throws Exception {
Object bean = applicationContext.getBean(scheduleConfig.getBeanName());
Method method = bean.getClass().getMethod(scheduleConfig.getMethodName());
System.out.println("bean : "+bean.getClass().getName()+",method : "+method.getName());
ScheduledFuture<?> scheduledFuture = taskScheduler.schedule(() -> {
System.out.println("#### taskScheduler schedule #### ");
try{
method.invoke(bean);
}catch (Exception e) {
e.printStackTrace();
}
},new CronTrigger(scheduleConfig.getCrontab()));
scheduledTasks.put(scheduleConfig.getIdx(),scheduledFuture);
}
위 코드를 보게 되면 startTask에서 우선 Schedule 테이블에 담겨있는 스케줄러들의 정보를 가져옵니다. 그리고 가져온 정보들을 통해 scheduleTask를 통해 스케줄을 실행합니다.
scheduleTaks의 프로세스를 보게되면 우선 method를 실행하기 위해서는 우선 bean 정보를 가져옵니다. bean 정보를 가져오게 되면 bean에 생성된 method의 이름과 일치하는 정보를 추출합니다.
taskScheduler.schedule을 실행하여 해당 method를 실행합니다. 실행시 new CronTrigger를 선언하여 크론 표현식을 적용할 수 있도록 합니다.
그리고 해당 스케줄러들의 정보를 Map<Long, ScheduledFuture<?>> scheduledTasks 에 넣어 메모리로 관리합니다.
4. SchedulerInitializer 클래스를 선언하여 서버가 실행이 될 때 자동으로 실행되도록 합니다.
@Component
@RequiredArgsConstructor
public class SchedulerInitializer {
protected final Logger logger = LoggerFactory.getLogger(this.getClass());
private final CmsSchedulerService cmsSchedulerService;
@PostConstruct
public void init() throws Exception {
logger.info("### scheduler start ###");
cmsSchedulerService.startTask();
}
}
우선 위와 같이 설정하게 된다면 서버가 실행이 될 때 자동적으로 SchedulerInitializer에 선언된 @PostConstruct가 실행이 되면서 스케줄러들이 자동 실행이 됩니다.
이 외에 조금 더 시스템 적으로 로그 관리, 서버가 재시작이 아니고 스케줄러 정보가 변경될 때 마다 재시작이 되도록 설정을 할 수 있습니다.
부가적인 내용은 적용기 2편을 통해 공유하고자 합니다.
감사합니다.
'프로그램 언어 > Spring' 카테고리의 다른 글
Spring Batch 개념을 이해하고 적용해보기 (4) | 2024.10.27 |
---|---|
Spring 스케줄러 (ThreadPoolTaskScheduler)를 이용한 동적 스케줄 관리 적용기 (2) - 로그 관리 (이벤트 리스너) (1) | 2024.10.20 |
Spring Cloud OpenFeign 정리 (1) | 2024.10.16 |
Hazelcast Multicast / Tcp-ip 적용하기 (0) | 2024.02.29 |
hazelcast-client 적용하기(실전편2) (0) | 2024.02.27 |
- Total
- Today
- Yesterday
- Quartz
- Lock
- 개념 이해하기
- 격리수준
- 스케줄러
- 권한
- centos7
- dfs
- MySQL
- Java
- ncp
- 네이버 클라우드
- mybatis
- 알고리즘
- spring
- 이미지
- 도커
- Cache
- Linux
- 정의
- leatcode
- hazelcast
- LocalDate
- 캘린더
- insert
- 리눅스
- docker
- 컨테이너
- 캐시
- dockerfile
일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |