Notice
Recent Posts
Recent Comments
Link
«   2025/04   »
1 2 3 4 5
6 7 8 9 10 11 12
13 14 15 16 17 18 19
20 21 22 23 24 25 26
27 28 29 30
Archives
Today
Total
관리 메뉴

전공공부

[분산락] Redisson 기반의 분산 락 적용기 본문

Study/Spring Boot

[분산락] Redisson 기반의 분산 락 적용기

monitor 2024. 11. 9. 13:01

 

 

 

카카오페이와 일맥상통하는 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을 걸고 싶은 범위에서만 사용이 가능하며 코드 확장성이 높고 유연한 코드를 작성 할 수 있습니다.