Framework/Spring

[Spring Framework] 멀티 서버에서 Spring 스케줄러 중복실행 방지

dalpang.e 2019. 11. 25. 16:40

 

Spring의 @Scheduled 어노테이션은 Spring Framework에서 쉽게 cron을 구성할 수 있는 유용한 기능입니다. 그런데 WAS를 늘리게 되면 상당히 골치아픈 상황이 벌어집니다. cron작업이 WAS마다 실행되기 때문에 WAS의 개수만큼 중복된 cron이 실행되기 때문인데요 이러한 상황을 해결할 수 있는 방법을 정리해보려고 합니다.


Scheduler Lock

위와 같이 2개 이상의 서버로 구성된 환경에서 중복된 Schedule이 실행되지 않도록 Lock을 걸 수 있게 만들어놓은 라이브러리입니다. maven repository에 shedlock이라는 이름으로 올라와있습니다. 그 중에 shedlock-spring과 shedlock-provider-jdbc-template을 추가합니다.

 

 

shedlock-provider-jdbc-template을 추가하는 것을 보면 데이터베이스(DataBase)를 이용하는 것을 예상할 수 있습니다. 이 포스팅에서는 RDB를 통해 lock을 하는 방벙블 정리하지만 RDB 말고도 maven repository 검색결과 목록에 MongoDB, Redis provider도 보이는 걸 보면 다른 저장소를 통해서도 가능한 것으로 보입니다. (개인적으로는 Redis로 하는 방법을 추가로 정리 해보고 싶네요)

 

maven repository shedlock 검색결과 페이지

 

pom.xml에 라이브러리 추가

<!-- Scheduler lock -->
<dependency>
    <groupId>net.javacrumbs.shedlock</groupId>
    <artifactId>shedlock-spring</artifactId>
    <version>3.0.0</version>
</dependency>

<!-- JDBC Template -->
<dependency>
    <groupId>net.javacrumbs.shedlock</groupId>
    <artifactId>shedlock-provider-jdbc-template</artifactId>
    <version>3.0.0</version>
</dependency>

 

Bean 등록

public class ScheduleController {
    @Bean
    public LockProvider lockProvider(DataSource dataSource) {
        return new JdbcTemplateLockProvider(dataSource);
    }
}

위와같이 java로 만들어도 되고 아래와 같이 xml로 만들어도 됩니다.

 

<bean id="lockProvider" class="net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider">
	<constructor-arg ref="dataSource"/>
</bean>
만약 기존에 연동된 jdbc 또는 DB Connector가 없는 경우에는 DB 연동부터 설정해주셔야 합니다. 여기서는 이미 연동된 상태를 전제로 설명합니다.

여기서 ref="dataSource"는 기존에 연결되어있는 dataSource 명을 넣어주시면 됩니다.

 

 

DB 테이블 생성

Schedule Lock을 사용하기 위해 위에서 설정한 dataSource에 해당하는 DB에 테이블을 생성합니다. 테이블을 통해서 lock기능을 사용할 수 있습니다.

DROP TABLE IF EXISTS `shedlock` RESTRICT;

CREATE TABLE `shedlock` (
	`name`       VARCHAR(64)  NOT NULL COMMENT '스케줄잠금이름', -- 스케줄잠금이름
	`lock_until` TIMESTAMP(3) NULL     COMMENT '잠금기간', -- 잠금기간
	`locked_at`  TIMESTAMP(3) NULL     COMMENT '잠금일시', -- 잠금일시
	`locked_by`  VARCHAR(255) NULL     COMMENT '잠금신청자' -- 잠금신청자
)
COMMENT '스케줄잠금';

ALTER TABLE `shedlock` ADD CONSTRAINT `PK_shedlock` -- 스케줄잠금 기본키
PRIMARY KEY (
	`name` -- 스케줄잠금이름
);

여기서 name은 어노테이션에서 전달할 이름입니다. 고유값으로 들어가고 이름으로 스케줄을 구분합니다.

 

@SchedulerLock

어노테이션을 사용해서 lock을 걸어줍니다.

 

@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "PT30S") // Scheduler Lock 사용 가능 설정 (기본 30초동안 Lock)
public class BatchController {

	private static final String ONE_MIN = "PT1M"; // 1분동안 LOCK
	
	@Scheduled(cron = "0 10 * * * ?") // 초(0~59) 분(0~59) 시(0~23) 일(1~31) 월(1~12) 요일(1~7, 일요일 : 1) 연도(생략가능)
	// Schdule Lock (1분동안)
	@SchedulerLock(name = "runScenarioOneTime", lockAtMostForString = ONE_MIN, lockAtLeastForString = ONE_MIN)
	public void runScenarioOneTime() throws Exception {
		// do something...
	}
	
	@Scheduled(cron = "0 20 * * * ?")
	// Schdule Lock (1분동안)
	@SchedulerLock(name = "runScenarioCycle", lockAtMostForString = ONE_MIN, lockAtLeastForString = ONE_MIN)
	public void runScenarioCycle() throws Exception {
		// do something...
	}
	
}

이렇게 간단한 어노테이션 설정을 통해서 각 WAS가 Scheduling할 때 생성한 테이블을 참조하여 아래와 같은 작업이 이루어집니다.

 

  • shedlock 테이블에 같은 이름의 데이터가 있는지 여부 파악
    • 같은 이름이 있는 경우
      • 잠금 기간 및 잠금 일시를 참조하여 Scheduling 중단
    • 같은 이름이 없는 경우
      • shedlock 테이블에 이름, 잠금일시, 기간 등을 설정하여 insert

 


이 글은 블로그를 참조하여 작성하였습니다.