Java/Spring

Java ShedLock 이란?

Z_Z 2025. 5. 13. 15:35
반응형

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 을 통해 최소 락 시간, 최대 락 시간을 설정한다.

 

 

 

 

 

 

 

 

 

반응형