Quartz 를 선택한 배경
- Spring Boot에서 사용하기 쉽도록 라이브러리화 및 안정화가 잘되어 있음
- 기존 윈도우 스케줄러를 대체할 스케줄러가 필요했으며, 이중화 처리를 지원했어야 함
Quartz Scheduler 개요
Quartz Scheduler는 Java 기반의 오픈 소스 스케줄링 라이브러리로, 정기적이거나 반복적인 작업을 자동으로 실행할 수 있게 해줍니다.
주로 Spring Boot나 Java 애플리케이션에서 시간 기반 작업을 예약 및 관리하는 데 사용되며, 복잡한 스케줄링 조건도 지원하기 때문에 다양한 비즈니스 요구 사항에 적합합니다.
Quartz Scheduler의 주요 구성 요소
Scheduler
Quartz의 핵심 인터페이스로, 작업을 등록하고, 시작 및 중지, 예약 작업의 상태를 관리하는 역할을 합니다. 보통 StdSchedulerFactory 클래스를 통해 인스턴스를 생성합니다.
Job
작업의 구체적인 로직을 정의하는 인터페이스입니다. Quartz에서는 Job 인터페이스를 구현하여 작업 로직을 작성하며, execute() 메서드에서 수행할 내용을 정의합니다.
JobDetail
Job 클래스의 인스턴스를 나타내며, 작업을 설명하는 메타 데이터를 담고 있습니다. Job과 Trigger를 연결하여 실행될 작업의 세부 사항을 설정합니다.
Trigger
작업을 언제 실행할지를 정의하는 객체입니다. Trigger는 특정 시간이나 간격에 따라 작업을 실행할 수 있도록 다양한 스케줄링 옵션을 제공합니다.
대표적인 트리거로는 SimpleTrigger와 CronTrigger가 있습니다.
- SimpleTrigger: 단순히 정해진 시간이나 반복 간격에 따라 작업을 트리거합니다.
- CronTrigger: Cron 표현식을 사용하여 특정 요일, 월, 시간 등을 포함한 복잡한 주기를 설정할 수 있습니다.
Quartz의 스케줄링 흐름
Scheduler 생성
StdSchedulerFactory를 통해 Scheduler 객체를 생성하고 시작합니다.
Job과 JobDetail 설정
Job 인터페이스를 구현한 클래스를 정의하고, JobDetail 객체를 통해 실행할 작업의 정보를 설정합니다.
Trigger 설정
Trigger를 통해 작업의 실행 조건을 설정합니다.
Scheduler에 Job과 Trigger 등록
scheduleJob() 메서드를 사용하여 JobDetail과 Trigger를 Scheduler에 등록하면, 설정된 주기에 맞게 작업이 실행됩니다.
스케줄러 실행 및 종료
스케줄러는 백그라운드에서 작업을 수행하며, 모든 작업이 완료되면 shutdown() 메서드로 종료할 수 있습니다.
Quartz 사용 예제 (Spring Boot)
import org.quartz.*;
import org.quartz.impl.StdSchedulerFactory;
import org.springframework.boot.CommandLineRunner;
import org.springframework.stereotype.Component;
@Component
public class QuartzExample implements CommandLineRunner {
@Override
public void run(String... args) throws Exception {
// Scheduler 생성 및 시작
Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
scheduler.start();
// JobDetail 생성
JobDetail jobDetail = JobBuilder.newJob(MyJob.class)
.withIdentity("myJob", "group1")
.build();
// Trigger 생성 (매 5초마다 실행)
Trigger trigger = TriggerBuilder.newTrigger()
.withIdentity("myTrigger", "group1")
.withSchedule(SimpleScheduleBuilder.simpleSchedule()
.withIntervalInSeconds(5)
.repeatForever())
.build();
// Scheduler에 JobDetail과 Trigger 등록
scheduler.scheduleJob(jobDetail, trigger);
}
public static class MyJob implements Job {
@Override
public void execute(JobExecutionContext context) {
System.out.println("Quartz Job 실행 중: " + System.currentTimeMillis());
}
}
}
// MyJob이라는 간단한 작업을 정의하고, 매 5초마다 해당 작업을 실행하도록 스케줄링하는 예시
|
Quartz Scheduler의 주요 장점
- 정밀한 시간 관리: 다양한 트리거와 Cron 표현식을 통해 복잡한 일정 및 조건을 설정할 수 있습니다.
- 확장성: 클러스터링 기능을 제공하여 여러 인스턴스에서 스케줄을 관리할 수 있습니다.
- 클러스터링의 장점
- 고가용성(High Availability): 특정 서버 인스턴스가 다운되더라도 다른 인스턴스가 작업을 인계받아 처리함으로써 서비스의 연속성을 보장합니다.
- 부하 분산(Load Balancing): 여러 인스턴스가 작업을 분산하여 처리하기 때문에 대규모 작업도 효과적으로 처리할 수 있습니다.
- 자동 장애 복구: 한 인스턴스가 작업을 수행하는 도중 문제가 발생해도 다른 인스턴스가 해당 작업을 감지하여 수행할 수 있습니다.
- 클러스터링의 장점
- 유연한 설정: XML이나 Properties 파일, Java Config를 통해 유연하게 설정할 수 있습니다.
- 트랜잭션 지원: 데이터베이스에 스케줄을 저장하고 트랜잭션을 통해 작업의 일관성을 유지할 수 있습니다.
Quartz의 Persistent Storage (JDBC JobStore) - Clustering 기능
Quartz는 스케줄 정보를 메모리뿐만 아니라 데이터베이스에도 저장할 수 있어, 애플리케이션이 종료되어도 스케줄 정보를 유지할 수 있습니다.
이를 위해 JDBC JobStore를 제공하며, 데이터베이스를 통해 스케줄러의 상태를 저장하고 관리할 수 있습니다.
Quartz Scheduler로 Clustering 기능 구현을 위한 방식 2가지
- JobStoreTX: 트랜잭션 지원 DB를 사용할 때 사용하는 JobStore.
- JobStoreCMT: 컨테이너 관리 트랜잭션을 사용할 때 사용하는 JobStore.
Quartz Scheduler의 클러스터링
Quartz는 다수의 인스턴스에서 동일한 스케줄러를 공유하는 클러스터링 기능을 제공합니다.
이를 통해 스케줄된 작업이 특정 노드에서 실행되지 않더라도 다른 노드에서 실행될 수 있어 장애에 강하고, 고가용성을 제공합니다.(Active - Active 구성이 가능)
클러스터링 기능을 사용하려면 모든 인스턴스가 동일한 데이터베이스를 공유해야 하며, 각 인스턴스가 동일한 jobStore를 설정하고 isClustered 옵션을 true로 설정해야 합니다.
Quartz Scheduler의 클러스터링 기능은 다중 서버 환경에서 여러 Quartz 인스턴스가 동일한 스케줄을 공유하면서도 작업의 중복 실행을 방지하고, 특정 인스턴스가 다운되더라도 다른 인스턴스가 작업을 이어서 수행하도록 설계된 기능입니다.
이를 통해 고가용성(High Availability, HA)을 제공하며, 클러스터링은 Quartz가 동일한 데이터베이스를 통해 작업의 상태와 스케줄을 공유하고 관리하는 방식으로 이루어집니다.
Quartz Clustering을 위한 Quart Table 목록
Quartz 클러스터링의 기본 원리 및 핵심 기능
- 공유 데이터베이스 사용
클러스터링 모드에서는 모든 Quartz 인스턴스가 동일한 데이터베이스를 공유하여, 스케줄 및 작업 상태 정보를 읽고 씁니다. JobStore가 데이터베이스에 스케줄, 작업의 상태, 실행 로그 등을 기록함으로써 각 인스턴스가 데이터를 일관성 있게 공유할 수 있습니다.
클러스터링 환경에서는 RAMJobStore가 아닌, 데이터베이스 기반의 JobStoreTX나 JobStoreCMT를 사용합니다.
이들 JobStore는 데이터베이스에 모든 스케줄링 정보를 저장하고 관리하는데, 데이터베이스 트랜잭션을 지원하므로 여러 인스턴스에서 동시에 동일한 작업에 접근하는 경우에도 충돌을 방지할 수 있습니다.
이 옵션을 통해 Quartz는 동일한 스케줄과 Job을 관리하는 여러 인스턴스가 동시에 실행되더라도 작업의 중복 실행을 방지합니다.
- 미완료 작업 감지 및 인수
각 인스턴스는 일정 주기마다 데이터베이스를 확인하여 실행하지 못한 작업이나, 다운된 인스턴스가 맡고 있던 작업이 있는지를 감지합니다.
특정 인스턴스가 다운되었을 때, 다른 인스턴스가 해당 작업을 인계받아 수행할 수 있게 됩니다.
- Misfire 정책
작업이 예정된 시점에 실행되지 못했을 경우(예: 이전 작업이 끝나지 않아 트리거되지 않은 경우), 이를 “미스파이어(Misfire)”라고 합니다.
Quartz는 미스파이어 정책을 통해 클러스터 내에서 이러한 작업이 누락되지 않도록 관리합니다. 미스파이어 정책에 따라 작업을 즉시 실행하거나 다음 스케줄로 넘기는 등의 선택을 할 수 있습니다.
- 클러스터링 시의 Lock 메커니즘
Quartz는 작업이 중복 실행되지 않도록 데이터베이스를 통한 Lock을 사용합니다.
특정 인스턴스가 작업을 할당받으면, 데이터베이스에 이를 반영하여 다른 인스턴스가 같은 작업을 실행하지 못하도록 Lock을 겁니다.
이 Lock은 트랜잭션과 함께 관리되며, 작업 완료 후 해제됩니다.
클러스터링 설정 예시
Quartz 클러스터링은 Quartz 설정 파일(quartz.properties)에서 몇 가지 옵션을 통해 구성할 수 있습니다.
예를 들어 다음과 같은 설정으로 클러스터링을 활성화할 수 있습니다.
# quartz.properties
org.quartz.scheduler.instanceName = MyClusteredScheduler # Clustering Grouping에 사용됨
org.quartz.scheduler.instanceId = AUTO
# 클러스터링 활성화
org.quartz.jobStore.isClustered = true # Clustering 활성화
# JobStore 설정
org.quartz.jobStore.class = org.quartz.impl.jdbcjobstore.JobStoreTX # DB 기반의 JobStore 사용(Clustering 사용 시 필수)
org.quartz.jobStore.driverDelegateClass = org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.useProperties = false
org.quartz.jobStore.dataSource = myDS
org.quartz.jobStore.tablePrefix = QRTZ_
# 클러스터에서 인스턴스 상태를 체크하는 간격 (밀리초)
org.quartz.jobStore.clusterCheckinInterval = 20000
# 데이터베이스 연결 설정
org.quartz.dataSource.myDS.driver = org.h2.Driver
org.quartz.dataSource.myDS.URL = jdbc:h2:tcp://localhost/~/quartz
org.quartz.dataSource.myDS.user = quartz
org.quartz.dataSource.myDS.password = quartz
org.quartz.dataSource.myDS.maxConnections = 10
# org.quartz.scheduler.instanceId는 각 인스턴스를 고유하게 식별하기 위한 값이며, AUTO로 설정하면 Quartz가 인스턴스를 자동으로 식별합니다.
# clusterCheckinInterval은 인스턴스가 자신의 상태를 데이터베이스에 업데이트하는 주기를 설정하며, 다른 인스턴스는 이 정보를 통해 다운된 인스턴스를 감지합니다.
|
클러스터링 기능에 대한 오해
Quartz의 클러스터링 기능은 주로 스케줄된 트리거와 작업의 실행을 통제하는 데 중점을 두고 있으며, scheduler.scheduleJob()을 호출하여 새로운 작업을 스케줄링하는 과정 자체는 클러스터링에 의해 직접 제어되지 않습니다.
즉, scheduleJob() 메서드는 클러스터링된 환경 내에서 여러 인스턴스에서 동시에 호출될 수 있습니다. 따라서 여러 인스턴스가 같은 작업을 중복으로 스케줄링하지 않도록 하기 위해서는, 보통 추가적인 로직이나 외부 동기화 메커니즘이 필요할 수 있습니다.
scheduleJob()은 호출되는 시점에 특정 인스턴스에서 새 트리거 및 작업을 스케줄러에 등록하는 함수로, 스케줄링 작업 자체는 데이터베이스에 기록되어 클러스터링된 다른 인스턴스들과 공유됩니다.
하지만 여러 인스턴스에서 동시에 scheduleJob()을 호출할 수 있으므로, 동일한 작업이 여러 번 등록되는 것을 막으려면 다음과 같은 방법이 필요합니다.
중복 스케줄 방지 방법
- 단일 초기화 인스턴스 지정
- 한 인스턴스를 초기화 전용 인스턴스로 지정하여, 해당 인스턴스에서만 초기 스케줄 정보를 설정하도록 합니다. 이를 위해 보통 다음과 같은 방법을 사용할 수 있습니다.
- Spring Profile 또는 환경 변수를 사용하여, 초기화 작업을 수행할 특정 인스턴스를 설정합니다. 예를 들어, SCHEDULE_INITIALIZER라는 플래그를 설정한 인스턴스에서만 초기 스케줄링 작업을 수행하게 하는 방식입니다.
- 초기화를 담당하는 인스턴스가 처음 실행될 때만 scheduleJob을 호출하여 스케줄 정보를 세팅하도록 설정합니다.
'[DEV] App Dev ∕ Web Back > Framework ∕ Spring' 카테고리의 다른 글
[Spring boot & React] 샘플 문항 (0) | 2023.07.10 |
---|---|
[Spring Boot & React] 샘플 문항 2 (0) | 2023.05.24 |
[Spring Boot] 개발 참고자료 목록 (0) | 2022.06.08 |
[Spring Boot] 어노테이션 정리 (0) | 2022.06.02 |
[Spring Boot] 기본 개발환경설정 (0) | 2022.05.26 |
최근댓글