Java용 작업 스케줄러 - Quartz
Posted by Albert 1134Day 17Hour 35Min 52Sec ago [2022-03-12]
Quartz는 표준 자바 프로그램으로 만들어진 작업을 지정된 일정에 따라 실행시키는데 사용하는 Java 패키지입니다. 특정한 시간에 특정한 작업을 한다든지 또는 주기적으로 실행해야 할 Java 애플리케이션을 사전에 정해 놓으면 일정에 따라 실행시켜 줍니다.
설치방법
다운로드 사이트에서 다운로드를 해서 lib폴더에 있는 패키지 파일을 프로젝트의 library에 추가합니다.
Quartz 사용하기
스케줄러를 사용하려면 먼저 SchedulerFactory를 사용해서 스케줄러 인스턴스를 만들어야 합니다. 스케줄러가 시작되기 전까지 또는 시작되었다 하더라도 정지모드일 때는 트리거가 작동되지 않습니다.
스케줄러 인스턴스를 만들고 작업을 추가하는 등의 간단한 예는 다음과 같습니다.
Quartz API
Quartz의 중요한 인터페이스는 아래와 같습니다.
Scheduler - 스케줄러를 다루는데 사용되는 메인 API입니다.
Job - 스케줄러에 의해 실행될 프로그램을 만드는데 사용하는 인터페이스입니다.
JobDetail - Job의 인스턴스를 만드는데 사용됩니다.
Trigger - Job이 실행되는 일정을 만드는데 사용됩니다.
JobBuilder - JobDetail을 만드는데 사용되는 빌더입니다.
TriggerBuilder - Trigger를 만드는데 사용되는 빌더입니다.
Scheduler는 SchedulerFactory에 의해서 생성된 후 shutdown()메소드에 의해서 없어질 때까지 실행됩니다. 일단 Scheduler가 생성되면 Job과 Trigger를 추가하거나 삭제 또는 그 리스트를 가져 오거나 트리거를 일시 멈추는 것과 같은 일정과 관련된 조작을 할 수 있습니다. 하지만 Scheduler는 start()메소드에 의해서 시작되기 전까지 작업을 실행시키는 트리거는 전혀 작동되지 않습니다.
Quartz는 DSL(Domain Specific Language 또는 “fluent interface”)이라고 하는 빌더(builder) 클래스를 제공합니다. 위의 예에서 job과 trigger는 이 빌더 클래스를 이용해서 만든 것입니다. 빌더 클래스를 사용하려면 아래와 같이 관련된 패키지를 import하여야 합니다. 다양한 ScheduleBuilder 클래스를 사용해서 다양한 스케줄을 정의할 수 있습니다. 예를 들어 DateBuilder 클래스의 evenHourDateAfterNow()를 사용하면 현재 시간 이후 다음 정각(분초는 00:00)이 되는 java.util.Date 인스턴스를 만들 수 있습니다.
Job과 Trigger
Job은 Job인터페이스를 사용해서 만든 클래스를 말합니다. Job인터페이스는 아래와 같이 execute메소드 한개로 되어 있습니다.
Job에 지정된 트리거가 작동하면 스케줄러의 워커 쓰레드에 의해 execute()메소드가 호출됩니다. execute()메소드에 매개변수로 전달되는 JobExecutionContext객체가 런타임 환경에 대한 정보를 제공합니다. 그 정보에는 Job을 실행시킨 Scheduler의 핸들, 작동된 Trigger의 핸들, Job의 JobDetail 객체, 기타 몇가지 정보가 포함됩니다.
JobDetail객체는 프로그래머가 작성한 애플이케이션에서 스케줄러에 Job이 추가될 때 만들어 집니다. JobDetail객체에는 Job과 JobDataMap에 대한 여러가지 속성이 있어서 job인스턴스의 상태를 저장할 때 사용될 수 있습니다.
Trigger객체는 job을 실행시키는데 이용됩니다. job을 스케줄하려면 trigger를 만들어서 일정을 지정하면 됩니다. Trigger객체는 JobDataMap도 가질 수 있는데 이것은 트리거를 작동시킨 원인을 Job에 전달하는데 사용됩니다.
Quartz는 몇가지 트리거를 제공하지만 가장 많이 사용하는 것은 SimpleTrigger와 CronTrigger입니다. SimpleTrigger는 특정한 시점에 한번만 실행되거나 특정한 시점 이후에 N번 반복 실행되는 경우에 적합합니다. CronTrigger는 캘린더와 유사한 방법으로 실행되는 경우에 적합합니다. 예를 들어 매주 금요일 정오나 매월 10일 10시 15분과 같은 일정을 추가할 수 있습니다.
일반적인 스케줄러는 작업(job)이나 트리거(trigger)에 대한 개념이 없는 경우가 많습니다. 또, 일부 스케줄러는 작업을 실행시간 또는 일정(schedule)으로 정의합니다. 또다른 일부 스케줄러는 Quartz의 작업과 트리거를 합친 것과 유사합니다. 일정과 작업을 서로 분리하면 이점이 많이 생깁니다. 트리거와 분리해서 작업을 만들고 만들어진 작업은 별도의 작업일정표에 저장할 수 있습니다. 또, 작업과 연결된 트리거가 만료된 경우 작업을 다시 만들지 않고 다시 트리거만 만들어서 새로운 일정을 지정할 수 있습니다. 즉, 트리거를 고치거나 교체할 필요가 있을 때 연결된 작업을 다시 만들지 않고도 고칠 수 있습니다.
Job과 Trigger의 키
Job과 Trigger는 Quartz 스케줄러에 등록이 되면서 개별 객체를 구분할 수 있는 키가 부여됩니다. Job과 Trigger의 키(JobKey와 TriggerKey)는 그룹으로 분류될 수 있어서 작업과 트리거를 “reporting job”이나 “maintenance job”과 같은 그룹으로 나누는데 사용할 수 있습니다. 작업과 트리거 키의 이름은 그룹 안에서 유일해야 합니다. 키 전체 이름은 키이름과 그룹이름이 합쳐진 것이 됩니다.
Trigger
작업과 마찬가지로 트리거도 사용하기 쉬운 것은 비슷하지만 트리거에는 다양한 옵션이 있어서 사용하기 전에 이해를 할 필요가 있습니다. 또한 서로 다른 상황에서 사용할 수 있는 트리거 종류도 있습니다. 일정마다 사용할 수 있는 서로 다른 트리거도 있습니다.
트리거의 공통적인 속성
트리거를 구분하는데 사용하는 TriggerKey속성 이외에도 모든 트리거에 공통인 속성들이 있습니다. TriggerBuilder를 사용해서 트리거를 정의할 때 이러한 공통적인 속성이 설정됩니다. 모든 트리거 타입에 공통적인 속성은 다음과 같습니다.
※ jobKey : 트리거가 작동될 때 실행되어야 하는 작업을 가르킵니다.
※ startTime : 트리거의 일정이 처음으로 적용되는 시간입니다. 시간은 java.util.Date 객체로 정의됩니다. 일부 트리거에서는 이 시간이 작업이 시작되는 시간이지만, 트리거에 따라서는 스케줄이 시작되는 시간을 의미하는 경우도 있습니다. 예를 들면 "매월 5일"에 실행하는 것으로 정하고 startTime을 4월 1일로 정할 수 있습니다.
※ endTime : 트리거의 일정을 더이상 적용하지 않는 시간입니다. 예를 들어 "매월 5일"에 작동되는 트리거가 있을 때 7월 1일을 endTime으로 정하면 마지막으로 트리거가 작동되는 날짜는 6월 5일이 됩니다.
Priority 속성
※ priority : 만약 Trigger가 많이 있거나 트리거를 체크하는 워커쓰레드가 부족한 경우 Quartz가 같은 시간에 지정된 모든 Trigger를 즉시 작동시키는 것이 불가능할 수 있습니다. 이런 경우 어떤 Trigger를 먼저 작동시켜야 할지 priority속성을 사용해서 지정할 수 있습니다. priority속성은 이런 경우에 사용할 수 있습니다. 만약 N개의 Trigger가 동시에 작동되어야 하는데 M워커 쓰레드만 사용하능하다면 우선순위가 높은 M개의 트리거가 먼저 작동됩니다. 만약 priority속성으로 우선순위를 지정하지 않았다면 디폴드 우선순위인 5가 됩니다. priority값으로 양수나 음수 모두 가능합니다. priority속성은 같은 시간에 작동되는 경우에만 우선순위를 비교하기 위해서 사용됩니다. 시간이 다른 트리거와는 priority속성을 비교하지 않습니다. 트리거의 작업이 복구되는 경우에는 원래의 우선순위와 같은 우선순위로 복구됩니다.
불발 지시자 (Misfire Instructions)
불발은 스케줄러가 다운되어 있는 동안 트리거가 작동되지 못했거나 작업을 실행시킬 Quartz의 쓰레드풀이 부족해서 트리거가 작동되지 못했을 때 발생합니다. 트리거 종류마다 불발지시자를 다르게 지정할 수 있습니다. 디폴트 지시자는 트리거 종류나 설정에 따라 동적으로 작동하는 smart policy 지시자입니다. 스케줄러가 시작되면 스케줄러는 불발된 트리거를 찾아서 지정된 불발지시자에 의해 처리합니다. Quartz를 사용한다면 사용하는 트리거 종류에 정의된 불발 지시자가 어떻게 작동하는지 알 필요가 있습니다.
Calendar
Quartz의 Calendar객체(java.util.Calendar가 아님)는 트리거가 정의될 때 트리거와 연결되어서 스케줄러에 저장될 수 있습니다. Calendar는 트리거의 일정에서 특정 기간을 제외하는데 사용될 수 있습니다. 예를 들어 매일 9:30 am에 작동되는 트리거를 만들 때 휴일을 제외시키는 일정을 만들 수 있습니다. Calendar는 Calendar인터페이스를 구현하는 직렬화가 가능한 객체이면 어떤 것이든 가능합니다. Calendar인터페이스는 아래와 같습니다. 시간은 밀리초로 지정합니다. Quartz는 특정한 일자를 지정할 수 있는 org.quartz.impl.HolidayCalendar도 제공하고 있습니다.
Calendar는 인스턴스로 만들어여야 하고 addCalendar(..) 메소드를 통해서 스케줄러에 등록되어야 합니다. 만약 HolidayCalendar를 사용한다면 일정에서 제외하고 싶은 날짜를 갖는 인스턴스를 만든 후 addExcludedDate(Date date)매소드를 사용해서 등록해야 합니다. 동일한 Calendar 인스턴스를 트리거 여러개에 사용할 수 있습니다.
SimpleTrigger
SimpleTrigger는 한번만 실행되는 작업을 다루거나 특정 시간에 시작해서 일정한 반복주기로 실행되는 작업을 정의하는데 사용될 수 있습니다. 예를 들어, 2019년 12월 31일 11:23:54 AM에 작동되는 트리거나 그 이후 10초마다 5회 반복되는 트리거를 지정할 수 있습니다. 따라서 SimpleTrigger의 속성에는 start-time, end-time, 반복 count, 반복 interval 등이 있습니다.
반복 count는 0이나 양수 또는 상수인 SimpleTrigger.REPEAT_INDEFINITELY가 될 수 있습니다. 반복 interval은 밀리초 단위로서 반드시 0이나 양수가 되어야 합니다. 만약 반복 interval이 0이면 반복 count만큼 연속으로 트리거가 작동됩니다.
endTime속성이 지정되면 반복 count보다 우선됩니다. 만약 10초에 한번씩 작동되는 트리거를 특정 시점까지 지속하는 경우 반복 count를 계산하는 것 보다 endTime속성에 끝나는 시점을 지정하고 반복 count에는 REPEAT_INDEFINITELY을 지정하는 것이 더 편리합니다.
SimpleTrigger 인스턴스는 TriggerBuilder와 SimpleScheduleBuilder를 사용해서 만들어집니다. 이와 같은 빌더를 DSL스타일로 사용하려면 다음과 같은 패키지를 import static으로 지정해야 합니다.
사용예는 아래와 같습니다.
SimpleTrigger의 불발 지시자
SimpleTrigger 는 불발이 발생했을 때 Quartz가 어떻게 해야 하는지를 알려주는 여러가지 지시자를 갖고 있습니다. 다음과 같은 지시자를 포함한 지시자들은 SimpleTrigger 에 상수로 정의되어 있습니다.
Trigger.MISFIRE_INSTRUCTION_SMART_POLICY 지시자는 모든 트리거가 사용할 수 있고 디폴트 지시자이기도 합니다. 만약 smart policy 지시자가 사용된다면, SimpleTrigger는 그 구성과 주어진 SimpleTrigger의 상태에 따라 여러가지 MISFIRE 지시자를 동적으로 선택하게 됩니다. SimpleTrigger.updateAfterMisfire()메소드의 문서에 이 동적 동작에 대한 자세한 설명이 있습니다.
CronTrigger
만약 작업이 캘린더처럼 작동되어야 하는 경우 SimpleTrigger로 일일이 일정을 지정하는 것 보다 CronTrigger를 사용하는 것이 더 편리합니다. CronTrigger를 사용하면 "매주 금요일 정오"나 "매일 9:30am", "1월 매주 월, 수, 금요일에 9:00am에서 10:00am 사이에서 5분마다"와 같이 지정할 수 있습니다.
SimpleTrigger와 마찬가지로 CronTrigger도 언제부터 일정을 체크하는지 지정하는 startTime과 언제 체크를 중지하는지 지정하는 endTime 속성을 갖고 있습니다.
Cron표현식 (Expressions)
CronTrigger를 설정하기 위해서 Cron표현식이 사용됩니다. Cron표현식은 일정을 설명하는 7개 부분으로 된 표현식으로 구성되어 있습니다. 표현식은 공백으로 구분되며 다음과 같은 순서로 되어 있습니다.
1. 초
2. 분
3. 시
4. 일 (월 중)
5. 월
6. 요일 (주 중)
7. 년 (생략가능)
각각의 필드에는 지정할 수 있는 값들이 정해져 있습니다. 예를 들어 분에는 0에서 59 사이를 지정해야 하고, 시에는 0에서 23 사이의 값을 지정해야 합니다. 일에는 0에서 31 사이의 값을 지정할 수 있지만 실제 지정된 월에 존재하는 일을 지정해야 합니다. 월은 0에서 11사이의 값을 지정하거나 영문 약자인 JAN, FEB, MAR, APR, MAY, JUN, JUL, AUG, SEP, OCT, NOV, DEC로 지정할 수 있습니다. 요일은 일요일인 1에서 토요일인 7까지 지정하거나 SUN, MON, TUE, WED, THU, FRI, SAT로 지정할 수 있습니다.
예를 들어 “0 0 12 ? * WED”은 "매주 수요일 12:00:00 pm"을 의미합니다. 각각의 표현식은 범위나 리스트를 포함할 수 있습니다. 앞의 예에서 "WED"는 "MON-FRI"나 "MON,WED,FRU" 또는 "MON-WED,SAT"와 같이 바꿀 수 있습니다.
"*"로 와일드카드를 지정할 수 있습니다. 위의 예에서 월에 "*"를 지정했는데 이것은 "모든 월"을 의미합니다. 만약에 요일에 "*"를 지정하면 "모든 요일"이 됩니다.
"/"는 값의 증가를 지정하는데 사용됩니다. 예를 들어 분에 "0/15"를 지정하면 "매 시간 0분에서 시작해서 15분마다"라는 의미입니다. 만약 분에 "3/20"을 지정하면 "매시간 3분에서 시작해서 20분마다"를 의미하고 "3,23,43"으로 지정한 것과 같은 효과를 갖습니다. 만약 "/35"로 지정하면 0이 생략된 것으로 봅니다. 따라서 "/35"는 "35분마다"의 의미가 아니고 "매 시간 0분에서 시작해서 35분마다"을 의미하는 것으로 "0,35"와 같습니다.
"?"는 일과 요일에 지정할 수 있으며 "값을 지정하지 않음"을 의미합니다. 이 것은 둘 중 하나는 값을 지정하면서 다른 하나는 값을 지정하지 않을 때 사용할 수 있습니다.
"L"도 일과 요일에 지정할 수 있습니다. L은 마지막(Last)의 첫글자이만 일과 요일에 있어서 의미가 다릅니다. 일에 있어서는 월의 마지막 날짜로서 1월의 경우에는 31일이고 2월의 경우에는 윤년이 아니면 28일입니다. 마지막 날짜에 며칠 앞선 날짜를 지정할 수 있는데, 예를 들어 일에 L-3으로 지정하면 월의 마지막 날짜보다 3일 앞선 날짜를 의미합니다. 요일에 사용되는 경우 마지막요일을 의미하며 7 또는 토요일을 의미합니다. 만약 요일을 지정하고 뒤에 L을 붙이면 그 달의 마지막 요일을 의마합니다. 예를 들어 금요일인 FRI에 L을 붙여서 FRIL로 지정하거나 6L로 지정하면 해당월의 마지막 금요일이 됩니다. L을 사용할 때 리스트를 지정하거나 범위를 지정하면 예상하지 못한 결과를 가져 올 수 있습니다.
"W"는 지정한 일에 가장 가까운 주중요일(Weekday)을 지정하는데 사용됩니다. 만약 일에 15W로 지정하면 해당월에 "15일과 가장 가까운 주중요일"을 의미합니다. 만약 15일이 토요일이면 14일에 트리거가 작동되고 15일이 일요일이면 16일에 트리거가 작동됩니다.
"'"은 해당월에 몇 번째 요일을 지정할 때 사용됩니다. 예를 들어 요일에 6'3나 FRI'3로 지정하면 3번째 금요일을 의미합니다.
"-"는 범위를 나타냅니다. 예를 들어 일에 1-20을 지정하면 1일부터 20일까지가 됩니다.
","는 리스트를 나타냅니다. 예를 들어 일에 1,20을 지정하면 1일과 20일이 됩니다.
Cron표현식 예
“0 0/5 * * * ?” : 5분마다 작동
“10 0/5 * * * ?” : 매 시간 0분 10초에 시작해서 5분마다 작동 (10:00:10 am, 10:05:10 am)
“0 30 10-13 ? * WED,FRI” : 매주 수요일, 금요일, 10:30, 11:30, 12:30, 13:30 작동
“0 0/30 8-9 5,20 * ?” : 매월 5일과 20일, 8시와 9시, 0분과 30분에 작동 (8:00, 8:30, 9:00, 9:30)
만약 조건이 복잡한 경우에는 트리거를 두개로 만들어서 같은 작업에 등록시키면 됩니다. 예를 들면 "9~10시 사이 매 5분마다"와 "13~20시 사이 매 20분마다"는 두개로 만들어야 합니다.
CronTrigger 사용
CronTrigger 인스턴스는 TriggerBuilder와 CronScheduleBuilder로 만들어 집니다. 이 빌더들을 DSL스타일로 사용하려면 다음과 같이 import해야 합니다.
사용예는 아래와 같습니다.
CronTrigger의 불발 지시자
CronTrigger 는 불발이 발생했을 때 Quartz가 어떻게 해야 하는지를 알려 주는 여러가지 지시자를 갖고 있습니다. 다음과 같은 지시자를 포함한 지시자들은 CronTrigger 에 상수로 정의되어 있습니다.
Trigger.MISFIRE_INSTRUCTION_SMART_POLICY 지시자는 모든 트리거가 사용할 수 있고 디폴트 지시자이기도 합니다. 만약 smart policy 지시자가 사용된다면, CronTrigger는 그 구성과 주어진 CronTrigger의 상태에 따라 여러가지 MISFIRE 지시자를 동적으로 선택하게 됩니다. CronTrigger.updateAfterMisfire() 메소드의 문서에 이 동적 동작에 대한 자세한 설명이 있습니다.
CronTrigger를 만들 때 불발지시자를 지정할 수 있습니다.