전공공부
[분산락] Redisson 기반의 분산 락 적용기 본문
카카오페이와 일맥상통하는 Redisson 기반의 분산 락 적용기
운영 중인 서비스에 분산 락 시스템을 적용하면서, 위의 카카오페이의 적용 사례를 참고 할 수 있을 것 같아 아래 사례 내역을 공유드립니다.
Redisson 기반의 분산 락은 여러 서비스가 동시에 접근할 때, Redis를 통해 임계 영역의 데이터가 서로 침범되지 않도록 제어하는 데 사용됩니다. 분산 시스템에서는 이러한 충돌이 의도치 않은 결과를 초래할 수 있기 때문에, 정확하고 일관된 데이터 처리를 위해 필수적입니다.
기존 AOP 방식 코드
아래는 기존에 사용하던 Aspect를 활용한 분산 락 코드입니다.
// Aspect 정의
@Aspect
@Component
public class LockAspect {
@Autowired
private RedissonClient redissonClient;
@Around("@annotation(Lockable) && args(lockKey)")
public Object lockMethod(ProceedingJoinPoint joinPoint) throws Throwable {
RLock lock = redissonClient.getLock(lockKey);
try {
lock.lock();
return joinPoint.proceed();
} finally {
lock.unlock();
}
}
}
//(...생략...)
// 사용측
@Lockable
public void processData(String lockKey) {
// 분산 락이 필요한 작업
System.out.println("Data is being processed");
}
일반적으로 위와 같은 방식으로 AOP를 사용해 메서드 레벨에서 분산 락을 적용합니다. 하지만 AOP 방식에는 몇 가지 불편한 점이 있습니다.
1. Private 메서드 적용 불가: AOP는 프록시를 통해 동작하므로, public 메서드가 아니면 적용이 어렵습니다.
2. 리팩토링 시 유지보수성 저하: 메서드 인자에 lockKey 외 다른 값이 추가될 경우, Aspect 코드도 변경이 필요하여 유지보수가 어려워집니다.
3. 확장성 제한: 특정 메서드 외의 코드 블록에 락을 적용하기 어려워, 유연한 확장이 어렵습니다.
개선
import com.mongodb.client.result.InsertOneResult;
import org.redisson.api.RedissonClient;
import org.redisson.api.RLock;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Component;
import java.util.concurrent.Callable;
@Component
public class UserLockManager implements UserLockOutPort {
private final RedissonClient redissonClient;
public LockManager(RedissonClient redissonClient) {
this.redissonClient = redissonClient;
}
public <R> R userLock(String userId, Callable<R> block) throws Exception {
RLock lock = redissonClient.getLock("lock:" + userId);
try {
lock.lock();
return block.call();
} finally {
lock.unlock();
}
}
}
위 코드에서 userLock 메서드는 특정 블록에 대해 락을 걸어야 할 범위를 지정할 수 있도록 Callable<R>을 인자로 받습니다. 이 방식으로 개선하면서 얻을 수 있는 장점입니다.
1. 락 범위 명확: 메서드 외부의 코드 블록에 락을 걸 수 있어, 특정 로직에 대해 명확히 락을 적용할 수 있습니다.
2. 테스트 용이성: 의존성 주입(DI)을 통해 LockManager를 테스트할 수 있으므로, 테스트 코드 작성이 훨씬 용이해졌습니다.
3. 유연한 확장성: 특정 메서드가 아닌 임의의 코드 블록에도 락을 적용할 수 있어, 확장 가능한 코드 구조를 갖출 수 있습니다.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.stereotype.Service;
@Service
public class UserAdapter implements UserUseCase {
private final UserLockOutPort userLockOutport;
private final MongoTemplate mongoTemplate;
@Autowired
public UserAdapter(UserLockOutPort userLockOutport, MongoTemplate mongoTemplate) {
this.userLockOutport = userLockOutport;
this.mongoTemplate = mongoTemplate;
}
public void insertUserWithLock(String userId, User user) throws Exception {
userLockOutport.userLock(userId, () -> {
// MongoDB에 사용자 정보를 삽입
mongoTemplate.insert(user);
System.out.println("User inserted with lock");
return null; // Callable이므로 반환값이 없으면 null로 처리
});
}
}
위와 같이 개선을 통해서 특정 코드에서 Lock을 걸고 싶은 범위에서만 사용이 가능하며 코드 확장성이 높고 유연한 코드를 작성 할 수 있습니다.
'Study > Spring Boot' 카테고리의 다른 글
Spring Security with Async (0) | 2024.02.27 |
---|---|
[Spring Cloud Gateway] API Gateway를 활용한 실습 (0) | 2024.02.20 |
[Spring Cloud Netflix Eureka] 기본 개념 정리 및 사용 방법 (0) | 2024.02.13 |
[Redis] Reactive Redis 연결 및 상세 설명 (0) | 2024.02.05 |
[Project 00] Webflux + DDD (1) | 2024.02.03 |