MSA 환경에서는 서비스가 여러 인스턴스로 수평 확장되어, 동일 데이터(예: 주문 처리, 재고 차감 등)에 동시 접근 시 race condition이 발생할 수 있습니다.
전통적 DB 락은 성능 부하와 데드락 리스크가 크고, 클러스터 환경 간 락 공유가 제한적입니다.
Redis 분산락은 대기 시간·만료 설정이 유연하고, 클러스터·Sentinel·Single 모드 모두 지원해 확장성·고가용성을 만족합니다.
Spring AOP로 락 획득·해제 로직을 애노테이션 한 줄로 추상화하면, 비즈니스 코드와 완전 분리된 재사용 가능한 분산락 모듈을 만들 수 있습니다.
build.gradle 예시:
dependencies {
// AOP
implementation 'org.springframework.boot:spring-boot-starter-aop'
// Redis
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
// Redisson (분산락 클라이언트)
implementation 'org.redisson:redisson-spring-boot-starter:3.17.7'
}
@Configuration
public class RedisConfig {
@Value("${spring.redis.host}") private String host;
@Value("${spring.redis.port}") private int port;
@Value("${spring.redis.password:}") private String password;
@Bean
public RedissonClient redissonClient() {
Config cfg = new Config();
// Single 서버 모드
cfg.useSingleServer()
.setAddress("redis://" + host + ":" + port)
.setPassword(password.isEmpty() ? null : password);
// └ Cluster나 Sentinel 모드 샘플도 아래처럼 추가 가능
// cfg.useClusterServers()…; cfg.useSentinelServers()…;
return Redisson.create(cfg);
}
@Bean
public RedisConnectionFactory redisConnectionFactory() {
LettuceConnectionFactory factory = new LettuceConnectionFactory(host, port);
if (!password.isEmpty()) factory.setPassword(password);
return factory;
}
}
Tip: 운영 환경별(Cluster/Sentinel) 설정 예시는 [Redisson 공식 문서]를 참고하세요.
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface DistributedLock {
/**
* SpEL 표현식으로 락 키를 생성합니다.
* ex) "order:#{#orderId}"
*/
String key();
/** 락 획득 최대 대기 시간 (default: 5초) */
long waitTime() default 5;
/** 락 만료 시간 (default: 10초) */
long leaseTime() default 10;
/** 시간 단위 (default: SECONDS) */
TimeUnit timeUnit() default TimeUnit.SECONDS;
}
@Aspect
@Component
public class DistributedLockAspect {
private final RedissonClient redissonClient;
private final ExpressionParser parser = new SpelExpressionParser();
private final DefaultParameterNameDiscoverer nameDiscoverer = new DefaultParameterNameDiscoverer();
public DistributedLockAspect(RedissonClient redissonClient) {
this.redissonClient = redissonClient;
}
@Around("@annotation(lockAnno)")
public Object around(ProceedingJoinPoint pjp, DistributedLock lockAnno) throws Throwable {
MethodSignature sig = (MethodSignature) pjp.getSignature();
Method method = sig.getMethod();
// 1) SpEL로 락 키 파싱
String lockKey = parseKey(lockAnno.key(), method, pjp.getArgs());
RLock lock = redissonClient.getLock(lockKey);
// 2) 락 획득 시도
boolean acquired = lock.tryLock(lockAnno.waitTime(), lockAnno.leaseTime(), lockAnno.timeUnit());
if (!acquired) {
throw new LockAcquisitionException("Lock 획득 실패: " + lockKey);
}
try {
// 3) 비즈니스 로직 실행
return pjp.proceed();
} finally {
// 4) 항상 락 해제
if (lock.isHeldByCurrentThread()) {
lock.unlock();
}
}
}
private String parseKey(String spelKey, Method method, Object[] args) {
EvaluationContext ctx = new StandardEvaluationContext();
String[] params = nameDiscoverer.getParameterNames(method);
if (params != null) {
for (int i = 0; i < params.length; i++) {
ctx.setVariable(params[i], args[i]);
}
}
return parser.parseExpression(spelKey).getValue(ctx, String.class);
}
}
parseKey()
: 메서드 인자를 SpEL 변수(#argName)로 바인딩해, survey:#{#dto.id}
같은 동적 키를 지원합니다.
tryLock(wait, lease, unit)
: 대기 시간(waitTime) 동안 락을 시도하고, 성공 시 leaseTime 후 자동 해제됩니다.
finally
에서 unlock()
을 호출해, 예외가 나도 락이 풀리도록 보장합니다.
@Service
public class SurveyService {
// surveyDto.id별로 동시성 제어
@DistributedLock(key = "survey:#{#dto.surveyId}", waitTime = 3, leaseTime = 5)
public void submitSurvey(SurveyDto dto) {
// 1) 중복 응답 검사
// 2) DB에 저장
// 3) 후속 알림 처리
}
}
key에 SpEL 표현식을 넣어, DTO 속성이나 메서드 파라미터를 바로 락 키로 사용합니다.
waitTime·leaseTime·timeUnit 속성으로, 서비스별 성격에 맞게 획득 대기·만료 타이밍을 미세 조정할 수 있습니다.
획득 실패율, 대기 시간 통계(Micrometer → Prometheus) → 임계치 이상 시 알람
애플리케이션 강제 종료 시 자동 해제를 보장하려면 leaseTime을 꼭 설정하고, Redis 만료 정책도 검토
락 해제(unlock)와 DB 커밋 타이밍이 잘 맞아야, 트랜잭션 롤백 시에도 락이 풀리도록 설계
Embedded Redis를 이용한 통합 테스트로, 멀티 스레드 환경에서 분산락 시나리오 검증
운영 Redis 구성에 따라 Redisson 설정(Cluster/Sentinel) 예시 코드를 추가해 두면 실무 적용이 편리
Spring AOP + Redis(Redisson) 분산락 모듈을 애노테이션 한 줄로 추상화하면,
비즈니스 코드와 완전 분리
다양한 락 키·타이밍 조절 가능
MSA 환경의 동시성 문제를 깔끔하게 해결