콘텐츠로 이동

2024 02 26

2024-02-26

레디스와 분산 락

참고: https://hyperconnect.github.io/2019/11/15/redis-distributed-lock-1.html 참고: https://hudi.blog/distributed-lock-with-redis/

  • 개요
    • 트랜잭션이 많이 일어나는 서비스에서 동기화된 처리가 필요한 경우
    • 여러 서버에 공통된 락을 줘야하는 경우
    • 분산 환경에서 상호 배제를 구현하여 동시성 문제를 다루는 것
      • 분산락은 데이터베이스의 갭락, s-lock, x-lock 등이랑 상관없다!
    • 락에 대한 정보를 '어딘가'에 공통적으로 보관하고 있어야 해
      • 다양한 여러대의 서버들은 공통된 '어딘가'를 보며, 자신이 critical section에 접근할 수 있는지 확인 (atomic 보장)
    • 레디스를 이용한 분산 락을 사용해보자!
  • 언제 발생?
    • Check-Then-Act : 검증시점의 정보가 행위 시점에 더 이상 유효하지 않아서 발생한 오류
  • 로컬 스핀락 : 지속적으로 락 획득할 수 있을때 까지 확인
    1. "락이 존재하는지 확인한다" & "존재하지 않는다면 락을 획득한다" => atomic!
      • setnx: 값이 존재하지 않으면 세팅한다, 값 세팅 여부를 리턴값으로 받아 락을 획득하는데 성공했는지 확인
        • 해당 옵션으로 "락을 사용중인지 확인하고, 락을 획득하는 것"을 atomic 하게 묶음
        • SET if Not eXists
    2. try 구문 안에서 락을 획득할 때 까지 시도
      • 약간의 sleep을 걸어줘 레디스에 너무 많은 요청이 가지 않도록 설정
    3. 락을 획득하면 연산 수행
    4. 락 사용한 뒤에는 꼭 해제할 수 있도록 finally 안에서 해제
      void doLocalSpinlock() {
          String lockKey = "local";
      
          try {
              while(!tryLock(lockKey)) {
                  try {
                      Thread.sleep(50);
                  } catch (Exception e) {
                      throw new RuntimeException(e);
                  }
              }
          } finally {
              unlock(lockKey);
          }
      }
      
      boolean tryLock(String key) {
          return command.setnx(key, "1");
      }
      
      void unlock(String key) {
          command.del(key);
      }
      
  • 위 코드의 문제점
    1. Lock 타임아웃이 없음
      • 사실상 락 획득 못하면 무한루프 도는 코드
      • 어플리케이션의 오류 때문에 종료되어 버리면, 영원히 락을 획득하지 못한채 무한루프 대기상태 => 장애
      • 일정 시간이 지나면 락이 만료되도록 구현!
      • 락 획득의 최대 허용 시간 or 최대 허용 횟수를 지정할 것!
    2. tryLock 로직이 try-finally 구문 밖일 것
      • 특정 시간/횟수 내에 락 획득 못하면 Exception
      • Exception이 발생해도 unlock이 된다면, 동기화 보장이 어려움
    3. 레디스에 많은 부하
      • ex) 300ms 걸리는 요청 100개 오면...
        • 1개가 락 잡고, 99개는 레디스에 요청 50ms 마다 락 요청 (300/50 * 99 == 594)
  • Redisson은 분산락을 개선했다
    • 참고: https://github.com/redisson/redisson/ 1. Lock에 타임아웃 구현되어 있음 2. 스핀락 사용하지 않음. pubsub 채택 3. Lua 스크립트...? 란 것으로 atomic하게 연산을 수행할 수 있게 지원
  • Message Broker 활용
    • SUBSCRIBE: 특정 채널을 구독한다
    • PUBLISH: 특정 채널에 메시지를 발행한다
    • 대기중이던 프로세스에게 '락 획득 시도하세요~' 알려줄 수 있음 (Redisson에서 지원하는 방식)