Distributed Locks with Redis (Redis docs)

0️⃣1️⃣·1일 전
0

redis

목록 보기
1/1

https://redis.io/docs/latest/develop/use/patterns/distributed-locks/

Distributed Locks

  • 많은 환경에서 다양한 프로세스에서 공유하는 자원을 상호 독점적으로 사용할 수 있게 해준다.

Safety and Liveness Guarantess

  • 효과적인 방법으로 분산락을 사용하기 위해서는 아래 조건들을 만족해야 한다.
    • Safety property
      • 어떤 순간에 하나의 클라이언트만 락을 가져야 한다.
    • Liveness property
      • 교착상태가 없다.
    • Liveness proprety
      • 대다수의 레디스 노드가 UP 상태라면, 클라이언트는 락을 획득하고 해제할 수 있다.

Why Failover-based Implementations Are Not Enough

리소스에 락을 하는 가장 간단한 방법은 인스턴스에 키를 만드는 것이다. 키는 대개 제한된 시간동안에 살아있고, 레디스 만료 기능을 통해서 해제된다. 클라이언트가 리소스를 해제하면, 키는 삭제된다. 그렇지만 여기에는 문제가 있다. 마스터 노드가 죽는다면, 단일 실패 지점이 될 수 있다. 그렇다고 레플리카를 추가할 순 없다. 레디스 복제는 비동기적으로 이뤄지므로 Safety property를 위배할 수 있다.

위배하는 상황은 아래와 같다.

  1. 클라이언트 A가 마스터에서 락을 획득한다.
  2. 키에 대한 쓰기를 레플리카로 보내기전에 마스터는 충돌한다.
  3. 복제본이 마스터로 승격된다.
  4. 클라이언트 B는 클라이언트 A가 락을 가진 리소스에 대해서 락을 가질 수 있게 된다. 이것은 Safety property를 위배한다.

만약 락을 동시에 갖는 것이 문제가 되지 않는다면 이 모델을 사용할 수 있다.

Implementations

  • Java중에서 Redisson(Java)

Correct Implementation with a Single Instance

    SET resource_name my_random_value NX PX 30000
  • 키가 이미 존재하지 않는 경우에만 키를 설정(NX)
  • 만료 시간은 30000 밀리초 (PX)
  • 값(my_random_value)은 모두 클라이언트와 모든 잠금 요청에서 고유해야 한다.
if redis.call("get",KEYS[1]) == ARGV[1] then
    return redis.call("del",KEYS[1])
else
    return 0
end
  • random value는 안전한 방식으로 락을 해제하기 위해서 사용된다. 다른 클라이언트가 락을 삭제하는 것을 방지할 수 있다. 락의 만료시간보다 오래 사용한 클라이언트가 다른 클라이언트에 의해 사용되고 있는 락을 지워버릴 수 있기 때문이다. 그래서, 랜덤 값을 통해서 값이 같을때만 삭제하게 된다.

The Redlock Algorithm

N개의 레디스 마스터가 있다고 가정한다. 노드들은 모두 독립적이고, 레플리카를 사용하지 않았다. 위에서 싱글 인스턴스에 대해서 안전하게 락을 획득하고 해제하는 방법을 알고 있다. 5개 레디스 마스터를 독립적인 환경에서 실패할 수 있도록 각자 다른 가상 환경에서 수행시킨다.

락을 얻기 위해서 클라이언트는 아래 작업들은 수행한다.

  1. 현재 시간을 밀리초로 가져온다.
  2. 같은 키와 랜덤 값으로 모든 인스턴스에서 순차적으로 락을 획득한다. 클라이언트에서는 락 해제시간보다 짧은 시간의 타임아웃을 사용하도록 한다. 이건 클라이언트에게 레디스 노드가 다운됐을 때 오랜 시간 기다리는 것을 막아준다.
  3. 클라이언트는 1번에서 가진 현재 시간을 기준으로 얼마나 많은 시간이 흘렀는지 계산한다. 만약에 클라이언트가 적어도 3개 이상에서 락을 획득할 수 있고, 획득한 전체 시간이 경과 시간보다 작다면 락을 획득한 것으로 여겨질 수 있다.
  4. 락이 획득됐다면, 처음 유효 시간에서 락을 얻기 위해 경과된 시간을 차감하면 된다.
  5. 만약에 클라이언트가 락을 획득하지 못했다면, 전체 인스턴스에 대해서 잠금을 해제한다.

N개의 레디스를 통해서 고가용성 및 일관성 확보

Is the Alogirhtm Asynchronous?

N개의 노드가 있으면, 노드의 환경에 따라서 만료 시간을 통한 시간 동기화 문제가 발생할 수 있다. 노드마다 락을 얻기 위해 경과된 시간이 다르면, 해제 시간이 만료될 때 문제가 발생할 수 있다.

과반수를 통한 락 획득을 통해서 안정성을 유지할 수 있도록 한다.

Retry on failure

클라이언트가 락을 획득하지 못하면, 랜덤한 딜레이 후에 재시도를 하게 된다. 대다수가 락을 획득하게 되면, 더 이상 나머지에 대해서는 재시도 하지 않아도 된다. 또한 잠금을 획득하지 못한 경우, 일부 획득한 잠금을 최대한 빨리 해제하는 것이 중요하다. 이렇게 빨리 잠금을 해제하면 키 만료를 기다릴 필요가 없어진다.

Releasing the Lock

특정 인스턴스에 락을 획득하거나 획득하지 못했더라도 락을 해제하는 것은 간단하다.

Safety Arguments

여러 개의 인스턴스에 락을 획득하려고 하면, 키는 다양한 시간대에 셋팅 될 수 있다. 만약에 T1을 가장 처음 락을 획득한 시간, T2를 가장 나중에 락을 획득한 시간으로 하면 아래의 식이 만족된다.

MIN_VALIDITY=TTL-(T2-T1)-CLOCK_DRIFT

최소 유효 기간동안에는 동시에 락을 잡을 수 없음을 의미한다.

이미 N/2+1 노드가 락되어 있는 동안에, 다른 클라이언트가 N/2+1을 획득할 수 없다.

최대 만료 시간(MAX_VALIDITY)보다 크거나 가깝게 락을 획득한 클라이언트는 락은 무효화되고 인스턴스들에서 락을 해제한다. 그러므로, 만료 시간내에 락을 획득한 경우에만 동시에 락을 잡지 못하는지 고려하면 된다. 이 상황에 대해서는 이미 MIN_VALIDITYN/2+1로 고려되어 있다.

Liveness Arguments

  1. 락의 자동 해제(키 만료 이후): 키는 다시 락될 수 있다.
  2. 락이 획득되지 못했을 때 락을 해제하거나 락이 획득되고 작업이 종료됐을 때 클라이언트들은 협력하므로, 우리는 락을 다시 얻기 위해서 키의 만료까지 기다릴 필요가 없다.
  3. 클라이언트가 락을 다시 시도할 때, 다수의 락을 획득하는 데 필요한 시간보다 충분히 긴 시간을 기다렸다가 시도하면, 자원 충돌 시 split brain 상황이 발생할 가능성을 낮출 수 있다.

네트워크가 분할되면 TTL 시간만큼의 가용성 저하가 발생하며, 만약 네트워크 분할이 지속적으로 발생하면 이 가용성 저하는 무한히 계속될 수 있다. 이런 상황은 클라이언트가 락을 획득한 후, 락을 해제하기 전에 네트워크가 분할되어 해당 인스턴스와 연결이 끊겼을 때 발생한다.

Performance, Crash Recovery and fsync

TBD

post-custom-banner

0개의 댓글