Initial commit
This commit is contained in:
@@ -0,0 +1,221 @@
|
||||
= Quartz Clustering with JDBC-JobStore
|
||||
|
||||
== JDBC-JobStore DB 초기화
|
||||
quartz db schema (배포된 sdl-appendix 내 참조) 를 사용하는 DBMS에 맞는 SQL을 실행해 JDBC-JobStore를 사용하기 위한 Table을 생성한다.
|
||||
|
||||
CAUTION: Tibero DB는 미지원
|
||||
|
||||
== application.properties
|
||||
properties 파일 내용 중 Quartz JDBC-JobStore 관련 설정이다.
|
||||
[source, properties]
|
||||
----
|
||||
# Spring Quartz 설정
|
||||
spring.quartz.job-store-type=jdbc
|
||||
spring.quartz.jdbc.initialize-schema=always
|
||||
|
||||
spring.quartz.datasource.driver-class-name=org.postgresql.Driver
|
||||
spring.quartz.datasource.jdbcUrl=jdbc:postgresql://10.40.87.189:5445/SDL
|
||||
spring.quartz.datasource.username=sdl5
|
||||
spring.quartz.datasource.password=dlatl#123
|
||||
|
||||
spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.PostgreSQLDelegate <1>
|
||||
spring.quartz.properties.org.quartz.jobStore.isClustered=true
|
||||
spring.quartz.properties.org.quartz.jobStore.misfireThreshold=60000
|
||||
spring.quartz.properties.org.quartz.jobStore.clusterCheckinInterval=15000
|
||||
spring.quartz.properties.org.quartz.scheduler.instanceName=sdl-prod <2>
|
||||
spring.quartz.properties.org.quartz.scheduler.instanceId=AUTO <3>
|
||||
spring.quartz.properties.org.quartz.scheduler.rmi.export=false
|
||||
spring.quartz.properties.org.quartz.scheduler.rmi.proxy=false
|
||||
spring.quartz.properties.org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
|
||||
spring.quartz.properties.org.quartz.threadPool.threadCount=10 <4>
|
||||
spring.quartz.properties.org.quartz.threadPool.threadPriority=5
|
||||
spring.quartz.properties.org.quartz.threadPool.threadsInheritContextClassLoaderOfInitializingThread=true
|
||||
spring.quartz.properties.org.quartz.scheduler-name=QuartzScheduler
|
||||
----
|
||||
|
||||
<1> DB 에 따라 Delegate 를 변경한다.
|
||||
+
|
||||
org.quartz.impl.jdbcjobstore.oracle.OracleDelegate, +
|
||||
org.quartz.impl.jdbcjobstore.MSSQLDelegate, +
|
||||
org.quartz.impl.jdbcjobstore.PostgreSQLDelegate
|
||||
<2> Quartz Scheduler 를 구별하는 Property 값으로 Clustering 할 서버에서는 같은 값을 세팅한다.
|
||||
<3> Unique 한 값이어야 하며 AUTO 일 경우 자동 생성된다.
|
||||
<4> Job 을 실행하기 위한 Thread 수
|
||||
|
||||
기타 자세한 Property 값은 http://www.quartz-scheduler.org/documentation/quartz-2.3.0/configuration/ConfigJDBCJobStoreClustering.html[Quartz 메뉴얼, window=_blank]을 참고 한다.
|
||||
|
||||
== QuartzClusteringConfig
|
||||
|
||||
QuartzClusteringConfig 파일은 Spring 에 등록된 CronTriggerFactoryBean 을 실행 하도록 SchedulerFactoryBean이 정의되어 있다.
|
||||
|
||||
[source, java]
|
||||
----
|
||||
@Bean
|
||||
public SchedulerFactoryBean setSchedulerFactoryBean(ApplicationContext applicationContext) {
|
||||
SchedulerFactoryBean schedulerFactoryBean = new SchedulerFactoryBean();
|
||||
|
||||
AutoWiringSpringBeanJobFactory jobFactory = new AutoWiringSpringBeanJobFactory();
|
||||
jobFactory.setApplicationContext(applicationContext);
|
||||
|
||||
schedulerFactoryBean.setJobFactory(jobFactory); <1>
|
||||
schedulerFactoryBean.setDataSource(quartzDataSource()); <2>
|
||||
schedulerFactoryBean.setApplicationContext(applicationContext);
|
||||
schedulerFactoryBean.setTransactionManager(quartzTransactionManager()); <3>
|
||||
schedulerFactoryBean.setWaitForJobsToCompleteOnShutdown(true);
|
||||
schedulerFactoryBean.setStartupDelay(60);
|
||||
schedulerFactoryBean.setOverwriteExistingJobs(true);
|
||||
|
||||
Properties properties = new Properties();
|
||||
properties.putAll(quartzProperties.getProperties()); <4>
|
||||
schedulerFactoryBean.setQuartzProperties(properties);
|
||||
|
||||
schedulerFactoryBean.setTriggers(
|
||||
knoxSyncBatchConfig.knoxApprovalSyncTrigger().getObject(),
|
||||
userBatchConfig.batchUserLongTermCheckTrigger().getObject(),
|
||||
userBatchConfig.batchUserAuthExpiredTrigger().getObject(),
|
||||
sysUseLogBatchConfig.batchSysUseLogTrigger().getObject(),
|
||||
sysUseLogBatchConfig.batchMenuUseHistoryTrigger().getObject()
|
||||
); <5>
|
||||
|
||||
return schedulerFactoryBean;
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConfigurationProperties(prefix = "spring.quartz.datasource")
|
||||
@QuartzDataSource <2>
|
||||
public DataSource quartzDataSource() {
|
||||
return DataSourceBuilder.create().type(HikariDataSource.class).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@QuartzTransactionManager <3>
|
||||
public DataSourceTransactionManager quartzTransactionManager() {
|
||||
return new DataSourceTransactionManager(quartzDataSource());
|
||||
}
|
||||
----
|
||||
|
||||
<1> Spring Bean을 AutoWiring
|
||||
<2> Job 정보를 조회, 저회 저장할 때 사용하는 Datasource (resources-dev, resources-prod/application.properties 참조)
|
||||
<3> Quartz 전용 TransactionManager
|
||||
<4> Quartz 설정 파일 세팅
|
||||
<5> SchedulerFactoryBean 에서 실행 할 Trigger
|
||||
|
||||
== JobDetail 및 Trigger Bean 등록
|
||||
아래는 KnoxSyncBatchConfig에 수행할 Job 클래스를 설정한 JobDetail Bean과 Trigger Bean을 등록한 예이다.
|
||||
|
||||
.KnoxSyncBatchConfig.java
|
||||
[source, java]
|
||||
----
|
||||
@Value("${knox.approval.sync.cron}")
|
||||
private String knoxApprovalSyncCron;
|
||||
|
||||
@Bean
|
||||
public JobDetail knoxApprovalSyncJobDetail() {
|
||||
return JobBuilder
|
||||
.newJob(KnoxSyncBatchExecutor.class) <1>
|
||||
.withIdentity("knoxApprovalSyncJob")
|
||||
.withDescription("Knox Approval Sync Batch")
|
||||
.storeDurably(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CronTriggerFactoryBean knoxApprovalSyncTrigger() {
|
||||
CronTriggerFactoryBean approvalTrigger = new CronTriggerFactoryBean();
|
||||
approvalTrigger.setJobDetail(knoxApprovalSyncJobDetail());
|
||||
approvalTrigger.setCronExpression(knoxApprovalSyncCron);
|
||||
return approvalTrigger;
|
||||
}
|
||||
----
|
||||
|
||||
<1> 배치잡 실행시 QuartzJobBean을 상속 받은 KnoxSyncBatchExecutor의 executeInternal 메소드가 실행된다.
|
||||
|
||||
== Job 구현
|
||||
QuartzJobBean은 Spring Bean을 DI Job의 구현체에서 사용할 수 있도록 Job을 구현한 추상 클래스이며,
|
||||
QuartzJobBean을 상속 받아 executeInternal 메소드를 오버라이드하여 실행할 배치 Service의 메소드를 호출한다.
|
||||
|
||||
.KnoxSyncBatchExecutor.java
|
||||
[source,java]
|
||||
----
|
||||
@Component
|
||||
@Log4j2
|
||||
public class KnoxSyncBatchExecutor extends QuartzJobBean {
|
||||
|
||||
@Value("${node-id}")
|
||||
private String nodeId;
|
||||
|
||||
@Autowired
|
||||
private KnoxApprovalSyncService knoxApprovalSyncService;
|
||||
|
||||
@Override
|
||||
protected void executeInternal(JobExecutionContext jobExecutionContext) throws JobExecutionException {
|
||||
JobDetail jobDetail = jobExecutionContext.getJobDetail();
|
||||
log.info(new StringBuilder()
|
||||
.append("nodeId : ").append(nodeId).append(", ")
|
||||
.append("jobName : ").append(jobDetail.getKey().getName()).append(", ")
|
||||
.append("description : ").append(jobDetail.getDescription())
|
||||
);
|
||||
knoxApprovalSyncService.synchronizeKnox();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
CAUTION: UserBatchExecutor의 경우 JobKey(JobBuilder.withIdentity 설정 값)를 이용해 분기 처리 하고 있으므로 Job 등록시 Name 명 지정에 주의하도록 한다.
|
||||
|
||||
.UserBatchConfig.java
|
||||
[source, java]
|
||||
----
|
||||
/**
|
||||
* 장기 미사용자 관리 Job
|
||||
*/
|
||||
@Bean
|
||||
public JobDetail batchUserLongTermCheckJob() {
|
||||
return JobBuilder
|
||||
.newJob(UserBatchExecutor.class)
|
||||
.withIdentity("batchUserLongTermCheck")
|
||||
.withDescription("User LongTerm Check Batch")
|
||||
.storeDurably(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* 만료 권한 삭제 Job
|
||||
*/
|
||||
@Bean
|
||||
public JobDetail batchUserAuthExpiredJob() {
|
||||
return JobBuilder
|
||||
.newJob(UserBatchExecutor.class)
|
||||
.withIdentity("batchUserAuthExpired")
|
||||
.withDescription("User Auth Expired Batch")
|
||||
.storeDurably(true)
|
||||
.build();
|
||||
}
|
||||
----
|
||||
|
||||
.UserBatchExecutor.java
|
||||
[source, java]
|
||||
----
|
||||
@Override
|
||||
protected void executeInternal(JobExecutionContext jobExecutionContext) {
|
||||
JobDetail jobDetail = jobExecutionContext.getJobDetail();
|
||||
String jobName = jobDetail.getKey().getName();
|
||||
log.info(new StringBuilder()
|
||||
.append("nodeId : ").append(nodeId).append(", ")
|
||||
.append("jobName : ").append(jobName).append(", ")
|
||||
.append("description : ").append(jobDetail.getDescription())
|
||||
);
|
||||
switch (jobName) {
|
||||
case "batchUserLongTermCheck" -> userBatchService.execUserLongTermCheck();
|
||||
case "batchUserAuthExpired" -> userBatchService.execUserAuthValidCheck();
|
||||
case "batchUserAuthExpiredMailing" -> {
|
||||
try {
|
||||
userBatchService.execUserAuthValidAlarmMail();
|
||||
} catch (IOException | MessagingException e) {
|
||||
log.error(e.getMessage(), e);
|
||||
}
|
||||
}
|
||||
default -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
Reference in New Issue
Block a user