티스토리 뷰

JAVA

쿼츠 스케줄러

cykei 2022. 3. 14. 21:01
반응형
1. Quartz란?
Quartz는 Terracotta 라는 회사에 의해 개발된 Job Scheduling 라이브러리입니다. 완전히 자바로 개발되어 어느 자바 프로그램에서도 쉽게 통합해서 개발할 수 있습니다. Quartz는 수십에서 수천 개의 작업도 실행 가능하며 간단한 interval 형식이나 Cron 표현식으로 복잡한 스케줄링도 지원합니다. 예를 들면 매주 금요일 새벽 1시 30분에 매주 실행하는 작업이나 매월 마지막 날에 실행하는 작업도 지정할 수 있습니다. 
 
2. 장단점
스프링을 개발하면서 Quartz를 Job Scheduler로 자주 사용하는 이유도 있지만, 단점도 존재합니다. 
 
장점
  • DB 기반으로 스케줄러 간의 Clustering 기능을 제공한다
  • In-memory Job Scheduler도 제공한다
  • 여러 기본 Plug-in을 제공한다
 
단점
  • Clustering 기능을 제공하지만, 단순한 random 방식이라서 완벽한 Cluster 간의 로드 분산은 안된다
  • 어드민 UI을 제공하지 않는다
  • 스케줄링 실행에 대한 History는 보관하지 않는다
  • Fixed Delay 타입을 보장하지 않으므로 추가 작업이 필요하다. (Fixed Delay: 이전 수행이 종료된 시점부터 delay 후에 재호출)

 

Fixed Delay가 안된다고?? 테스트 해보자. (구현관련 설명은 Quartz 2.2.2 QuickStartGuide 참고)

 

public class MyObject implements Job {

    @Override
    public void execute(JobExecutionContext jobExecutionContext) throws JobExecutionException {
        JobKey jobKey = jobExecutionContext.getJobDetail().getKey();
        System.out.println(jobKey);
        System.out.println("my shceduler is running!");
        System.out.println("the time now is " + new Date());
        try {
            for (int i = 0; i < 3; i++) {
                System.out.println("Sleep " + i + " " +Thread.currentThread().getName());
                Thread.sleep(1000);
            }
        }catch(Exception e) {
            System.out.println(e);
        }

    }
}
public class MyMainScheduler {
    public static void main(String[] args) throws SchedulerException {

        JobDetail jobDetail = JobBuilder.newJob(MyObject.class)
                .withIdentity("job1","group1")
                .build();

        Trigger trigger = TriggerBuilder.newTrigger()
                .withIdentity("trigger1", "group1")
                .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                        .withIntervalInSeconds(2)
                        .repeatForever())
                .build();

        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();
        scheduler.scheduleJob(jobDetail, trigger);
        scheduler.start();

    }
}
#quartz.properties

org.quartz.scheduler.instanceName = MyScheduler
org.quartz.threadPool.threadCount = 20
org.quartz.jobStore.class = org.quartz.simpl.RAMJobStore
# log4j.properties

log4j.rootLogger=DEBUG, stdout

# quartz logging
log4j.logger.org.quartz=DEBUG

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyyy-MM-dd HH:mm:ss} %-3p [%t] %c{3} %M:%L - %m%n

 

로그 결과

그렇군. 처음 job1 이 종료된 후, 2초후에 잡이 다시 시작되는게 아니라 그냥 2초 단위로 시작되는군. 

잠깐! 지금은 쿼츠 스레드가 20개로 지정되있어서 이렇게 스레드 변경해서 2초마다 그냥 시작된다지만 최대 스레드가 한개라면???

오호... 그렇군 2초후에 잡이 시작되려고 했는데 이미 돌아가고 있는 잡이 있어서 시작되려는 잡이 홀딩되고, 앞의 잡이 끝나자마자 실행되는군!

 

쿼츠는 수천개까지 잡을 스케줄링 할 수 있다고 한다. 

진짜로??? 가볍게 2000개만 만들어서 테스트 해보자.

 

 public static void main(String[] args) throws SchedulerException {

        Scheduler scheduler = StdSchedulerFactory.getDefaultScheduler();

        for (int i=0; i<2000; i++) {
            JobDetail jobDetail = JobBuilder.newJob(MyObject.class)
                    .withIdentity("job" + i, "group1")
                    .build();


            Trigger trigger = TriggerBuilder.newTrigger()
                    .withIdentity("trigger"+i, "group1")
                    .withSchedule(SimpleScheduleBuilder.simpleSchedule()
                            .withIntervalInSeconds(2)
                            .repeatForever())
                    .build();

            scheduler.scheduleJob(jobDetail, trigger);
        }
        scheduler.start();

    }

아니 이럴수가?! 1, 0, 10, 11 순서로 실행되다니?

몇번 실행해봤는데 딱히 스케줄러에 잡이 등록되는 규칙같은건 없는듯 하다. 이래서 그냥 라운드로빈 방식이고, 완벽하게 분산하는 건 어렵다고 적혀있던 거군?

 

분명 job0, job1, job2 순서대로 등록했을 텐데 job1, job0, job10 순서대로 시작된다라.

여기서 우리는 스레드 20개가 전부 꽉 차서 일을 하고 있을 경우, 동일 스레드 상에서 job 2가 우연치 않게 매우 느린 job1 뒤에 스케줄링 된 경우 job2 는 엄청나게 지연될 것이라는 것을 추측할 수 있다. 

 

이걸 해결하려면... 음... 쿼츠 스케줄링에 등록된 잡들중에 지연되는 잡이 있으면 그 이후의 잡은 해당 스레드에서 빼서 지연되고 있지 않은 스레드에 다시 넣어줄 필요가 있다. 즉 주기적으로 현재 동작중인 잡이 몇분 정도 지났는지 파악을 하면서 지연되고 있는지를 체크해야한다는 것. 

 

일단 현재 동작중인 잡 이후에 실행될 잡을 스케줄링에서 동적으로 뺄 수 있는지 알아봐야겠다. 

공식문서를 참고하자

http://www.quartz-scheduler.org/documentation/quartz-2.2.2/cookbook/UnscheduleJob.html

 

Cookbook

 

www.quartz-scheduler.org

반응형

'JAVA' 카테고리의 다른 글

[JAVA] 상속의 목적  (0) 2020.04.16
[JAVA] static 이란?  (0) 2020.04.09
[Java 함수] StringBuilder  (0) 2020.02.15
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2025/06   »
1 2 3 4 5 6 7
8 9 10 11 12 13 14
15 16 17 18 19 20 21
22 23 24 25 26 27 28
29 30
글 보관함