웹 서버 개발이던 Rest 서버 개발이던 거의 모든 실제 현업 개발 환경에서 꼭 등장하는 것이 "배치" 와 "스케줄러" 이다.
사용자의 요청에 의해서 기능이 수행되는 것이 실제 업무 로직의 많은 부분을 차지하겠지만, 분명 시스템의 특정 부분은 "자동으로" 기능이 수행되어야 하는 것이 존재한다.
순수하게 DB to DB 로 작업하는 기능의 경우에는 오라클 등의 DBMS 자체에서 제공하는 스케줄러 기능이 있지만, 많은 경우 서버단에서 스케줄러를 개발해야 할 것이다. 이 또한 매우 다양한 방법이 있고, 몇몇 자주 사용되는 라이브러리가 존재한다. 그 중 내가 주로 자바 스케줄러 개발에 사용하는 오픈소스 라이브러리인 Quartz Scheduler 에 대해 작성해보려고 한다.
개발 환경은 Maven 기반의 Spring boot 환경이지만, 기본 설정을 제외하고는 Gradle 이나 기본 Spring3 등에서도 사용하는 방법은 비슷하다.
당연한 말이겠지만 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 라이브러리를 사용할 수 있게 되었을 것이다.
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 으로 메시지를 출력하고 현재 일시를 출력하는 간단한 코드를 작성했다.
이제 스케줄에 따라 위에서 작성한 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 스케줄이 동시에 작동하게 된다.
스케줄러를 호출해 실행하는 방법 역시 개발자가 구현하기 나름이겠지만, 배치 스케줄에 대한 시작, 종료 정보와 동작 주기 등을 이미 스케줄러 클래스에서 다 지정해 주기 때문에 보통 서버 시작과 동시에 호출해서 사용하게 된다.
나는 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 ?");
Cron Expression Examples
위의 정리한 내용으로 만든 몇 가지 예제이다. 예제는 quart-scheduler.org 의 tutorial-lesson 에서 따왔다.
사실 크론 표현식 자체가 어려운 것은 아니지만, 다소 혼동을 주는 표현들이 많다. 때문에 실제 업무에서 사용한다면 직접 만들기 보다는 cronMaker 와 같은 사이트를 사용해서 표현식을 만든 뒤에 검증하는 방법을 사용하는 것이 좋다.
원문 : https://www.leafcats.com/93
https://www.leafcats.com/94