ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 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 아키텍처 / 구성요소


    용어

    1. Job
      • Quartz API에서 단 하나의 메서드를 가진 execute Job 인터페이스를 제공
      • 실제 수행해야 하는 작업을 이 메서드에서 구현
      • Job의 Trigger가 발생하면 스케줄러는 JobExecutionContext 객체를 넘겨주고 execute 메서드를 호출한다.
    2. JobDataMap
      • Job 인스턴스가 실행할 때 사용할 수 있게 원하는 정보를 담을 수 있는 객체
      • Job 실행시 JobDataMap 객체에 접근하여 사용할 수 있다.
    3. JobDetail
      • Job을 실행시키기 위한 정보를 담고 있다.
      • 이름, 그룹, JobDataMap 속성 등
      • Trigger가 Job을 수행할 때 이 정보를 기반으로 스케줄링을 한다.
    4. Trigger
      • Job을 실행시킬 스케줄링 조건등을 담고 있다.
      • Scheduler는 이 정보를 기반으로 Job를 수행시킨다.
      • Trigger와 Job의 관계
        • 1 : 1
          • 반드시 하나의 Trigger는 하나의 Job을 지정
        • N : 1
          • 하나의 Job을 여러 시간때로 실행 가능
          • ex) 매주 월요일, 매시간마다
      • Trigger 2가지 형태
        • SimpleTrigger
          • 특정 시간에 Job을 수행할 때 사용
          • 반복 횟수와 실행 간격등을 지정할 수 있다.
        • CronTrigger
          • cron 표현식으로 Trigger를 정의하는 방식
          • SimpleTrigger와 같이 단순 반복뿐만 아니라 더 복잡한 스케줄링도 지정할 수 있다.
    5. Misfire Instructions
      • Misfire는 Job이 실행되어야 하는 시간, fire time을 지키지 못한 실행 불발을 의미
      • Scheduler는 Misfire된 Trigger에 대해 어떻게 처리할 지에 대한 다양한 정책을 지원한다.
    6. Listener
      • Scheduler의 이벤트를 받을 수 있도록 Quartz에서 제공하는 인터페이스
      • 2가지를 제공
        • JobListener
          • Job 실행 전부로 이벤트를 받는다.
        • JobStore
          • Job과 Trigger 정보를 2가지 방식으로 저장할 수 있다.
          • RAMJobStore
            • 메모리에 스케줄 정보를 저장
            • 기본값
            • 성능이 좋지만, 문제가 발생시 데이터를 유지 못한다.
          • JDBCJobStore
            • DB에 정보를 저장
            • 시스템에 문제가 생겨도 스케줄 정보는 유지되어 재시작시 다시 실행할 수 있다.

    구조, 흐름

     

    🧑‍💻 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

    댓글

Designed by Tistory.