컴퓨터 탐험가 찰리 2026. 3. 21. 06:59
728x90
반응형

 

멀티 인스턴스 환경일 때 특정 key값에 대해서 동시성(동시 수정) 방지를 위해 사용한다.

 

Redis의 싱글 스레드라는 특성, Redisson 라이브러리를 이용한다.

Hash 자료구조를 사용한다.

- Key: lock:{key이름}:{보통 사용하는 id값}

- Field: {Redisson UUID} : {Java Thread ID}

소유자 식별자로 사용하며, Redisson UUID를 사용하면 RedissonClient 인스턴스가 서버(JVM) 인스턴스마다 고유하게 부여한다.

ex) a3f2e1d0-b7c4-4f2a-9e88-1234abcd5678 : 52

- Value: 재진입 횟수로 정수값으로 기록한다.

 

-> 똑같은 스레드가 똑같은 userId(보통 사용하는 id값)에 대해서 접근 시도(락 획득)를 할 때 이 키와 필드값을 검사하여 락을 확인하는 구조이다. 동일 키, 필드 값인 경우인데 락이 잡혀 있으면 재진입 처리를 하여 value 값을 올린다고 이해하면 된다.

 

 

 

키 및 로직

HEXIST로 내가 소유한 락인지 확인한다. HINCRBY로 원자적으로 증가시킨다. Lua Script를 통해서 다음과 같은 방식으로 구현되어 있다. Redisson이 제공하는 코드라고 이해하면 된다.

- key 구성: EVALSHA <script> 2 <KEYS[1]> <KEYS[2]> <ARGV[1]> <ARGV[2]> <ARGV[3]>

- 값 치환:

KEYS[1]: 실제 key 이름

KEYS[2]: pub/sub 채널명. ex) redisson_lock__channel:{lock key 값}

ARGV[1]: 소유자. UUID:threadId

ARGV[2]: leaseTime

ARGV[3]: pub 메시지. ex) 0 -> 0을 전송하여 이 field가 Hash에서 lock이 안잡혀 있음을 알림.

(다음은 참고용 락 구현 코드)

-- 1. 내가 소유한 락인지 확인
  if (redis.call('hexists', KEYS[1], ARGV[1]) == 0) then
      return nil  -- 내 락이 아니면 무시
  end

  -- 2. 재진입 카운트 감소
  local counter = redis.call('hincrby', KEYS[1], ARGV[1], -1)

  -- 3. 아직 카운트가 남아있으면 TTL만 갱신
  if (counter > 0) then
      redis.call('pexpire', KEYS[1], ARGV[2])
      return 0
  end

  -- 4. 카운트가 0이면 키 삭제 + 대기 중인 스레드에 알림
  redis.call('del', KEYS[1])
  redis.call('publish', KEYS[2], ARGV[3])
  return 1
 

 

실제 개발자가 사용하는 로직 예시

  1. [Java] lock() 호출 → (위 코드 부분) 락 획득용 루아 스크립트가 Redis에서 실행됨.
  2. [Java] 락 획득 성공 시, try { ... } 블록 진입.
  3. [Java] ★여기서 실제 로직 수행★
    • someService.purchase(userId, someId); (DB 트랜잭션 등)
  4. [Java] finally { unlock(); } 호출.
  5. [Redis] **'해제용 루아 스크립트'**가 실행되어 락을 반납함.

 

 

 

재진입 처리(reentrant)

코드가 길어지면 로직 내부에서 키 값을 중첩으로 호출할 수 있기 때문에, 같은 로직 내에서는 재진입이 가능하도록 방어적 설계로 처리함. 위 로직 예시부분에서, someService.purchase만 있는게 아니라 purchase 내부에 다시 lock을 획득하는 다른 메서드를 호출하는 로직이 있다고 가정해보면 Lock을 두 번 획득하게 됨.

항상 lock과 해제가 쌍으로 돌아가야 정상적으로 0으로 돌아가며, 내부 unlock을 빠뜨리면 leaseTime동안 다른 스레드가 대기하게 된다.

lock  → count: 0 → 1 (외부 획득)
lock  → count: 1 → 2 (내부 재진입)
unlock → count: 2 → 1 (내부 해제, 키 유지)
unlock → count: 1 → 0 (외부 해제, 키 삭제 → 다른 스레드 획득 가능)
728x90
반응형