본문 바로가기
서버

Redis에서 사용하는 분산락 알고리즘인 RedLock에 대해 알아보자

by 베어 그릴스 2023. 9. 16.
320x100

개요

redis에서는 분산락을 제공하기 위해 redLock 알고리즘을 사용한다.

이에 대해 공부해보고 내용을 정리해본다.

 

참고: https://redis.io/docs/manual/patterns/distributed-locks/

 

Distributed Locks with Redis

A distributed lock pattern with Redis

redis.io

 

분산락을 적용하는 방법

레디스가 단일 인스턴스일 때의 방법은 간단하다.

레디스의 SetNX 를 사용하면 된다.

 // 락이 존재하지 않는다면 value를 set하고 3초 뒤에 ttl 한다. 
 SET key_name random_value NX PX 3000

 

spring data redis를 활용한 스프링 코드로 보면 다음과 같을 것이다.

 

과정을 간단하게 서술해보면,

  1. 레디스의 lock key를 만든다.
  2. 그 키에 값이 할당 되어있는지 setIfAbsent 메서드를 할당한다.
  3. 값이 할당 되어있지 않다면 값을 설정하고 다음으로 넘어간다.
  4. 값이 할당되어있다면 중복된 요청이므로 에러를 return한다.
  5. (코드에는 없지만) 할 일들이 끝나면 키 값을 삭제한다.

즉, 레디스에서 키에 대한 값이 있다면 락이 있는 것이고 키에 대한 값이 없다면 락이 없는 것으므로 락을 휙득하기 위해 키에 대한 값을 set 하는 것이다.

참고로, 레디스는 싱글 스레드로 돌아가기 때문에 단일 명령어에 대해서는 동시성 이슈를 생각하지 않아도 된다.

 

 

위 코드는 완벽한 동시성 제어를 제공할 수 없다.

 

이유는 무엇일까??

 

이유는 락을 생성하지 않은 요청에서 락을 삭제할 수도 있기 때문이다.

 

락의 조건이 키에 대한 값이 있냐 없냐인데 키만 알고 있다면 코드 어디서든 똑같은 요청이 아니더라도 삭제할 수 있다.

(가령 극단적이긴 하지만 멀티 스레드 환경에서 다른 스레드에서 delete 해버릴 수도 있다.)

 

즉, 위 방법은 안전하지 않다.

 

 

그렇다면 락을 건 요청 (스프링 MVC에서라면 하나의 스레드) 에서만 락을 해제할 수 있게하려면 어떻게 할 수 있을까?

 

value에 자기만 아는 unique random값을 걸면 된다.

 

그리고 락을 해제할 때 키에 unique random 값(UUID 같은 값)이 있다면 해제하면 락을 건 요청만 락을 해제할 수 있을 것이다.

 

하지만 위와 같은 코드를 자바 코드로 짜게 되면 또 문제가 된다.

 

스프링 환경에서 위와 같이 자바 코드로 한줄한줄 redis의 key 값을 get하고 이 값이 내가 설정한 uuid와 같다! 라고 판단하는 것은 자바 코드 상에서 또 동시성 이슈가 생길 수 있다. 

 

이를 아래와 같은 lua 스크립트를 통해 redis 명령어들을 작성하고 이를 한번에 실행 시켜 동시성 이슈를 해결할 수 있다.

 

if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end

 

RedLock 알고리즘

redis가 싱글 노드일 때는 위와 같은 설정만 하면 충분하지만 분산 환경일 때 (redis가 N대의 싱글 노드일 때)는 어떻게 할 수 있을까에 대한 답이 바로 RedLock이다.

 

락을 휙득하기 위해서는 아래와 같은 RedLock 과정을 거쳐야한다.

 

  1. 현재 시간을 밀리초 단위로 가져온다.
  2. (위에서 봤듯이) 모든 인스턴스에서 동일한 키 이름과 random unique value를 사용하여 모든 인스턴스에 Lock을 얻으려고 요청을 동시에 보낸다. 이때 락을 얻기 위한 timeout 시간으로 lock ttl(time to live) 시간 보다 조금 더 짧게(5~50 밀리 세컨드) 설정한다. 이는 죽은 Redis Node로 부터 오래 기다리는 것을 방지할 수 있게한다. 즉, 레디스 노드에 문제가 있다고 판단 되면 빠르게 다음으로 넘어간다.
  3. 락을 휙득하기 위해 걸린 시간 (1의 시간 - 현재 시간)을 계산하여 경과된 시간을 구하고 이 시간이 lock의 ttl 보다 짧은 경우 락을 휙득한 것으로 간주한다.
  4. 2/N + 1개의 락을 휙득했다면 락 휙득에 성공했다고 간주한다.

 

RedLock의 문제와 Fencing Token

RedLock은 기본적으로 모든 노드와 요청 클라이언트가 동일한 시간(클락)을 가지고 있을 때 정상 동작을 보장한다.

즉, 몇개의 노드가 시간이 더 빨리가거나 (클락이 튀는 현상이 발생하거나) 하게 되면 문제가 일어날 수 있다.

 

사실 이런 일이 일어나기는 힘들다고 보긴하지만 혹여나 이를 위해 Fencing Token이라는 개념이 도입된다.

(redis의 redLock은 이 기능을 제공하지는 않는다.)

 

 

참고

https://martin.kleppmann.com/2016/02/08/how-to-do-distributed-locking.html

 

How to do distributed locking — Martin Kleppmann’s blog

How to do distributed locking Published by Martin Kleppmann on 08 Feb 2016. As part of the research for my book, I came across an algorithm called Redlock on the Redis website. The algorithm claims to implement fault-tolerant distributed locks (or rather,

martin.kleppmann.com

 

위와 같이 일종의 데이터 버저닝과 같은 순차 증가하는 fencing token을 이용해 클락 지연 혹은 클락이 튀는 문제를 해결할 수 있다.

(위 그림은 클락 지연에 의한 문제이다.)

 

하지만 분산 환경이기 때문에 fencing token을 동기화해야하고 이를 위한 합의 알고리즘이 추가로 필요해진다.

 

 

728x90