Java ShedLock 이란?
ShedLock 이란?
Java 에서 ShedLock 은 분산 작업 환경에서 스케쥴러가 동시에 여러 인스턴스에서 실행되는 것을 방지하기 위해
사용하는 분산 락 라이브러리다.
쉽게 말해 Java 에서 스케쥴러가 돌고 있다고 치면 동시에 여러 스케쥴러가 같은 시간에 같은 동작을 하면
안되기 때문에 Lock 을 걸어 같은 작업을 동시에 못하도록 막아버린다.
예를 들어 Spring Boot 어플리케이션이 이중화되어 2대가 있다고 하면
@Scheduled 어노테이션이 존재하는 클래스가 2개가 존재하고 작업을 실행했을 때
별도의 조치가 없다면 2개의 스케쥴 작업이 진행된다. 이때 데이터 중복 처리나 성능 문제가
발생할 수 있기 때문에 ShedLock 을 사용하여 Lock 을 걸어 동시성 문제를 해결한다.
🔧 동작 원리
1. 스케줄러가 실행되기 전에 락 정보를 데이터베이스에 저장한다.
2. 다른 인스턴스는 해당 락을 확인하고, 이미 락이 존재하면 작업을 실행하지 않는다.
3. 작업이 완료되면 락을 해제하거나 TTL(Time-To-Live)을 기다린다.
설정 방법
1. 라이브러리 다운을 위한 의존성 추가
본인 버전은 Spring Boot 3 + java17 이다.
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-spring</artifactId>
<version>5.10.0</version> <!-- 최신 안정 버전 -->
</dependency>
<dependency>
<groupId>net.javacrumbs.shedlock</groupId>
<artifactId>shedlock-provider-jdbc-template</artifactId>
<version>5.10.0</version>
</dependency>
2. ShedLock 사용을 위한 Bean 등록
package com.obo.system.config;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.scheduling.annotation.EnableScheduling;
import net.javacrumbs.shedlock.core.LockProvider;
import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;
import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider.ColumnNames;
import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock;
@Configuration
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "PT1M")
public class ApplicationConfig {
@Bean
public LockProvider lockProvider(DataSource dataSource) {
return new JdbcTemplateLockProvider(
JdbcTemplateLockProvider.Configuration.builder()
.withTableName("m_shedlock")
.withColumnNames(new ColumnNames("name", "lock_until", "locked_at", "locked_by"))
.withJdbcTemplate(new JdbcTemplate(dataSource))
.withDbUpperCase(false)
.build()
);
}
}
@Configuration, @Bean
Spring Bean 등록을 하여 관리한다.
@EnableScheduling
해당 어노테이션을 통해 Spring 기본 @Scheduled 스케쥴러 기능을 활성화 시킨다.
@EnableSchedulerLock
ShedLock 이 @Scheduled 메서드에 걸린 락을 적용할 수 있도록 활성화해준다.
@SchedulerLock 이 제대로 동작하려면 필수다.
defaultLockAtMostFor 은 기본 락 지속시간을 글로벌하게 지정할 수 있다.
🔍 요약 비교
항목 | @EnableScheduling | @EnableSchedulerLock |
기능 | Spring 스케줄링 활성화 (@Scheduled) | ShedLock 락 적용 가능하게 설정 |
필요 조건 | @Scheduled 사용 시 필수 | @SchedulerLock 사용 시 필수 |
선언 횟수 | 한 번만 선언 | 한 번만 선언 |
위치 | 설정 클래스, 보통 @SpringBootApplication 근처 | ShedLock 설정 클래스 |
2. 스케쥴러 및 ShedLock 사용
package com.obo.controller.shedlock;
import java.time.LocalDateTime;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import net.javacrumbs.shedlock.spring.annotation.SchedulerLock;
@Component
public class ShedLock1Controller {
@Scheduled(fixedDelay = 10000)
@SchedulerLock(name = "shedlock1", lockAtLeastFor = "PT2S", lockAtMostFor = "PT2S")
public void shedlock1() {
System.out.println("ShedLock1 실행 - "+LocalDateTime.now());
}
@Scheduled(fixedDelay = 5000)
@SchedulerLock(name = "shedlock1", lockAtLeastFor = "PT2S", lockAtMostFor = "PT5S")
public void shedlock2() {
System.out.println("ShedLock2 실행 - "+LocalDateTime.now());
}
}
@Scheduled(fixedDelay = 10000)
스케쥴러를 돌리는데 10초마다 스케쥴러가 실행된다.
@SchedulerLock(name = "shedlock1", lockAtLeastFor = "PT2S", lockAtMostFor = "PT2S")
스케쥴러에 ShedLock 을 사용한다.
- name : DB 에 저장되는 락의 고유 이름(동일 이름이면 중복 실행 방지된다)
- lockAtLeastFor="PT2S" : 스케쥴러 실행 시 락이 실행되고 최소 2초를 유지한다.
작업이 빨리 끝나도 최소 2초는 유지한다. - lockAtMostFor="PT4S" : 작업이 오래걸리거나 실패했을 때 최대 4초 뒤에는 락이 자동해제
lockAtLeastFor 설정과 lockAtMostFor 설정은 병렬로 적용되거나 더해지는게 아니라 따로 동작한다.
2초 + 4초가 아니라 둘다 따로 2초, 4초다.
예를 들어 작업이 1초라고 하면 1초뒤에 바로 풀리는게 아니라 최소 시간인 lockAtLeastFor 설정에 따라
2초뒤에 락 해제다.
그리고 작업이 3초일 경우 최소시간이 아닌 lockAtMostFor 설정에 의해 4초뒤에 락이 해제된다.
즉, lockAtLeastFor 과 lockAtMostFor 은 동시에 동작하는게 아니라
락을 해제할 수 있는 시점의 하한 / 상한 역할을 한다.
🔔 정리하면:
상황 | 락 해제 조건 |
작업이 1초 걸림, lockAtLeastFor=2초 | 2초까지 락 유지 (작업보다 락이 더 오래 유지됨) |
작업이 3초 걸림, lockAtMostFor=2초 | 2초 후 자동 해제 (작업 중이지만 락은 풀림 → 중복 실행 가능성 있음) |
3. @SchedulerLock 어노테이션 사용하지 않고 LockProvider Bean 가져와서 사용하기
동적으로 스케줄링을 해야할 경우 @SchedulerLock 어노테이션을 사용할 수 없다.
고정적으로 시간이 정해진 스케줄링이 아닌 동적 스케줄은 Bean 으로 등록한 LockProvider 를 직접 가져와
사용하면 된다.
아래는 ShedLock 을 사용하기 위한 설정이다.
package com.obo.system.config;
import javax.sql.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.scheduling.annotation.EnableScheduling;
import net.javacrumbs.shedlock.core.LockProvider;
import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider;
import net.javacrumbs.shedlock.provider.jdbctemplate.JdbcTemplateLockProvider.ColumnNames;
import net.javacrumbs.shedlock.spring.annotation.EnableSchedulerLock;
@Configuration
@EnableScheduling
@EnableSchedulerLock(defaultLockAtMostFor = "PT1M")
public class ApplicationConfig {
@Bean
public LockProvider lockProvider(DataSource dataSource) {
return new JdbcTemplateLockProvider(
JdbcTemplateLockProvider.Configuration.builder()
.withTableName("m_temp_shedlock")
.withColumnNames(new ColumnNames("name", "lock_until", "locked_at", "locked_by"))
.withJdbcTemplate(new JdbcTemplate(dataSource))
.withDbUpperCase(false)
.build()
);
}
}
위처럼 @Bean 으로 등록된 LockProvider 를 가져와 사용하면 된다.
// LockProvider 를 가져와 LockConfiguration 을 통해 최소 락 시간, 최대 락 시간을 설정한다.
Optional<SimpleLock> lock = lockProvider.lock(new LockConfiguration(
Instant.now(),
b.getJ_SEQ().toString(),
Duration.ofSeconds(30), // 최대 락 시간
Duration.ofSeconds(20) // 최소 락 시간
));
if(lock.isPresent()) {
try {
..동적 프로시저 호출 및 로직수행
} catch(Exception e) {
}
}
LockProvider 를 가져와 LockConfiguration 을 통해 최소 락 시간, 최대 락 시간을 설정한다.