1. 분산 락(Distributed Locks) 개요
- 분산 환경에서는 특정 리소스에 대한 작업이 배타적으로 수행되어야 하는 상황이 자주 발생합니다.
- 단일 애플리케이션에서는 Java의
java.util.concurrent.locks.Lock구현체를 사용하여 해결할 수 있습니다. - 그러나 애플리케이션이 여러 서버에 분산되거나 클러스터에서 실행될 때는 단순한 로컬 락으로는 동시성 제어가 불가능합니다.
- 분산 환경에서는 모든 서버가 공유할 수 있는 외부 저장소를 기반으로 락을 구현해야 합니다.
- Distributed Lock 더 보기
2. Spring의 LockRegistry 인터페이스
- 레퍼런스
- Spring Integration은 락 관리를 위한
LockRegistry인터페이스를 제공합니다. - 이 인터페이스는 일반적인 락 관리 기능을 정의하며, 구현체에 따라 단일 JVM 내 락이나 분산 락으로 활용할 수 있습니다.
- 동일한 인터페이스를 통해 다양한 저장소 기반의 락 구현체를 일관된 방식으로 사용할 수 있습니다.
2.1 주요 메서드
obtain(Object lockKey)- 주어진 락 키에 대한 락 객체를 획득합니다.
void executeLocked(Object lockKey, CheckedRunnable<E> runnable)- 스프링 6.2 이상에서 사용 가능
- 락을 획득하고 주어진 작업을 실행합니다.
- 해당 메서드는 태스크 호출에서 발생한 예외를 다시 던지며, Lock이 중단(interrupt)될 경우 InterruptedException을 던집니다.
T executeLocked(Object lockKey, CheckedCallable<T,E> callable)- 락을 획득하고 결과를 반환하는 작업을 실행합니다.
- 해당 메서드는 태스크 호출에서 발생한 예외를 다시 던지며, Lock이 중단(interrupt)될 경우 InterruptedException을 던집니다.
void executeLocked(Object lockKey, Duration waitLockDuration, CheckedRunnable<E> runnable)- 지정된 시간 동안 락 획득을 시도하고 작업을 실행합니다.
- 해당 메서드는 태스크 호출에서 발생한 예외를 다시 던지며, Lock이 중단(interrupt)될 경우 InterruptedException을 던집니다.
- 지정 시간동안 락을 획득하지 못하면 TimeoutException가 발생합니다.
T executeLocked(Object lockKey, Duration waitLockDuration, CheckedCallable<T,E> callable)- 지정된 시간 동안 락 획득을 시도하고 결과를 반환하는 작업을 실행합니다.
- 해당 메서드는 태스크 호출에서 발생한 예외를 다시 던지며, Lock이 중단(interrupt)될 경우 InterruptedException을 던집니다.
- 지정 시간동안 락을 획득하지 못하면 TimeoutException가 발생합니다.
2.2 사용 예시
2.2.1 락을 직접 관리하는 방식
// 락을 직 접 관리하는 방식
Lock lock = registry.obtain("someLockKey");
try {
if (lock.tryLock(3, TimeUnit.SECONDS)) {
try {
// 배타적 리소스에 접근하는 코드
}
finally {
lock.unlock();
}
}
}
catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
- LockRegistry의
obtain메서드를 사용하여 락을 획득합니다. tryLock메서드를 사용하여 지정된 시간 동안 락을 획득하려고 시도합니다.tryLock메서드는 지정된 시간 동안 락을 획득하지 못하면 false를 반환합니다.
- 락을 획득한 후에는
unlock메서드를 호출하여 락을 해제합니다.
2.2.2 executeLocked를 사용한 간편한 방식
registry.executeLocked("someLockKey", () -> {
// 배타적 리소스에 접근하는 코드
});
- executeLocked를 사용한 간편한 방식으로 Spring 6.2 이상에서 사용 가능합니다.
- 이 API의 동작은 잘 알려진 JdbcTemplate, JmsTemplate 또는 RestTemplate과 유사합니다.
- 템플릿 콜백 패턴으로 구현되어 있습니다.
2.3 주요 구현체
DefaultLockRegistry:ReentrantLockAPI 기반의 인메모리 락 구현체로, 단일 JVM 내에서만 유효하며 분산 환경에서는 사용할 수 없습니다.RedisLockRegistry: Redis를 사용하여 여러 서버 간에 분산 락을 구현합니다.JdbcLockRegistry: 데이터베이스를 사용한 분산 락 구현체입니다.ZookeeperLockRegistry: Zookeeper를 사용한 분산 락 구현체입니다.- 각 구현체는 사용 목적과 환경에 따라 선택해야 하며, 분산 환경에서는
DefaultLockRegistry가 아닌 분산 저장소 기반 구현체를 사용해야 합니다.
3. LockRegistry 확장 인터페이스
3.1 ExpirableLockRegistry
public interface ExpirableLockRegistry extends LockRegistry {
void expireUnusedOlderThan(long age);
}
- 스프링 5.0부터 RedisLockRegistry는
ExpirableLockRegistry를 구현합니다. - 마지막으로 획득한 지 age 이상 지났고 현재 잠겨있지 않은 락을 제거합니다.
- 오래된 락으로 인한 자원 낭비를 방지하는 데 유용합니다.
3.2 RenewableLockRegistry
public interface RenewableLockRegistry extends LockRegistry {
void renewLock(Object lockKey);
}
- 이미 획득한 락의 유효 시간을 연장할 수 있는 기능을 제공합니다.
- 장시간 실행되는 작업에서 락이 중간에 만료되는 것을 방지할 수 있습니다.