이번 게시글에서는 Quartz 스케줄러를 활용한 경험을 공유하려고 한다. Quartz를 도입하면서 필요했던 기본 설정 과정이 예상외로 복잡했기 때문에, 이에 대해서 기록해두려고 한다.
Configuration of Quartz is typically done through the use of a properties file, in conjunction with the use of StdSchedulerFactory (which consumes the configuration file and instantiates a scheduler).
By default, StdSchedulerFactory load a properties file named “quartz.properties” from the ‘current working directory’. If that fails, then the “quartz.properties” file located (as a resource) in the org/quartz package is loaded. If you wish to use a file other than these defaults, you must define the system property ‘org.quartz.properties’ to point to the file you want.
spring:
quartz:
scheduler-name: QuartzScheduler # Quartz 스케줄러 이름
jdbc:
initialize-schema: never # Quartz만의 저장소 사용을 위한 스키마 수동 생성
job-store-type: jdbc # JobStore 유형 설정
logging:
level:
org:
quartz: DEBUG # Quartz 로그 레벨 설정
Load-balancing occurs automatically, with each node of the cluster firing jobs as quickly as it can. When a trigger’s firing time occurs, the first node to acquire it (by placing a lock on it) is the node that will fire it.
Only one node will fire the job for each firing. What I mean by that is, if the job has a repeating trigger that tells it to fire every 10 seconds, then at 12:00:00 exactly one node will run the job, and at 12:00:10 exactly one node will run the job, etc. It won’t necessarily be the same node each time - it will more or less be random which node runs it. The load balancing mechanism is near-random for busy schedulers (lots of triggers) but favors the same node for non-busy (e.g. few triggers) schedulers.
# 스케줄러 기본 설정
org.quartz.scheduler.instanceName=QuartzScheduler
org.quartz.scheduler.instanceId=AUTO
# 스레드 풀 설정
org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
org.quartz.threadPool.threadCount=20
org.quartz.threadPool.threadPriority=5
org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true
org.quartz.threadPool.threadNamePrefix=QuartzThread-
# JobStore 설정
org.quartz.jobStore.class=org.quartz.impl.jdbcjobstore.JobStoreTX
org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
org.quartz.jobStore.tablePrefix=QRTZ_
org.quartz.jobStore.isClustered=true
org.quartz.jobStore.misfireThreshold=1000
org.quartz.jobStore.clusterCheckinInterval=20000
# org.quartz.jobStore.useProperties=true
org.quartz.jobStore.dataSource=quartz
# 데이터 소스 설정
org.quartz.dataSource.quartz.provider=hikaricp
org.quartz.dataSource.quartz.driver=com.mysql.cj.jdbc.Driver
org.quartz.dataSource.quartz.URL=jdbc:mysql://127.0.0.1:3306/quartz?useSSL=false&useUnicode=true&serverTimezone=Asia/Seoul&allowPublicKeyRetrieval=true
org.quartz.dataSource.quartz.user=root
org.quartz.dataSource.quartz.password=2721
org.quartz.dataSource.quartz.maxConnections=30
앞에서 initialize-schema: never을 통해 스키마를 수동으로 생성해주어야한다.
uartz-scheduler/distribution/src/main/assembly/root/docs/dbTables/
public class AutoWiringSpringBeanJobFactory extends SpringBeanJobFactory implements ApplicationContextAware {
private transient AutowireCapableBeanFactory beanFactory;
@Override
public void setApplicationContext(ApplicationContext applicationContext)
throws BeansException {
beanFactory = applicationContext.getAutowireCapableBeanFactory();
}
@Override
protected Object createJobInstance(TriggerFiredBundle bundle) throws Exception {
final Object job = super.createJobInstance(bundle);
beanFactory.autowireBean(job);
return job;
}
}
@Autowired를 사용하기 위해 설정한다.
@Slf4j
@Configuration
@RequiredArgsConstructor
public class QuartzConfig {
private final ApplicationContext applicationContext;
private final PlatformTransactionManager platformTransactionManager;
@Bean
public SchedulerFactoryBean schedulerFactoryBean() {
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
AutoWiringSpringBeanJobFactory autoWiringSpringBeanJobFactory = new AutoWiringSpringBeanJobFactory();
autoWiringSpringBeanJobFactory.setApplicationContext(applicationContext);
schedulerFactoryBean.setJobFactory(autoWiringSpringBeanJobFactory);
schedulerFactoryBean.setOverwriteExistingJobs(true);
schedulerFactoryBean.setAutoStartup(true);
schedulerFactoryBean.setTransactionManager(platformTransactionManager);
schedulerFactoryBean.setQuartzProperties(quartzProperties());
return schedulerFactoryBean;
}
private Properties quartzProperties() {
PropertiesFactoryBean propertiesFactoryBean = new PropertiesFactoryBean();
propertiesFactoryBean.setLocation(new ClassPathResource("quartz.properties"));
Properties properties = null;
try {
propertiesFactoryBean.afterPropertiesSet();
properties = propertiesFactoryBean.getObject();
} catch (IOException e) {
e.printStackTrace();
}
return properties;
}
}
참고로 해당 설정을 하여도 @RequiredArgConstructor가 작동하지 않으므로 @Autowired를 사용해야한다.
설정하지 않으면 사진처럼 의존성을 주입받지 못한다.
크론 표현식을 이용하여 매분 5초일때 마다 job을 실행하도록 하였다.
매분 5초일때 마다 다른 인스턴스에서 작동하는 모습이다.
It won’t necessarily be the same node each time - it will more or less be random which node runs it. The load balancing mechanism is near-random for busy schedulers (lots of triggers) but favors the same node for non-busy (e.g. few triggers) schedulers.
공식문서에서 설명한 내용처럼 랜덤한 방식으로 인스턴스를 선정하는 모습도 볼 수 있다.