Spring

Spring Boot + Quartz 적용하기 - 3

댕발바닥 2024. 9. 17. 11:14

앞서 Spring Boot + Quartz 스케줄링을 구현해보았습니다.

해당 포스팅은 클러스팅 환경에서 동작할 수 있게 구현해보았습니다.

 

 

1. Quartz 데이터베이스 생성

Quartz 클러스터 환경을 구축하기 위해서는 메타 정보를 관리하는 데이터베이스를 생성해 주어야한다.

 

인텔리제이를 사용하는 사용자는 아래처럼 sql을 찾아서 테이블을 생성해주면 됩니다.

table sql

 

아래처럼 스키마를 구성해주면 됩니다.

database schema

 

이후 application.yml에 아래와 같이 설정해주었습니다.

application.yml

 

2. quartz properties 설정

이제는 quartz properties 설정을 해주면 됩니다. datasource를 설정해주고 클러스팅 처리 설정을 ture로 진행해주세요.

우리는 클러스터링 환경을 구성하기 위하여 JDBC JobStore를 선택해줘야합니다.

 

  • JobStoreTX: 트랜잭션을 수동으로 관리하는 데이터베이스 저장소.
  • JobStoreCMT: 트랜잭션이 컨테이너 관리 트랜잭션(CMT)에 의해 관리되는 데이터베이스 저장소. 주로 EJB 컨테이너 환경에서 사용됩니다.

driverDelegateClass

JDBCJobStore에서 데이터베이스와 상호작용할 때 사용되는 JDBC 드라이버를 지정하는 설정입니다. Quartz는 다양한 데이터베이스 시스템과 호환되며, 각 데이터베이스마다 SQL 쿼리의 차이가 있기 때문에 Quartz는 데이터베이스 별로 적합한 Driver Delegate 클래스를 제공합니다.

 

아래 설정 표준 설정을 진행했습니다.

 

 

org.quartz.scheduler.instanceName = QUAARTZ-SCHEDULER

# 유니크한 스케줄러 작업을 위한 ID설정
org.quartz.scheduler.instanceId = AUTO

# quartz 메타데이터가 사용될 dataSource 설정
org.quartz.dataSource.quartzDataSource.URL= 데이터베이스 URL
org.quartz.dataSource.quartzDataSource.driver=com.mysql.cj.jdbc.Driver
org.quartz.dataSource.quartzDataSource.user= 계정
org.quartz.dataSource.quartzDataSource.password= 패스워드
org.quartz.dataSource.quartzDataSource.provider=hikaricp

# quartz dataSource를 위에서 설정한 이름으로 설정
org.quartz.jobStore.dataSource = quartzDataSource


# jobStroe 설정
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.useProperties=false

# quartz 메타데이터 테이블 명 prefix (default QRTZ_)
org.quartz.jobStore.tablePrefix=QRTZ_
# 잘못된 실행에 대한 트리거 허용 시간 (default 60000)
org.quartz.jobStore.misfireThreshold=60000
# 인스턴스 간 검사하는 빈도 (default 15000)
org.quartz.jobStore.clusterCheckinInterval=15000
# 클러스터링 처리 기능 설정 (default false)
org.quartz.jobStore.isClustered=true

org.quartz.threadPool.threadNamePrefix = QuartzScheduler
# threadPool 설정
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
# Thread 수 설정 (1 ~ 100) (default -1)
org.quartz.threadPool.threadCount=10
# Thread 우선 순위 (1 ~ 10) (default 5)
org.quartz.threadPool.threadPriority=5

 

3. quartz properties 적용

 

앞서 적용한 SchedulerFactoryBean 추가로 properties를 적용해주었습니다. 위에 properties를 resource 밑에 따로 파일을 설정해주었으며 해당 파일 load하여 설정을 진행했습니다.

package com.kr.quartz.config;

import com.kr.quartz.listener.MyJobListener;
import com.kr.quartz.listener.MyTriggerListener;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ClassPathResource;
import org.springframework.scheduling.quartz.SchedulerFactoryBean;
import org.springframework.scheduling.quartz.SpringBeanJobFactory;

import javax.sql.DataSource;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

@Slf4j
@Configuration
@RequiredArgsConstructor
public class SchedulerConfig {


    @Bean
    public SchedulerFactoryBean schedulerFactoryBean(ApplicationContext applicationContext) {
        var schedulerFactoryBean = new SchedulerFactoryBean();

        var jobFactory = new SpringBeanJobFactory();

        schedulerFactoryBean.setJobFactory(jobFactory); // job factory 등록
        schedulerFactoryBean.setApplicationContext(applicationContext); // context 등록

        schedulerFactoryBean.setOverwriteExistingJobs(true); // job 중복 허용 (덮어쓰기)
        schedulerFactoryBean.setWaitForJobsToCompleteOnShutdown(true); // shutdown wait 속성

        schedulerFactoryBean.setGlobalTriggerListeners(new MyTriggerListener());
        schedulerFactoryBean.setGlobalJobListeners(new MyJobListener()); // job 리스너 등록

        Properties properties = new Properties();
        ClassPathResource classPathResource = new ClassPathResource("/quartz.properties");
        try {
            properties.load(classPathResource.getInputStream());
        } catch (IOException e) {}

        schedulerFactoryBean.setQuartzProperties(properties);

        return schedulerFactoryBean;
    }

}

 

지금은 Job이 있는경우는 삭제해서 재등록하는 방식으로 처리해두었습니다. 실제에서는 이런방식이 아닌 변경 감지 후 처리하는게 맞을거 같습니다.

 

이제 두개의 프로세스를 생성해서 적용이 되는지 확인해보겠습니다.

 

일반 Job경우는 10초 단위, QuartzJobBean은 5초 단위로 실행하였습니다.

왼쪽 서버에서 모든 Job들이 수행되다 2번째 서버가 동작되자 Job들이 분산되어 실행되는것을 확인 할 수 있었습니다.

quartz 클러스터링

 

오른쪽 서버를 shutdown하여 fail-over 처리에 대해서 확인해보았습니다. 앞선 인스턴스 검사 시간에 따라 약간의 딜레이는 존재하나 정상적으로 왼쪽 서버가 인스턴스가 죽은것을 확인하고 fail-over 되는것을 확인할 수 있었습니다.

quartz fail-over

 

이로써 Quartz에 사용법에 대해서 확인하였습니다. Bean 적용을 통하여 Spring과 함께 사용하여 좀더 효과적으로 사용 할 수 있을거 같습니다.