[Spring] Redis를 이용한 분산 Lock 구현 알아보기

kshired·2023년 3월 11일
1
post-thumbnail

시작하며

서비스를 구현하다 보면, 여러 쓰레드에서 동시다발적으로 일어나는 작업에 대해 동기화 혹은 일관적인 처리가 필요할 때가 존재한다.

그럴 때는 여러 방법을 이용하여 동시성 문제를 해결 할 수 있는데, 오늘은 많이 사용하는 key-value store인 Redis를 이용하여 Lock을 구현하는 방법에 대해 알아보자.

프로그래밍 언어에서 제공하는 Lock을 이용

사실 이 방법은 분산 락 구현 관점에서는 옳지 않다. 서비스가 단일 프로세스로만 구동되는 상황이라면 적합할 수 있지만, 한 서비스가 분산환경에서 동작하고 있다면 이 방법은 적용해봤자 분산환경에서는 원하는대로 동작하지 않는다.

그렇다면, 어떻게 구현해야 할까?

Redis를 이용하면 손쉽게 구현할 수 있는데, 그 방식을 알아보자.

추가 : Java 혹은 Kotlin에서 프로세스 단위 동시성 제어에 대해 궁금하다면, ReentarantLock 혹은 synchronized에 대해 공부해보자.

Redis를 이용한 구현

Redis를 이용하여 Lock을 구현하는 방식중 가장 쉽게 떠올릴 수 있는 생각은 무엇일까?

가장 간단하게 생각하면, Reids에 특정 key가 존재하는지 확인하고 없으면 key를 세팅하는 방식을 생각할 수 있다.

그 구현 예시를 알아보자.

구현 예시

@Component
class Lock(private val redisTemplate: StringRedisTemplate) {
    fun lock(key: String, ttl: Long): Boolean {
        redisTemplate.opsForValue().get(key)?.let {
            return false
        }
      	
        redisTemplate.opsForValue().set(key, "locked", ttl)
        return true
    }
  
    fun unlock(key: String) {
        redisTemplate.delete(key)
    }
}

Spring을 사용하는 경우에는 spring-data-redis 를 통해 Redis의 명령어를 손쉽게 사용할 수 있다.

구현에서 알 수 있듯이, key가 존재하는지 확인하고 없으면 그 key를 세팅하는 과정을 거치면 된다.

하지만 이 방식에도 문제점이 있다. 그 문제점을 알아보자.

문제점

이 방식의 문제점은 key가 존재하는지 체크하는 과정과 없으면, set하는 과정이 원자적으로 일어나지 않는 것이다. 보통 이러한 상황을 check-act가 원자적으로 동작하지 않는다고 이야기한다.

이게 왜 문제일까? 모든 쓰레드가 갇혀있다가 나갔을 때, race condition이 발생할 수 있는 문제점이 존재하기 때문이다.

이러한 문제에 대해 간단하게 알아보고 싶다면, 이 포스트를 읽어보는 것을 추천한다.

그럼 이것을 어떻게 해결할까? 다음 파트인 "Redis를 이용한 Atomic한 구현"에서 알아보자.

Redis를 이용한 Atomic한 구현

위에서 알아보았듯이, check-act를 원자적으로 구현하지 않으면 동시성 문제를 제대로 해결하지 못한다. 어떻게 해결할 수 있을까?

그 방법은, Redis에서 제공하는 SETNX 연산을 사용하는 것이다.

SETNX 는 SET if Not eXist 의 줄임말으로, 키가 존재하지 않을 경우 값을 지정하는 방식으로 동작한다.

"Redis를 이용한 구현"에서 보여주었던 것 처럼, 존재하는지 확인하고 없으면 SET하는 방식이 아닌 check-act를 원자적으로 동작하도록 보장한 방법이다.

구현 예시

@Component
class Lock(private val redisTemplate: StringRedisTemplate) {
    fun lock(key: String, ttl: Long): Boolean {
        return redisTemplate.opsForValue().setIfAbsent(key, "locked", ttl) ?: false
    }
  
    fun unlock(key: String) {
        redisTemplate.delete(key)
    }
}

spring-data-redisRedisTemplate 은 위와 같이 setIfAbsent 라는 함수를 통해 SETNX 명령어를 사용할 수 있도록 해준다.

마치면서

사실 이렇게 직접 구현할 필요없이, Redisson이라는 라이브러리에서 SETNX 를 이용한 lock을 제공하고있다.

구현하지 않고, 이러한 라이브러리를 잘 사용하는 것도 좋은 방법이다.

Reference

profile
글 쓰는 개발자

0개의 댓글