-
Quartz (1)🌱 spring/🚛 spring batch 2022. 12. 22. 17:59
Quartz ❓
Job Scheduling 라이브러리
- 자바로 개발되어 자바 프로그램 어느 것에서도 쉽게 통합해 개발할 수 있다.
- 수십에서 수천 개의 작업을 실행할 수 있다.
- 간단한 interval 형식이나 Cron 표현식으로 복잡한 스케줄링도 지원한다.
- ex) 매 주 토요일 새벽 2시에 실행하는 작업, 매월 마지막 날에 실행하는 작업 등
Cron 표현식
Cron 표현식은 Cron 스케줄러의 정규 표현식이다.
총 7개의 필드로 구성되어 있다.
- 필드와 특수문자를 조합하여 스케줄링을 조절할 수 있다.
* * * * * * * 초 분 시 일 월 요일 년도(생략가능)
특수문자 의미
특수문자 의미
특수문자 의미 * 모든 값
minutes 에 사용시 매분 동작
hours 에 사용시 매시 동작? 해당 필드 미사용 - 특정 기간 지정
- minutes 에 10-11 →10, 11 분에 동작, 특정 시간(날짜) 지정
- hours 에 2, 4, 6 → 2시, 4시, 6시 동작/ 시간 시간 / 반복 단위
- minutes에 5 / 10 → 5분에 시작 10분 단위로 동작L 마지막 날짜(요일)에 동작 W 가장 가까운 평일에 동작 LW 해당 달의 마지막 평일에 동작 # 몇 번째 주와 요일 설정
- 6#3 → 3번째 주 금요일에 동작Quartz 장단점
장점
- DB 기반으로 스케줄러 간의 Clustering 기능을 제공
- 시스템 Fail-over 와 Round-robin 방식의 로드 분산처리 지원
- In-memory Job Scheduler 제공
- 여러 Plug-in 제공
- ShutdownHookPlugin : JVM 종료 이벤트 캐치 → 스케줄러에게 종료를 알려줌
- LoggingJobHistoryPlugin : Job 실행에 대한 로그를 남긴다.
단점
- Clustering 기능을 제공하지만, 단순한 random 방식
- 완벽한 Cluster 간의 로드 분산은 안된다.
- 어드민 UI를 제공하지 않는다.
- 스케줄링 실행에 대한 History는 보관하지 않는다.
- Fixed Delay 타입을 보장하지 않아 추가 작업이 필요하다.
🔧 Quartz 아키텍처 / 구성요소
용어
- Job
- Quartz API에서 단 하나의 메서드를 가진 execute Job 인터페이스를 제공
- 실제 수행해야 하는 작업을 이 메서드에서 구현
- Job의 Trigger가 발생하면 스케줄러는 JobExecutionContext 객체를 넘겨주고 execute 메서드를 호출한다.
- JobDataMap
- Job 인스턴스가 실행할 때 사용할 수 있게 원하는 정보를 담을 수 있는 객체
- Job 실행시 JobDataMap 객체에 접근하여 사용할 수 있다.
- JobDetail
- Job을 실행시키기 위한 정보를 담고 있다.
- 이름, 그룹, JobDataMap 속성 등
- Trigger가 Job을 수행할 때 이 정보를 기반으로 스케줄링을 한다.
- Trigger
- Job을 실행시킬 스케줄링 조건등을 담고 있다.
- Scheduler는 이 정보를 기반으로 Job를 수행시킨다.
- Trigger와 Job의 관계
- 1 : 1
- 반드시 하나의 Trigger는 하나의 Job을 지정
- N : 1
- 하나의 Job을 여러 시간때로 실행 가능
- ex) 매주 월요일, 매시간마다
- 1 : 1
- Trigger 2가지 형태
- SimpleTrigger
- 특정 시간에 Job을 수행할 때 사용
- 반복 횟수와 실행 간격등을 지정할 수 있다.
- CronTrigger
- cron 표현식으로 Trigger를 정의하는 방식
- SimpleTrigger와 같이 단순 반복뿐만 아니라 더 복잡한 스케줄링도 지정할 수 있다.
- SimpleTrigger
- Misfire Instructions
- Misfire는 Job이 실행되어야 하는 시간, fire time을 지키지 못한 실행 불발을 의미
- Scheduler는 Misfire된 Trigger에 대해 어떻게 처리할 지에 대한 다양한 정책을 지원한다.
- Listener
- Scheduler의 이벤트를 받을 수 있도록 Quartz에서 제공하는 인터페이스
- 2가지를 제공
- JobListener
- Job 실행 전부로 이벤트를 받는다.
- JobStore
- Job과 Trigger 정보를 2가지 방식으로 저장할 수 있다.
- RAMJobStore
- 메모리에 스케줄 정보를 저장
- 기본값
- 성능이 좋지만, 문제가 발생시 데이터를 유지 못한다.
- JDBCJobStore
- DB에 정보를 저장
- 시스템에 문제가 생겨도 스케줄 정보는 유지되어 재시작시 다시 실행할 수 있다.
- JobListener
구조, 흐름
🧑💻 Quartz 기본 구성 구현
의존성
build.gradle
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-quartz', version: '2.7.6'
quartz.properties
org.quartz.scheduler.instanceName = MyQuartzScheduler org.quartz.threadPool.threadCount = 5 org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
- scheduler 인스턴스의 이름
- Job을 실행할 스레드 풀의 스레드 수
- 스케줄링된 JobStore 지정
Job
public class HelloJob implements Job { public HelloJob() { System.out.println("==== Quartz Hello Job Created ===="); } @Override public void execute(JobExecutionContext context) throws JobExecutionException { System.out.println("==== Quartz Hello Job execute ===="); } }
- Job 구현체는 scheduler에 의해 호출될 때마다 생성된다.
JobExecutionContext
- execute 메서드의 파리미터로 넘어가는 인자
- JobDetail 인스턴스가 scheduler에 의해 실행될 때 넘어오고, 실행이 완료된 뒤에는 Trigger로 넘어간다.
JobDetail
JobDetail을 생성하기 위해 JobBuilder 클래스 이용
public class JobDetailBuilder { public JobDetail getJobDetail() { JobDetail jobDetail = newJob(HelloJob.class) .withDescription("Simple Hello Job") .withIdentity("HelloJob", "HelloGroup") .usingJobData("num", 0) .build(); return jobDetail; } }
- newJob(Class<? extends Job> jobClass)
- JobBuilder를 반환하는 메서드
- Job 구현체의 class를 지정해서 넘겨주는 방식과 공란으로 넘겨주는 방식이 있다.
- 공란인 경우 내부에서 ofType 메서드를 호출하여 Job의 class를 넘겨줘야한다.
- withIdentity(name, group)
- Job의 이름, 그룹을 지정하는 메서드
- 그룹은 중복될 수 없다.
- 이름은 그룹에서 유니크하다.
- usignJobData(key, value)
- Job에 넘겨줄 JobData 설정하는 메서드
- key는 오직 String
- value는 String, Integer, Long, Float, Double, Boolean 형태가 가능
JobData ❓
- 단순히 Job의 외부에서 Job의 내부로 데이터를 전달해주기 위함
- Job 객체는 스케줄러에 의해 호출될 때마다 새로운 객체가 생성되는데 이때, 이전 Job과 현재 Job이 데이터를 공유할 수 있는 방법이 필요
- @PersistJobDataAfterExecution 어노테이션으로 JobData가 유지됨을 명시할 수 있다.
JobTrigger
Trigger 생성을 위해 TriggerBuilder가 필요
public class TriggerBuilder { public Trigger getTrigger() { Trigger trigger = newTrigger() .withIdentity("HelloTrigger", "HelloGroup") .startNow() .withSchedule(simpleSchedule() .withIntervalInSeconds(5) .repeatForever()) .build(); return trigger; } }
- newTrigger()
- TriggerBuilder 반환하는 메서드
- withSchedule()
- Trigger가 어떤 스케줄을 따를지 정하는 메서드
- simpleSchedule, cronScheduler이 존재
- startNow() 혹은 startAt() 메서드를 통해 언제부터 스케줄러가 돌아갈지 정할 수 있다.
- 위 코드에서는 job이 등록된 순간부터 scheduler가 멈출때까지 5초간격으로 실행하도록 설정
Scheduler 사용
Scheduler의 인스턴스화
Job 등록, 실행, 중지
public class QuartzSchedulerMain { public static void main(String[] args) { try { SchedulerFactory schedulerFactory = new StdSchedulerFactory(); Scheduler scheduler = schedulerFactory.getScheduler(); scheduler.scheduleJob(new JobDetailBuilder().getJobDetail(), new TriggerBuilder().getTrigger()); scheduler.start(); Thread.sleep(55000); scheduler.shutdown(); } catch (SchedulerException | InterruptedException e) { } } }
- SchedulerFactory
- Scheduler를 생성하는 역할
- 구현체가 필요 - StdSchedulerFactory
- StdSchedulerFactory.getScheduler()
- application.yaml에 접근하여 설정한 ‘MyQuartzScheduler’라는 이름으로 Scheduler 인스턴스를 생성하여 반환
- scheduler.scheduleJob(JobDetail, JobTriger)
- JobDetail과 JobTrigger를 파라미터로 주면 Job을 스케줄에 등록할 수 있다.
- scheduler.start() - scheduler.shutdown()
- 스케줄링 시작 및 정지
실행
JobData
Job에서 JobData 활용하기
public class HelloJob implements Job { public HelloJob() { } @Override public void execute(JobExecutionContext context) throws JobExecutionException { JobDataMap dataMap = context.getJobDetail().getJobDataMap(); int num = dataMap.getInt("num"); System.out.println("==== Quartz Hello Job execute ==== " + num); } }
실행
JobData 유지
@PersistJobDataAfterExecution 어노테이션을 활용하여 Job이 실행될 때마다 num 숫자가 증가하도록 하기
@PersistJobDataAfterExecution public class HelloJob implements Job { public HelloJob() { } @Override public void execute(JobExecutionContext context) throws JobExecutionException { JobDataMap dataMap = context.getJobDetail().getJobDataMap(); int num = dataMap.getInt("num"); System.out.println("==== Quartz Hello Job execute ==== " + num); num++; dataMap.putAsString("num", num); } }
실행
Retry 로직
JobData의 특정 값이 계속 유지되는 것을 이용하여 Job이 실패시 재시도하는 로직을 만들 수 있다.
@PersistJobDataAfterExecution public class HelloJob implements Job { public HelloJob() { } @SneakyThrows @Override public void execute(JobExecutionContext context) throws JobExecutionException { JobDataMap dataMap = context.getJobDetail().getJobDataMap(); int num = dataMap.getInt("num"); if(num > 4) { JobExecutionException e = new JobExecutionException("Retry Failed!!"); e.setUnscheduleAllTriggers(true); throw e; } try { System.out.println("==== Quartz Hello Job execute ==== " + num); // 성공시 num을 0으로 초기화 dataMap.putAsString("num", 0); } catch (Exception e) { // 실패시 num을 1 증가 num++; dataMap.putAsString("num", num); Thread.sleep(6000); JobExecutionException e2 = new JobExecutionException(e); // job will refire immediately e2.refireImmediately(); throw e2; } } }
- 5번까지 재시도
- 각 재시도마다 6000ms 텀을 둔다.
- 계속 실패시 Trigger를 멈춰 Job이 돌아가지 않도록 한다.
참고자료
https://developyo.tistory.com/251
https://prodo-developer.tistory.com/174
https://blog.advenoh.pe.kr/spring/Quartz-Job-Scheduler란/
https://examples.javacodegeeks.com/enterprise-java/quartz/java-quartz-architecture-example/
http://www.quartz-scheduler.org/documentation/quartz-2.3.0/configuration/ConfigMain.html
https://velog.io/@tjeong/Quartz-사용법-공유
https://www.quartz-scheduler.org/api/2.4.0-SNAPSHOT/org/quartz/JobExecutionException.html
http://www.quartz-scheduler.org/documentation/quartz-2.3.0/examples/Example6.html
'🌱 spring > 🚛 spring batch' 카테고리의 다른 글
Quartz를 이용하여 softDelete한 내용을 실제로 delete 하기 (0) 2022.12.28 Quartz2 (0) 2022.12.22 Spring Batch - ItemProcessor (0) 2022.12.16 Spring Batch - ItemWriter (0) 2022.12.16 SpringBatch - ItemReader (0) 2022.12.16