본문 바로가기

Web

[ spring ] ShedLock 스케줄러 중복 실행 이슈 해결

반응형

 

✅  개발환경

Java 8
Spring Boot 2.7  
ShedLock 4.44.0 
MySQL (JDBC Template 기반) 

초반에 java8인데 ShedLock 5.10.1 버전 사용하려해서 계속 에러 이슈 있었다.. 후

 

 

| Gradle 의존성 (build.gradle)

dependencies {
    implementation 'net.javacrumbs.shedlock:shedlock-spring:4.44.0'
    implementation 'net.javacrumbs.shedlock:shedlock-provider-jdbc-template:4.44.0'
    
    implementation 'org.springframework.boot:spring-boot-starter-jdbc'
    implementation 'mysql:mysql-connector-java:8.0.33'
}

 



| ShedLock 테이블 생성 (필수)

CREATE TABLE shedlock (
    name VARCHAR(64) NOT NULL PRIMARY KEY,
    lock_until TIMESTAMP(3) NOT NULL,
    locked_at TIMESTAMP(3) NOT NULL,
    locked_by VARCHAR(255) NOT NULL
);


💡 이 테이블이 없으면 ShedLock이 동작하지 않습니다.

 

 

| ShedLock 설정 클래스

jdbc 사용할 경우 ShedLock 설정 클래스를 수동으로 무조건 생성해 줘야 한다.

import net.javacrumbs.shedlock.core.LockProvider;
import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;

import javax.sql.DataSource;

@Configuration
public class ShedLockConfig {

@Bean
public LockProvider lockProvider(ObjectProvider<DataSource> provider) {
// DataSource를 바로 주입 받으려다 실패할 경우 발생하여 아래와 같이 안전하게 처리
        DataSource dataSource = provider.getIfAvailable();
        if (dataSource == null) {
            throw new IllegalStateException("DataSource not found!");
        }

        return new JdbcTemplateLockProvider(
                JdbcTemplateLockProvider.Configuration.builder()
                        .withJdbcTemplate(new JdbcTemplate(dataSource))
                        //.usingDbTime() 생략 → 시스템 시간 사용
                        .build()
        );
    }
}



log4jdbc 사용하면 .usingDbTime() 호출 시 에러가 발생하여 생략하고 시스템 시간을 사용했다.
db 시간을 사용하고 싶으면 com.mysql.cj.jdbc.Driver 사용하면 된다.
또한, dataSource 값을 가져오려면 jdbc 프로퍼티 설정이 되어 있어야한다.

 

application.properties 예시

spring.datasource.url=jdbc:log4jdbc:mysql://localhost:3306/your_db?serverTimezone=Asia/Seoul
spring.datasource.username=your_user
spring.datasource.password=your_password
spring.datasource.driver-class-name=net.sf.log4jdbc.DriverSpy



| 스케줄러 클래스

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

@Component
public class MyJob {
/*
 * lockAtLeastFor 작업이 빨리 끝나도 최소 이 시간만큼 락 유지
 * lockAtMostFor 락을 최대 이 시간까지만 유지
 */
@Scheduled(cron = "0 0/1 * * * *") // 매 1분마다 실행
@SchedulerLock(name = "MyJob_scheduledTask", lockAtLeastFor = "PT5S", lockAtMostFor = "PT10S")
public void scheduledTask() {
	System.out.println("스케줄 실행됨: " + System.currentTimeMillis());
	runTask() // 실행 작업
	System.out.println("스케줄 종료됨: " + System.currentTimeMillis());
    }
}



| @EnableScheduling 설정 (보통 메인 클래스에 붙임)

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.scheduling.annotation.EnableScheduling;

@SpringBootApplication
@EnableScheduling
public class YourApp {
    public static void main(String[] args) {
        SpringApplication.run(YourApp.class, args);
    }
}

 

여기까지 하면 ShedLock 스케줄러를 사용할 수 있다. 

테스트를 해보다가 스케줄러가 겹칠 경우, 메세지를 띄워서 확인하고 싶었다.

그래서 아래와 같이 적용했다.

반응형

 


| LockConfiguration (응용)

스케줄러가 이미 동작 중일 경우에 메세지를 띄우고 싶다면 
아래와 같이 LockConfiguration 을 사용하면 된다.

@Scheduled(cron = "*/2 * * * * *")
public void myScheduledTask() {
    System.out.println("스케쥴러");

    LockConfiguration lockConfig = new LockConfiguration(
        Instant.now(),
        "MyScheduledJob",
        Duration.ofSeconds(30), // lockAtMostFor
        Duration.ofSeconds(10)   // lockAtLeastFor
    );

    SimpleLock lock = null;
    try {
        lock = lockProvider.lock(lockConfig).orElse(null);
        if (lock == null) {
        System.out.println("[INFO] 이미 실행 중입니다. 다음 주기를 기다립니다.");
        return;
    }

    // 본 작업 실행
    System.out.println("[INFO] 작업 시작: " + Instant.now());
    runMyTask();
    System.out.println("[INFO] 작업 종료: " + Instant.now());

    } catch (Exception  e) {
    Thread.currentThread().interrupt();
    System.out.println("[ERROR] 작업 중단됨: " + e.getMessage());
    } finally {
        if (lock != null) {
            lock.unlock();
        }
    }
}

 

 

스케쥴러를 2초마다 동작하도록 하고, 스케줄러가 최소 10초 동안은 lock 을 유지하도록 하고 실행을 해보았다.

그럼 다음과 같이 log 가 찍히는 것을 확인할 수 있다.

runTask() 에는 DB insert 하는 로직을 넣어두었는데, 10초마다 데이터가 들어가는 것을 확인했다.

 

콘솔로그
DB 데이터

 

 

반응형