본문 바로가기

목록이 없습니다.

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

Framework/Spring

     

    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

     


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