티스토리 뷰

개요

  • Spring Boot에서 특정 빈의 메써드에 지정된 @Scheduled는 특정 시간 또는 주기로 애플리케이션 로직이 실행될 수 있도록 해준다. @Scheduled에서 발생할 수 있는 주요 이슈로, 분산 시스템의 개념이 없기 때문에 애플리케이션을 n개의 멀티 노드에 배포하면 같은 스케쥴 작업이 n개 노드에서 동시에 실행된다는 단점이 있다. 어떤 상황에서는 특정 스케쥴 작업이 반드시 1개 노드에서만 실행되어야 하는 경우가 있는데, 이번 글에서 소개할 SchedLock 라이브러리를 이용하면 특정 작업 실행시 락을 생성하여 다른 인스턴스에서는 해당 작업의 실행을 무시하도록 설정할 수 있다.

DynamoDB 테이블 생성

  • ShedLock은 락 정보를 저장할 테이블로 여러 저장소를 지원한다. 이번 글에서는 DynamoDB를 저장소로 지정하여 예제를 작성해보겠다. (DynamoDB를 예제에서 선택한 이유는 AWS 클라우드 환경에서 셋업이 가장 쉽고 빠르기 때문이다.) AWS 콘솔에 접속하여 아래와 같이 테이블을 생성한다. (테이블 이름은 자유롭게 해도 상관없지만, 파티션 키는 반드시 _id이어야 한다.)
DynamoDB 콘솔 접속
→ [테이블 생성]

# 테이블 생성
→ 테이블 이름: shedlock (입력)
→ 파티션 키 이름: _id (입력)
→ 파티션 키 타입: [문자열] 선택
→ 설정: [설정 사용자 지정] 선택
→ 테이블 클래스 선택: [DynamoDB Standard] 선택
→ 용량 모드: [온디맨드] 선택
→ [테이블 생성] 클릭

build.gradle.kts

  • 프로젝트의 /build.gradle.kts에 아래 내용을 추가한다.
dependencies {
    implementation("net.javacrumbs.shedlock:shedlock-spring:4.33.0")
    // 아래 2개는 데이터 저장소로 DynamoDB를 사용할 경우 추가
    implementation("net.javacrumbs.shedlock:shedlock-provider-dynamodb2:4.33.0")
    implementation("software.amazon.awssdk:dynamodb-enhanced:2.17.162")
}

application.yml

  • 프로젝트의 /src/main/resources/application.yml에 아래 내용을 추가한다.
shedlock:
  table: shedlock

@EnableScheduling 클래스 작성

  • 스케쥴 작업 실행을 전담할 총 10개의 쓰레드 풀을 가진 taskScheduler 빈을 생성한다. (프로젝트 상황에 맞게 줄이거나 늘리면 된다.)
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import org.springframework.scheduling.TaskScheduler
import org.springframework.scheduling.annotation.EnableScheduling
import org.springframework.scheduling.concurrent.ThreadPoolTaskScheduler

@Configuration
@EnableScheduling
class SchedulerConfig {

    @Bean
    fun taskScheduler(): TaskScheduler {

        val taskScheduler = ThreadPoolTaskScheduler()
        taskScheduler.poolSize = 10

        return taskScheduler
    }
}

@EnableSchedulerLock 클래스 작성

  • ShedLock 활성화와 함께 데이터 저장소로 DynamoDB를 지정할 차례이다. 클래스 작성에 앞서 dynamoDbClient 빈의 생성은 본 블로그의 이 글을 따라 진행한다.
import net.javacrumbs.shedlock.core.LockProvider
import net.javacrumbs.shedlock.provider.dynamodb2.DynamoDBLockProvider
import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.beans.factory.annotation.Value
import org.springframework.context.annotation.Bean
import org.springframework.context.annotation.Configuration
import software.amazon.awssdk.services.dynamodb.DynamoDbClient

@Configuration
@EnableSchedulerLock
class SchedulerShedLockConfig(
    @Value("\${shedlock.table}") val shedLockTable: String
) {
    @Bean
    fun lockProvider(@Qualifier("dynamoDbClient") dynamoDbClient: DynamoDbClient): LockProvider {

        return DynamoDBLockProvider(dynamoDbClient, shedLockTable)
    }
}

@SchedulerLock 스케쥴러 적용

import net.javacrumbs.shedlock.spring.annotation.SchedulerLock
import org.springframework.scheduling.annotation.Scheduled
import org.springframework.stereotype.Component

@Component
class FooSchedule {

    // 매일 4시 30분 0초에 단 1개의 노드에서만 스케쥴 작업을 실행
    @Scheduled(cron = "0 30 4 * * *")
    @SchedulerLock(name = "aSchedule", lockAtLeastFor = "30s", lockAtMostFor = "1m")
    fun aSchedule() {
        // 스케쥴로 실행될 내용 작성
    }
}
  • 기본 설정에서 락이 설정된 작업은 실행이 종료되면 즉시 락이 해제된다. 하지만 예외 발생 등의 이유로 작업이 완료되지 못했을 때, lockAtMostFor를 설정하여 작업 실행 중 여부와 관계 없이 지정된 시간이 지난 후에는 강제로 락을 해제할 수 있다. 이 값은 반드시 실제 작업의 예상 소요 시간보다 길게 설정해야 한다.
  • 노드마다 시스템 시간의 미세한 차이가 있을 수 있기 때문에 작업이 아주 빨리 끝나는 경우, 락이 설정되었는데도 작업이 중복 실행되는 경우가 있다. 이 경우, lockAtLeastFor 를 설정하여 중복 실행을 방지할 수 있다. 작업이 지정된 시간보다 빨리 끝날 경우에도 지정된 시간까지 락을 보장한다. (작업이 지정된 시간을 초과했을 경우에는 이 값이 무시된다.)

참고 글

댓글
댓글쓰기 폼