Study/Spring

API 동시호출 방지(따닥 이슈) - redis를 이용한 분산락

짱이08 2023. 10. 17. 18:00

❗️문제사항

: 프로젝트에서 API의 중복 호출로 인해 DB에 두번 insert되는 문제 발생

❓ 문제 개요

: 가입 신청시 가입신청 API를 호출

-> apply_no를 파라미터로 join table에 저장 후 가입 승인/반려 시 apply_no를 조회하여 가입 신청내역이 존재하는지 확인하는데 여기서 두개 이상의 결과가 조회되는 경우가 있기 때문에 오류 발생.

🔴 해결방안

  • DB unique key 설정
    -> join 테이블의 apply_no 를 유니크 키로 설정하면 해당 오류는 발생하지 않는다. 그렇지만, api가 두번 호출되어 신청(apply) 테이블에는 해당 리소스가 두번 이상 들어가는 경우가 있게 된다. 따라서, 해당 방법은 근본적인 해결 방법이 되지 못한다.

 

  • java synchronized
    -> 하나의 프로세스 내에서만 보장, 서버가 두대일 경우 불가 : 운영기는 was가 두대이기 때문에 적용 불가.

 

  • 분산락
    ->분산락은 데이터베이스에서 적용하는 것이 아니라 공통된 저장소를 사용하여 자원이 현재 사용중인지 확인한다.
    따라서 여러 서버나 프로세스에서 전체 시스템에 동기화된 처리를 가능하게 한다.

Redis를 사용하여 분산락 구현

- redis를 사용한 이유

 

1. 빠른 응답 시간 : 레디스는 메모리 기반의 데이터 베이스로 매우 빠른 속도로 읽고 쓸 수 있습니다.

2. 내장된 락 기능 : 레디스에서 분산 락을 구현하기 위한 메서드를 제공하고 있습니다.

 

redis 클라이언트 라이브러리로 Lettuce와 Redission이 있습니다.

 

Lettuce 방식

장점

: 간단하고 직접적인 방법으로 락을 설정할 수 있습니다.

 

단점

: 락을 수동으로 해제해야 하며, 예상치 못한 오류로 락이 영구히 남을 수 있습니다.

스핀락으로 인한 리소스 낭비가 발생할 수 있습니다.

 

Redisson 방식

장점

: 자동으로 타임아웃을 설정하여 안전하고 신뢰성 있는 락 관리를 제공합니다.

 

단점

: 설정한 타임아웃이 지나기 전까지 락이 유지되어 일부 상황에서는 자동 해제되지 않을 수 있습니다.

Redisson 라이브러리 사용과 설정이 필요합니다.

 

Lettuce의 경우 락을 획득하기 위해 반복적으로 확인을 해야 하므로 CPU 리소스를 계속 사용하게 되어 성능 저하가 될수 있습니다.

따라서 해당 프로젝트에서는 Redission방식을 사용하여 구현하였습니다.

 

 

아래는 Lettuce로 분산락을 구현한 코드입니다.

void doProcess() {
    String lockKey = "lock";

  try {
            // 락을 시도하고 성공하면 작업 수행
            if (syncCommands.setnx(lockKey, "1") == 1) {
                System.out.println("락 획득 성공! 작업을 수행합니다.");
            } else {
                System.out.println("락을 획득할 수 없습니다. 다른 프로세스가 락을 소유 중입니다.");
            }
        } finally {
            // 락을 해제
            syncCommands.del(lockKey);
            System.out.println("락 해제 완료.");
            connection.close();
            redisClient.shutdown();
        }
    }
}

 

Redission으로 분산락을 구현한 코드입니다.

void doProcess() {
    // 분산 락을 얻습니다.
        RLock lock = redisson.getLock("myLock");

        try {
            // 락을 획득하고 최대 10초간 대기합니다. 10초 후에 락을 자동으로 해제합니다.
            boolean isLocked = lock.tryLock(10, 10, java.util.concurrent.TimeUnit.SECONDS);

        } catch (InterruptedException e) {
            // 예외 처리
        } finally {
            // 락을 해제
            lock.unlock();
            System.out.println("락 해제 완료.");
            redisson.shutdown(); // Redisson 클라이언트를 종료합니다.
        }
}

 

두 라이브러리 모두 락을 획득하고 해제하는 과정은 동일합니다.

lock.tryLock(10, 10, java.util.concurrent.TimeUnit.SECONDS);

차이점은 위에 말한것 처럼 Redission의 경우 위의 코드로 락을 획득할때 자동으로 타임아웃을 설정하여 해당 시간이 지나면 자동으로 락을 해제시킵니다.