본문 바로가기

Hub Project/AltTab PJ

[AltTab] Java의 Redis Client와 동시성

728x90

📜 개요


🔹 다중 사용자를 위한 웹 애플리케이션을 설계하다 동시성 문제를 접하게 되었다. 이러한 동시성 문제를 해결하기 위해서는 다양한 방법이 존재한다.

 

1. synchronized, Lock 사용  

2. 낙관적 락(Optimistic Lock), 비관적 락(Pessimistic Lock)

3. USER-LEVEL Lock(Named Lock) 사용

4. Redis 사용(Lettuce, Redisson) 등등...

 

이 중 4번의 방법을 제외한 1, 2, 3의 방법으로는 단일 웹 서버 환경의 동시성 문제만 해결할 수 있다던지 혹은

다중 웹 서버 환경에서 동시성 문제를 해결할 수 있지만 스케일 아웃된 DB 환경에서는 동시성 제어를 할 수 없다는 문제점을 갖고있다. 이러한 문제점 없이 어느 환경에서도 동시성 문제를 해결할 수 있는 Redis를 활용하여 동시성 문제를 해결해보자.

 

🐳 Redis를 활용한 Lock 구현을 선택한 이유


🔹 먼저 동시성 이슈를 해결하지 않으면 발생할 수 있는 문제는 다음과 같다.

 

1. Race Condition

    ◈ 두개 이상의 스레드가 임계 영역에 동시에 접근하면 예상과 다른 결과가 나올 수 있다.

 

 2. DeadLock

    ◈ 두개 이상의 스레드들이 서로의 lock을 기다리다 아무 스레드도 실행되지 않을 수 있다.

 

동시성 이슈를 해결하기 위해서는 lock을 구현하여 요청이 들어온 스레드의 원자성을 보장해주어야 한다. Redis를 통해 lock을 구현하기로 결정한 이유는 다음과 같다.

 

1. 프로젝트는 멀티 스레드 환경에서 구동이 되고 이번 프로젝트는 서버가 하나지만 보통은 서버를 다중화해서 사용한다. 이런 분산환경 속에서 동시성 이슈를 해결하기 위해서는 분산락이 필요하다.

2. 분산락은 Redis를 통해 구현하는게 적절하다고 생각했다.

→ Redis는 분산형 메모리 내 데이터 저장소로 분산환경에서의 lock을 구현하는데 적합→ session에 대해 신경 쓸 필요X

→ 메모리 내에서 데이터를 다루기 때문에 디스크 기반의 데이터베이스보다 빠른 응답시간 제공

→ MySQL의 네임드락은 lock을 걸려있는 지 지속적으로 확인해야하고 이러한 session에 대한 cost가 큼

 

이러한 이유들로 Redis를 통해 lock을 구현하기로 한다. Redis를 활용하기로 결정하였다면 고려해야할 것이 하나 더 남았다. Redis의 Lettuce 라이브러리, Redisson 라이브러리 둘 중 어떤 라이브러리를 활용할 지 결정해야 한다.

 

🖋 Java 의 Redis Client


🔹 Java 의 Redis Client 는 크게 세 가지가 있다. 바로 Jedis, Lettuce, Redisson 이다. 원래는 Jedis 를 많이 사용했으나 여러 가지 단점 (멀티 쓰레드 불안정, Pool 한계 등등..) 과 Lettuce 의 장점 (Netty 기반이라 비동기 지원 가능) 때문에 Lettuce 로 추세가 넘어가고 있었다. 하지만, Lettuce에도 단점이 있기 때문에 Redisson과 장단점을 비교하여 사용해야 한다.

결국 Spring Boot 2.0 부터 Jedis 가 기본 클라이언트에서 deprecated 되고 Lettuce 가 탑재되었다.

 

⚙️ Lettuce


🔹 Lettuce는 Netty기반의 Redis Client이며, 요청을 논블로킹으로 처리하여 높은 성능을 가진다. spring-data-redis 의존성을 추가했다면, 기본적으로 Lettuce 기반의 Redis Client가 제공된다.

 

Lettuce를 사용한다면 SETNX 명령어를 통해서 분산락을 구현할 수 있다. 

⛓ Lettuce 사용시 장단점

Lettuce는 redis 의존성을 추가하는 경우 기본 Redis Client로 제공되므로, 별도의 설정 없이 간단히 구현할 수 있다는 장점이 있다.

 

그러나 구현 방식에서 스핀락을 사용하기 때문에 레디스에 부하를 줄 수 있다는 것이 단점이다. 스핀락 같은경우는 락을 얻기위해 SETNX 명령어로 계속 요청을 보내야하므로 Redis의 부하를 줘서 비효율적이다. 또한, 락의 타임아웃도 구현되어있지않아서 무한루프에 빠질가능성도 많다.

SETNX : SET IF NOT EXIST 특정 key 값이 존재 하지않을 경우 set 하라는 명령어로 특정 키에 대해 value가 없을경우에만 값을 세팅하는 명령어

 

🚧 Redisson


🔹 Redis는 Pub/Sub 기능을 제공해주고 있다. 이를 사용하면 위처럼 스핀락 방식을 사용하지 않고 분산락을 구현할 수 있습니다. 직접 구현할 수도 있지만 Redisson에서는 Pub/Sub 기반의 분산락을 이미 구현하여 제공해주고 있으므로, 이를 사용하면 된다.

⛓ Redisson 사용시 장단점

Redisson에서는 분산락을 이미 구현하여 제공하고 있으므로, 별도의 구현 로직이 필요하지 않다는 장점이 있다. 또한, Pub/Sub 기반으로 동작하여 기존 Lettuce를 통한 스핀락 기반의 분산락에 비해 Redis에 부하를 덜 준다는 장점이 있다. redisson의 tryLock 메소드 같은경우는 락을 획득했을때의 타임아웃과 락 대기 타임아웃도 구현해 놓았다.

 

그러나 별도의 의존성을 추가해야 한다는 것이 단점이라고 볼 수 있다.

 

📜 결론


🔹 위의 내용만 봤을때는 동시성 이슈를 해결하기 위해서는 Redisson을 사용하는 것이 맞아보인다. 하지만, 프로젝트의 요구사항에 따라 라이브러리 선택이 달라질 수 있다. 물론, 어떤 라이브러리를 사용해도 목적은 같다. 분산락을 구현하는 것이다. 이 둘의 차이를 간략히 보면 다음과 같다.

  Lettuce Redisson
분산 락 기능 제공 X(직접 구현 필요) O
라이브러리 크기 상대적으로 작음 상대적으로 큼
구현 방식 spin lock pub-sub 구조

Lettuce는 spin lock 방식으로 구현하므로 lock이 해제되었는 지 주기적으로 retry를 해야하므로 이 부분에서 부하가 커질 수 있다.

 

Redisson은 분산락을 쉽게 사용할 수 있는 기능을 포함해 분산 시스템에서 사용하기 위한 다양한 기능을 제공하고 pub-sub구조이기 때문에 lock이 해제되면 lock 해제를 기다리던 스레드들에게 알려주는 구조로 구현할 수 있다.

 

일반적으로 Redisson을 이용하는 것이 분산락을 구현했을 때 성능이 더 좋다고 알려져있지만 둘의 차이를 이해하고 현재 프로젝트의 요구사항에 따라 선택한 라이브러리를 선택하는 것이 좋아보인다.

 

📸 참조