JSON-OBJECT Software Engineering Blog

Professional Senior Backend Engineer. Specializing in high volume traffic and distributed processing with Kotlin and Spring Boot as core technologies.

View on GitHub
26 December 2021

Spring Boot, ShedLock으로 멀티 노드 환경에서 스케쥴 중복 실행 방지하기

by Taehyeong Lee

개요

DynamoDB 테이블 생성

DynamoDB 콘솔 접속
→ [테이블 생성]

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

build.gradle.kts

dependencies {
    implementation("net.javacrumbs.shedlock:shedlock-spring:5.10.2")
    implementation("net.javacrumbs.shedlock:shedlock-provider-dynamodb2:5.10.2")
    implementation("software.amazon.awssdk:dynamodb-enhanced:2.22.12")
}

환경 변수

# JDK 21 에서 Virtual Thread를 활성화
SPRING_THREADS_VIRTUAL_ENABLED=true
# ShedLock에서 사용할 테이블 이름을 지정
SHEDLOCK_TABLE=shedlock-dev

@EnableScheduling 빈 작성

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.ConcurrentTaskScheduler
import org.springframework.scheduling.concurrent.SimpleAsyncTaskScheduler
import java.util.concurrent.Executors

@Configuration
@EnableScheduling
class SchedulingConfig {

    @Bean
    fun taskScheduler(): TaskScheduler {

        // [옵션 1] JDK 21 에서 Virtual Thread를 활성화
        return SimpleAsyncTaskScheduler().apply {
            this.setVirtualThreads(true)
            this.setTaskTerminationTimeout(30 * 1000)
        }
        
        // [옵션 2] JDK 19/20 에서 Virtual Thread를 활성화
        return ConcurrentTaskScheduler(
            Executors.newScheduledThreadPool(0, Thread.ofVirtual().factory())
        )
        
        // [옵션 3] JDK 17 이하에서 Platform Thread의 개수를 10개로 설정
        return ThreadPoolTaskScheduler().apply {
            this.poolSize = 10
        }
    }
}

@EnableSchedulerLock 빈 작성

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 ScheduleService {

    // 매일 KST 04:30:00에 중복 실행 없이 단 1개의 노드에서만 스케쥴 작업을 실행
    @Scheduled(cron = "0 30 4 * * *", zone = "Asia/Seoul")
    @SchedulerLock(name = "fooSchedule", lockAtLeastFor = "30s", lockAtMostFor = "1m")
    fun fooSchedule() {
        // 스케쥴로 실행될 내용 작성
    }
}

런타임에서 다이나믹하게 스케쥴러 실행 예약 적용

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

@Service
class FooService(
    private val taskScheduler: TaskScheduler
) {
    ...
    taskScheduler.schedule(
        { // 스케쥴러로 예약 실행할 로직 작성 },
        // 현재 시간에서 10초 뒤에 예약 실행 설정
        Instant.now().plusSeconds(10)
    )
}

참고 글

tags: Spring Boot