-->

Intro

  • Redis는 싱글 쓰레드로 동작하기 때문에 단일 노드로 구축해 사용해도 동시성 문제가 발생하지 않는다.
  • SPOF(Single Point Of Failure) : 단일 장애 지점
  • Replication : Master - Slave - Slave의 구조
  • Failover(장애조치) : 현재 마스터를 복제복으로 바꾸고 복제본을 마스터로 변경한다. 마스터에서 실행하는 것
  • Redis의 복제는 비동기식이기 때문에 상황에 따라 경합(race condition)이 발생할 수 있다. (키에 대한 쓰기가 복제본으로 전송되기 전 마스터 다운)
  • Race Condition(경쟁 상황) : 2개 이상의 프로세스가 동시에 같은 자원에 접근하여 read, write 하는 상황
  • 임계구역 : 둘 이상의 쓰레드가 접근해서는 안되는 공유 자원을 접근하는 코드의 일부. 임계 구역은 지정된 시간이 지난 후 종료되며 때문에 어떤 쓰레드가 임계 구역에 들어가고자 한다면 지정된 시간만큼 대기해야한다.
  • Distributed Lock(분산락) : 경쟁 상황이 발생했을 때 혹은 하나의 공유자원에 접근할 때 데이터에 결함이 발생하지 않도록 원자성을 보장하는 기법.
  • SET 명령어
    • NX(Not eXists) 옵션 : 키가 존재하지 않을 때만 SET
    • PX 옵션 : ms로 유효 시간 조정
    • SETNX(Set  if Not eXists) 명령어 : 키가 없는 경우에만 SET이 성공 
  • Lock : 공유 자원에 대한 동시 접근을 제어하는 매커니즘. 하나의 쓰레드만 해당 자원에 접근, 변경 할 수 있다.
    • 비관적 락은 읽기조차 불가능

 

RedLock 알고리즘

과반 수 이상의 노드에서 Lock을 획득했다면 Lock을 획득한 것으로 간주

분산환경에서 Redis가 권장하는 방법이다.

  1. 현재 시간을 ms 단위로 구한다.
  2. 순차적으로 N대의 Redis 서버에 잠금을 요청한다. 이 때 timeout은 Lock의 유효 시간 보다 훨씬 작은 시간을 쓴다. 만약 Lock의 유효 시간이 10초라면 각 Redis 서버에 잠금을 획득하기 위한 timeout은 5~50ms 이다. 이렇게 짧은 timeout을 사용해 장애가 발생한 Redis 서버와 통신에 많은 시간을 사용하지 않도록 방지할 수 있다.
  3. Redis 서버가 5대라고 가정할 때 과반수(3대) 이상의 서버로부터 Lock을 획득했고 Lock을 획득하기 위해 사용한 시간이 Lock의 유효 시간보다 작았다면 Lock을 획득했다고 간주한다. 즉 Lock의 유효시간이 10초인데 Lock을 얻기 위해 11초가 걸렸다면 실패한 것이다.
  4. Lock을 획득 한 후 유효시간은 처음 Lock의 유효시간 - Lock을 얻기 위해 걸린 시간이다. Lock의 유효 시간이 10초인데 획득에 3초가 걸렸다면 얻은 후부터 7초 뒤에 만료된다.
  5. Lock을 얻지 못했다면 모든 Redis 서버에게 Lock 해제 요청을 보낸다. 예를 들어 5대의 Redis 서버 중 한 대의 서버에게만 Lock 획득을 성공했다. 과반 수 이상의 Lock을 획득하지 못했으므로 Lock 획득에 실패했고 모든 Redis 서버에 Lock 해제 요청을 보낸다.

요약 → 

  • Client는 Lock을 획득하기 위해 모든 Redis 서버에게 Lock을 요청. 과반 수 이상의 Redis 서버에게 Lock을 획득하면 Lock을 획득
  • 실패했다면 모든 Redis 서버에게 Lock 해제를 요청하고 일정 시간 후에 Lock을 획득하기 위한 재 시도를 한다.

완벽하진 않음 

GC, 네트워크 지연, 타이밍 이슈로 깨질 수 있음

 

Lettuce

Redis의 java 구현체

SETNX 명령어와 같은 원자적 연산을 사용해 스핀락 기법으로 분산락을 구현해야한다.

  • Spin Lock :멀티 스레딩 환경에서 공유 자원에 대한 동시 접근을 방지 하기 위한 락 중 하나.
    다른 락과는 다르게, 락을 획득할 대까지 계속해서 락 획득을 시도하고 조건을 확인하면서 대기하는 기법.
    1. A스레드가 락을 획득하려고 시도
    2. 락이 이미 다른 스레드가 획득했다면 A스레드가 반복적으로 요청하면서 락 획득 시도
    3. 락이 해제되면 다음으로 먼저 요청한 스레드중 하나가 랜덤으로 락을 획득  

지속적으로 Retry 하기 때문에 성능 이슈가 발생한다.

 

Redisson

pub/sub 방식을 이용한다.

Lock이 해제될 때마다 subscribe 중인 클라이언트에게 "이제 락 획득을 시도해도 된다" 라는 알림을 보낸다.

따라서 클라이언트에서 락 획득을 실패했을 때 redis에 지속적으로 락 획득 요청을 보내는 과정이 사라지고 이에 따라 부하가 발생하지 않게 된다.

또 Ridesson은 RLock이라는 락을 위한 인터페이스를 제공함.

RLock rLock = redissonClient.getLock(lockName);

try {
  boolean available = rLock.tryLock(waitTime, leaseTime, timeUnit);
  
  if(!available){
    return false; //락 획득 실패
  }
  //락 획득 후 로직 수행
}catch (InterruptedException e){
  //락을 얻으려고 시도하다가 인터럽트를 받았을 때 발생하는 예외
}finally{
	try{
      rLock.unlock();
      log.info("unlock complete: {}", rLock.getName());
    }catch (IllegalMonitorStateException e){
      //이미 종료된 락일 때 발생하는 예외
    }
}

 

 

<참고>

[Redis] 레디스가 제공하는 분산락(RedLock)의 특징과 한계

Redis FAILOVER

Redis로 동시성 문제 해결하기

레디스를 활용한 분산 락(Distrubuted Lock) feat lettuce, redisson

풀필먼트 입고 서비스팀에서 분산락을 사용하는 방법 - Spring Redisson (마켓컬리)

Distributed Lock 구현 과정 (채널톡에서 Redisson pub/sub 사용)

+ Recent posts