redisson trylock 내부로직 살펴보기

https://opengraph.githubassets.com/c90eb78f30bf36acd86efd3a6d4093807ca77c6f8aa26f27630af6e2e17f3f76/redisson/redisson

redisson 이란??

  • redisson은 자바 언어로 구현된 레디스 분산락 클라이언트이다

  • 레디스 분산락은 서로 다른 프로세스가 서로 베타적인 방식으로 공유 리소스와 함께 작동해야 하는 많은 환경에서 매우 유용한 기본 기능이다

  • 자바 언어 이외에 ruby, python, php 등 다양한 클라이언트 라이브러리가 존재한다

그렇다면 분산락을 구현하는 로직은 어떻게 될까?

  • 구현 자체는 간단하다

  • 획득하고자 하는 이름의 락을 정의하고 유효 시간까지 락 획득을 시도하게 된다

  • 만약 유효 시간이 지나면 락 획득은 실패하게 된다

  • 단 여기서 주의할 점은 unlock할 경우에, 해당 세션에서 잠금을 생성한 락인지 확인해야 한다

  • 그렇지 않으면 다른 세션에서 수행중인 잠금이 해제될수도 있다

    • isLocked() : 잠금이 되었는지 확인

    • isHeldByCurrentThread() : 해당 세션에서 잠금을 생성했는지 확인

락을 점유를 시도하는 tryRock은 어떻게 구현되어 있나?

전체 코드

  • 전체 코드를 확인하고 아래는 각 단계별 수행하는 내용에 대해서 추가적으로 설명하려고 한다

  • redisson은 일반적으로 lettuce로 동시성을 해결하기 위한 차선책으로 많이 제시된다

  • 그 이유는 재시도가 필요한 경우에 lettuce는 직접 스핀락으로 구현해서 락 점유를 시도하기 때문이다

  • redisson은 스핀락으로 락을 점유하지 않고 pub/sub 구조로 레디스에 부하를 줄인다고 이야기하지만 실제 내부 로직을 살펴보면 스핀락 개념이 아예 없지는 않다

  • redisson 구현의 특징은 lua script와 세마포어의 사용이라고 할 수 있다

1. lock 획득을 시도한다

  • 획득을 시도하려는 lock의 유지시간을 확인한다

  • 아래에서 tryAcuiqre 로직을 더 살펴보겠지만 우선 ttl값이 null이면 lock을 점유했다고 간주한다

  • tryAcquire 내부 로직을 살펴보면 lua script를 사용해서 setnx를 실행하는 것을 확인할 수 있다

2. waitTime이 초과되었는지 확인한다

  • lock 에 대한 점유시간이 아직 남아있다면 다시 lock에 대한 획득을 시도하기 이전에 waitTime(lock 획득 시간)이 초과되지는 않았는지 확인한다

  • 만약 이미 초과되었다면 lock 획득은 실패로 리턴한다

3. 고유 Thread Id를 채널로 구독하여 lock이 available할때까지 대기한다

  • CompleteFuture.get() 메서드를 호출하여 thread id로 구독한 채널로 lock 획득이 유효할때까지 대기한다

  • 만약에 사용자가 설정한 waitTime을 초과할 경우 TimeoutException이 발생하여 lock 획득에 실패한다

  • 여기서 중요한 점은 subscribe 내부에는 세마포어를 사용해서 공유자원에 대한 점유를 수행한다는 것이다

  • 세마포어를 사용하여 공유자원을 점유하기 때문에 스핀락 보다는 레디스 I/O에 대한 부하를 줄일 수 있다

  • subscribe 내부 로직을 간단히 살펴보면 세마포어를 사용하는 것을 확인할 수 있다

  • 아래는 CompletableFuture.get 메소드에서 exception이 발생하는 케이스를 확인한 내용이다

redisson은 왜 세마포어를 사용했는가?

공식문서를 살펴 보면 세마포어를 사용한 이유를 파악할 수 있다. 레디스는 싱글 쓰레드로 동작하기 때문에 공유 자원에 대해서 쓰레드 세이프하게 동작하기 위해 동기화 매커니즘을 수행하기 위한 용도로 사용되었다고 설명하고 있다

공식문서 참고 : https://redisson.org/glossary/java-semaphore.html

4. waitTime 이전까지 무한루프를 수행하면서 lock 점유시간을 한번 더 확인한다

  • lock을 획득하여 아직 점유 유효시간이 남아있는지 한번 더 체크한다

5. thread id로 구독한 객체로 유효시간 또는 남은시간까지 lock이 avaliable한지 구독한다

  • ttl은 이전에 lock이 점유되어 남아있던 시간을 의미한다

  • time은 시도할 수 있는 남은 시간을 의미한다

6. lock을 시도할 수 있는 시간이 남아있는지 체크한다

7. 그 이후에는 유효시간 동안 while문으로 4~6번 과정을 반복하게 된다

결론

  • 처음엔 redisson은 spin lock 로직이 없이 내부적으로 pub/sub 구조만 가지고 있는줄 알았다

  • 하지만 살펴보니 spin lock 개념은 완전히 걷어내지는 못했지만 그래도 lettuce로 spin lock을 구현하는 것보단 훨씬 적은 부하로 락을 획득할 수 있다고 보여진다

참고

Last updated

Was this helpful?