아키텍처 구조
💡현재 프로젝트 구조
💡인터페이스 구현 구조
application service
↓
application port(interface)
↑
infrastructure adapter(implementation)
- Redisson에 대한 직접 의존을 application service에서 분리하기 위해, application 계층에 port 인터페이스를 두고 infrastructure 계층에서 구현하도록 설계했다.
- (왜 인터페이스는 application 계층에 두고, 구현체는 infrastructure 에 두는 걸까?)
- 아 어렵넹
Lock 추상화 인터페이스 StockLockManager 구현
💡StockLockManager.java
package com.hubEleven.stock.application.port;
import java.util.function.Supplier;
public interface StockLockManager {
<T> T executeWithLock(String lockKey, Supplier<T> supplier);
}
- lockKey : 어떤 작업에 락을 걸지 구분하는 키
- Supplier<T> : 락을 잡은 뒤 실행할 실제 작업
- <T> : 작업 결과 타입을 유연하게 받기 위한 제네릭
💡코드 작동 예시
"이 lockKey로 락을 잡고,
락을 잡은 동안 supplier 안의 재고 감소 작업을 실행해줘"
💡RedissonStockLockManager.java
package com.hubEleven.stock.infrastructure.lock;
import static com.hubEleven.stock.domain.exception.StockErrorCode.STOCK_LOCK_TIMEOUT;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.springframework.stereotype.Component;
import com.commonLib.common.exception.GlobalException;
import com.hubEleven.stock.application.port.StockLockManager;
import lombok.RequiredArgsConstructor;
@Component
@RequiredArgsConstructor
public class RedissonStockLockManager implements StockLockManager {
private static final long WAIT_TIME_SECONDS = 3L;
private static final long LEASE_TIME_SECONDS = 5L;
private final RedissonClient redissonClient;
@Override
public <T> T executeWithLock(String lockKey, Supplier<T> supplier) {
RLock lock = redissonClient.getLock(lockKey);
boolean locked = false;
try {
locked = lock.tryLock(WAIT_TIME_SECONDS, LEASE_TIME_SECONDS, TimeUnit.SECONDS);
if (!locked) {
throw new GlobalException(STOCK_LOCK_TIMEOUT);
}
return supplier.get();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new GlobalException(STOCK_LOCK_TIMEOUT);
} finally {
if (locked && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
}
이 코드를 하나 씩 살펴보면,
1. Redis에서 lockKey 이름의 자물쇠를 가져온다.
RLock lock = redissonClient.getLock(lockKey);
- 해당 lockKey 는 서비스 코드에서 생성되며, 재고 감소하려는 상품 ID (productId) 를 조합하여 생성한다.
- tryLock 시도 후 이 lockKey 가 겹쳤을 때, 동시성 제어를 하게된다.
2. 최대 3초 동안 자물쇠를 잡으려고 기다리고, 잡으면 5초 동안 락이 유지된다.
locked = lock.tryLock(3, 5, TimeUnit.SECONDS);
3. 락을 획득한 뒤, 실제 작업을 실행한다.
return supplier.get();
4. 작업 성공, 실패 여부와 상관 없이 마지막에는 락을 해체한다.
finally {
if (locked && lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
💡Redisson 분산 락 기반 재고 감소 동시성 제어 흐름

💡RLock 이 뭐지?
💡그래서 supplier.get() 은 무슨 작업을 하는거지?
Questions
1️⃣ 왜 StockServiceImpl이 RedissonClient나 RLock을 직접 쓰지 않고, StockLockManager 같은 인터페이스를 거치게 만드는 게 좋을까?
- StockServiceImpl이 RedissonClient나 RLock을 직접 쓰면 서비스 코드가 Redisson과의 의존성이 강해진다.
- 예를 들어 서비스 코드에 아래와 같이 RLock 을 직접 사용하게 되면,
@Service
public class StockServiceImpl {
private final RedissonClient redissonClient;
public void decrease() {
RLock lock = redissonClient.getLock("stock");
lock.lock();
try {
// 재고 감소
} finally {
lock.unlock();
}
}
}
- 락 방식을 변경하고자 할 때, 서비스 코드를 직접 수정해야한다.
- 즉, 하위 기술 변경이 상위 비즈니스 코드까지 연쇄적으로 수정되게 되고 유지보수가 어려워진다.
- 따라서 StockLockManager 와 같은 락 추상화 인터페이스를 활용해 해결할 수 있다.
public interface StockLockManager {
<T> T executeWithLock(String lockKey, Supplier<T> supplier);
}
- 인터페이스 구현체 예시
StockLockManager
├─ RedissonStockLockManager
├─ DatabaseStockLockManager
└─ InMemoryStockLockManager
2️⃣ 왜 executeWithLock(String lockKey, Supplier<T> supplier) 구조에서 락을 잡는 코드와 락 안에서 실행할 실제 작업을 분리해서 넘길까?
참고 자료
1. Java Docs : Supplier
https://docs.oracle.com/javase/8/docs/api/java/util/function/Supplier.html
Supplier (Java Platform SE 8 )
Represents a supplier of results. There is no requirement that a new or distinct result be returned each time the supplier is invoked. This is a functional interface whose functional method is get().
docs.oracle.com
2. Redisson Docs : Redisson Locks and Synchronizers
https://redisson.pro/docs/data-and-services/locks-and-synchronizers/index.html
Locks and synchronizers - Redisson Reference Guide
Locks and synchronizers Lock Valkey or Redis based distributed reentrant Lock object for Java and implements Lock interface. Uses pub/sub channel to notify other threads across all Redisson instances waiting to acquire a lock. If Redisson instance which ac
redisson.pro
'Projects > hub-eleven' 카테고리의 다른 글
| [동시성 처리] RedissonClient Bean 설정 클래스 추가 (2) (리소스 보완 필요) (0) | 2026.05.28 |
|---|---|
| [동시성 처리] RedissonClient Bean 설정 클래스 추가 (1) (0) | 2026.05.27 |
| [동시성 처리] 동시성 문제 (Race Conditon) 해결하기 (0) | 2026.05.11 |
| [동시성 처리] 동시성 문제 (Race Condition) 해결 방법 3가지 관점 - (3) Redis Distributed Lock (0) | 2026.03.11 |
| [동시성 처리] 동시성 문제 (Race Condition) 해결 방법 3가지 관점 - (2) Database Lock (0) | 2026.03.09 |