[Spring boot Quartz Scheduler] Cron Expression 정리 - Scheduler 개발을 위한 Cron 표현식

nooyji·2022년 9월 27일
1
post-custom-banner

웹 서버 개발이던 Rest 서버 개발이던 거의 모든 실제 현업 개발 환경에서 꼭 등장하는 것이 "배치" 와 "스케줄러" 이다.
사용자의 요청에 의해서 기능이 수행되는 것이 실제 업무 로직의 많은 부분을 차지하겠지만, 분명 시스템의 특정 부분은 "자동으로" 기능이 수행되어야 하는 것이 존재한다.
순수하게 DB to DB 로 작업하는 기능의 경우에는 오라클 등의 DBMS 자체에서 제공하는 스케줄러 기능이 있지만, 많은 경우 서버단에서 스케줄러를 개발해야 할 것이다. 이 또한 매우 다양한 방법이 있고, 몇몇 자주 사용되는 라이브러리가 존재한다. 그 중 내가 주로 자바 스케줄러 개발에 사용하는 오픈소스 라이브러리인 Quartz Scheduler 에 대해 작성해보려고 한다.

개발 환경은 Maven 기반의 Spring boot 환경이지만, 기본 설정을 제외하고는 Gradle 이나 기본 Spring3 등에서도 사용하는 방법은 비슷하다.

  1. pom.xml 에 Dependency 설정

당연한 말이겠지만 maven 환경에서 외부 라이브러리를 사용하기 위해서는 pom.xml 에 디펜던시를 추가해 주어야 한다. quartz 2.2.1 을 아래처럼 추가해 주자.

<!-- Quartz scheduler -->
<dependency>
	<groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz</artifactId>
    <version>2.2.1</version>
</dependency>

<dependency>
	<groupId>org.quartz-scheduler</groupId>
    <artifactId>quartz-jobs</artifactId>
    <version>2.2.1</version>
</dependency>

이제 Quartz 라이브러리를 사용할 수 있게 되었을 것이다.

  1. Job 코드 작성

Quartz Scheduler 는 기능을 수행하는 단위인 Job 과 스케줄에 대한 정보를 가진 Trigger 를 스케줄러에 걸어 실행하는 구조이다. 먼저 스케줄에 따라 동작할 Job 클래스를 작성한다.

public class LcmgrTestJob implements Job {

	@Override
    public void execute(JobExecutionContext context) throws JobExecutionException{
    	/*
        * Job Interface 를 implements 하여
        * execute 메소드에 로직을 넣는다.
        * */
        System.out.println("--job수행--");
        System.out.println(DateUtil.getCurrentTimestamp());
    }
}
    

quartz.Job 인터페이스를 implements 하여 구현하게 된다.
해당 인터페이스의 execute 메소드를 상속받아 수행될 로직을 작성하면 된다. 스케줄에 따라 job 이 동작하는지 테스트하기 위해 sysout 으로 메시지를 출력하고 현재 일시를 출력하는 간단한 코드를 작성했다.

  1. 스케줄러 코드 작성

이제 스케줄에 따라 위에서 작성한 Job 을 수행하는 코드를 Quartz 라이브러리를 사용해 개발할 차례이다. 우선 아래 코드를 보자.

@Component
public class LcmgrTestScheduler {
	private SchedulerFactory schedulerFactory;
    private Scheduler scheduler;
    
    @PostConstruct
    public void start() throws SchedulerException{
    
    	schedulerFactory = new StdSchedulerFactory();
    	scheduler = schedulerFactory.getScheduler();
    	scheduler.start();
    
    	//job 지정
    	JobDetail job = JobBuilder.newJob(LcmgrTestJob.class).withIdentity("testJob").build();
    
    	//trigger 생성
    	Trigger trigger = TriggerBuilder.newTrigger().
    			withSchedule(CronScheduleBuilder.cronSchedule("15 * * * * ?")).build();
//    	startAt 과 endAt 을 사용해 job 스케줄의 시작, 종료 시간도 지정할 수 있다.
//    	Trigger trigger = TriggerBuilder.newTrigger().startAt(startDateTime).endAt(EndDateTime)
//            .withSchedule(CronScheduleBuilder.cronSchedule("*/1 * * * *")).build();
		scheduler.scheduleJob(job, trigger);
	}
]

Spring bean 으로 등록하기 위해 클래스에 @Component 어노테이션을 달아 줬으며, 실제 스케줄러를 구현한 start() 메소드에 @postConstruct 어노테이션을 달아줬다.
@postConstruct 어노테이션을 간략하게 설명하자면, 해당 클래스가 인스턴스화 되자마자 자동으로 동작하게 하려는 메소드에 선언해 주는 어노테이션이다. 즉, 위 클래스가 인스턴스화 되자 마자 start() 메소드가 동작할 것이다.
스케줄러를 통한 배치 잡 자체가 사용자의 동작 없이 자동으로 수행하게 하기 위한 로직이기 때문에 어딘가에서 메소드를 호출해 실행하기 보다는 이런 식으로 자동으로 로직이 수행되도록 구현하는 것이 좋다.

quartz.SchedulerFactory 를 선언한 후, quartz.Scheduler 를 .getScheduler() 메소드를 통해 지정해준다.
그 뒤, 해당 스케줄러를 .start() 해주는 것으로 스케줄러를 시작하겠다는 명령을 내리게 된다.

14번 라인에서 이제 아까 작성한 job 을 지정해 줄 차례이다.
identity 는 해당 job 을 구분하는 고유명을 지정해주면 된다. 간혹 같은 job 로직이라도 서로 다른 스케줄로 동작하게 할 경우가 있기 때문에 각각의 job 은 고유한 identity 를 가져야 한다.

17번 라인에서 trigger 를 코딩해준다. trigger 는 TriggerBuilder 클래스를 사용해 구현하게 되는데, 스케줄러를 수행할 스케줄 정보를 담고 있다.
이 때, Cron 문법을 사용해 스케줄을 지정하는 방법이 주로 사용된다. 주석 친 부분처럼 startAt 이나 endAt 을 사용해 스케줄을 시작할 시작, 종료 일시를 지정해 줄 수도 있다.
위 코드에서 넣어준 cron 인 "15 * * * * ?" 는 매 분 15초에 동작하라는 의미이다.

마지막으로 스케줄러에 job 과 trigger 를 연결해주면 끝난다. 물론 job 과 trigger 를 여러개 만들어 각각 scheduler 에 지정해주면 여러 개의 job 스케줄이 동시에 작동하게 된다.

  1. 스케줄러 실행

스케줄러를 호출해 실행하는 방법 역시 개발자가 구현하기 나름이겠지만, 배치 스케줄에 대한 시작, 종료 정보와 동작 주기 등을 이미 스케줄러 클래스에서 다 지정해 주기 때문에 보통 서버 시작과 동시에 호출해서 사용하게 된다.
나는 Spring boot 를 사용하였기 때문에 아래 코드처럼 main 메소드가 있는 application 클래스에서 개발한 스케줄러를 Autowired 하여 서버 시작과 동시에 자동으로 동작하게 구현했다.

@SpringBootApplication
public class LcmgrApplication {

	@Autowired
    private LcmgrTestScheduler scheduler;
    
    public static void main(String[] args) {
    	SpringApplication.run(LcmgrApplication.class, args);
    }
}

서버를 구동시켜 보면 설정한 대로 매분 15초마다 해당 job 이 동작하여 로그를 찍어 주는 것을 확인할 수 있다.

Cron, 크론은 본래 유닉스 계열의 운영체제에서 시간 기반으로 잡 스케줄링을 하는 후면 프로세스의 명칭이다.
이 때 잡 스케줄링의 파라미터로 사용되는 표현식을 Cron 표현식이라고 한다. 유닉스나 리눅스의 스케줄링 작업에도 사용되지만, 자바 스프링 기반의 Quartz 등으로 서버 스케줄러를 개발할 때에도 종종 사용된다.

크론 표현식은 Job 스케줄을 관리하거나 스케줄러 개발을 할 때 필요한 순간마다 검색해서 사용하곤 했다. 사실 크론 표현식을 생성해주는 cronMaker 와 같은 사이트에서 편하게 만들 수 있지만, 표현식을 읽을 수 있는 것도 매우 중요하기 때문에 한번 정리해두려고 한다.

Cron Expression

Cron 표현식은 7개로 구분된 단위 표현식으로 이루어진 String 문자열이다. 각각의 단위는 초/분/시 등으로 세분화되어 사용되며 각각의 단위는 공백 (" ") 으로 구분된다. 아래 코드를 보면 한 눈에 알 수 있다.

CronExpression cexp = new CronExpression("0 30 13 ? 1-12 ?");
  1. 가장 앞에 오는 단위는 초 (Seconds) 이다.
  2. 두번째는 분 (Minutes) 을 나타낸다.
  3. 세번째는 시 (Hours) 를 나타낸다.
  4. 네번째는 일 (Day-of-Month, DOM) 을 나타낸다.
  5. 다섯번째로 월 (Month) 에 대한 정보를 기술한다.
  6. 여섯번째는 요일 (Day of Week) 을 나타낸다. 요일은 0~6 의 숫자로 쓸 수도 있지만 "MON", "SUN" 과 같이 요일의 약자로 사용할 수도 있다.
  7. 마지막으로 일곱번째에는 연도 (Year) 가 온다. 연도는 optional 이다.
  • 와일드카드 (*) 문자는 "매 번" 을 의미한다
  • 물음표 (?) 는 '설정값 없음' 을 나타낸다. 이는 일 (DOM) 과 요일 (DOW) 에만 사용할 수 있다.
  • 슬래쉬 (/) 는 값 증가 표현에 사용된다. 분 (Minutes) 항목에 "10/15" 라고 쓴다면, "10분 부터 시작해서 매 15분 마다" 를 의미한다.
  • 샾 (#) 은 k#N 으로 사용되며, 이 달의 N번째 K요일을 의미한다. 요일 (DOW) 항목에 "5#2" 라고 적는다면, "이 달의 두번째 목요일" 을 뜻한다.
  • 문자 "L" 은 마지막 (Last) 를 의미한다. L 은 일 (DOM) 과 요일 (DOW) 에만 사용할 수 있다. 예를 들어 일 (DOM) 항목에 L 이 사용된다면 단순하게 해당 월의 마지막 날을 의미한다. 조금 다른 방법으로도 사용되는데, 특정 값 뒤에 사용된다면 "이 달의 마지막 날"을 의미하게 된다. 예를 들어 요일에 "6L" 을 준다면, "이 달의 마지막 금요일" 을 의미하게 된다.
  • 문자 "W" 는 해당 날로부터 가장 가까운 평일 (Weekday) 을 의미한다. 예를 들어 일 (DOM) 항목에 "10W" 라고 준다면, "이 달의 10째 날로부터 가장 가까운 평일" 을 의미한다.
  • 각각의 단위는 범위나 목록으로 나타낼 수도 있다. 일 (DOM) 에 "1-15" 라고 적는다면 1일부터 15일까지를 뜻한다.
  • 각가의 항목은 항목에 유효한 값만이 들어올 수 있다. 예를 들어 일은 1 ~ 31 사이의 숫자만 허용되고, 시간은 0 ~ 23 사이의 시간만 허용한다.

Cron Expression Examples

위의 정리한 내용으로 만든 몇 가지 예제이다. 예제는 quart-scheduler.org 의 tutorial-lesson 에서 따왔다.

  1. "0 0/5 * * * ?" : 매 시 5분마다 수행
  2. "10 0/5 * * * ?" : 10초 뒤 5분마다 수행
  3. "0 30 10-13 ? * WED,FRI" : 매 주 수요일과 금요일 10시 ~ 13시 30분에 수행
  4. "0 0/30 8-9 5,20 * ?" : 매 월 5일과 20일에 8시 ~ 9시대에 30분 간격으로 수행 (ex. 8:00, 8:30, 9:00, 9:30 에 수행)

사실 크론 표현식 자체가 어려운 것은 아니지만, 다소 혼동을 주는 표현들이 많다. 때문에 실제 업무에서 사용한다면 직접 만들기 보다는 cronMaker 와 같은 사이트를 사용해서 표현식을 만든 뒤에 검증하는 방법을 사용하는 것이 좋다.

원문 : https://www.leafcats.com/93
https://www.leafcats.com/94

post-custom-banner

0개의 댓글