우선 게시판 프로젝트의 아키텍처를 살펴보면 아래와 같다.
Nginx에서 라운드 로빈 방식으로 부하를 분산하려는 시도를 했다.
원래는 여러 EC2에 스프링 톰캣 서버를 띄워야 EC2하나가 죽더라도 로드밸런싱의 의미가 있지만
작은 프로젝트였기 때문에 하나의 EC2에 Docker compose로 스프링 톰캣 서버를 2개 구축했다.
현재 로그인 방식은 세션 방식이기 때문에 Spring자체 세션 저장소를 이용했다.
사실! Spring서버에는 자체 세션 저장소는 없다. 범위를 벗어나는 이야기이기때문에 길게 설명하진 않겠지만
톰캣의 SESSION.ser 파일에 저장된다.
하지만 지금 WAS가 2개인 상황에선 세션을 고정 시킬 수 없었다.
해결방법을 고민해보던 중 몇 가지 대안을 찾았다.
- Nginx 로드밸런싱 알고리즘을 ip_hash로 변경
- Tomcat Session Clustering
- Redis Session Clustering
Nginx 라운드로빈 알고리즘을 ip_hash로 변경
Nginx 로드밸런싱 설정을 라운드 로빈이 아닌 ip_hash로 가져가게되면
특정 유저는 특정 WAS에만 세션이 유지된다.(ip를 인덱스로 사용) 따라서 세션을 저장한 WAS가 내려간다면 중요한 작업을 진행 중인 유저가 생산한 데이터가 유실될 위험이 있다.
또한 가장 중요한 균등한 부하분산이 어려워 오히려 성능이 저하될 수 있다.
해당 WAS가 내려가더라도 바로 옆 WAS로 서비스를 지속적으로 제공해야한다고 생각했기 때문에 이 방법은 제외했다.
Tomcat Session Clustering
구글에 세션클러스터링이라고 검색해보면 Redis를 이용한 세션 클러스터링을 정말 많이 사용한다.
하지만 레디스가 아니더라도 Tomcat 서버의 설정으로 Scale Out 상황에서 세션 클러스터링을 구현할 수 있다.
톰캣은 크게 2가지 세션 클러스터링 방식을 지원한다.
아래 레퍼런스를 참고했다.
1. all-to-all session replication
all-to-all 방식은 기본적으로 모든 노드가 각자의 세션을 별도로 저장한다. 그리고 각 노드의 세션 저장소에 변경이 발생하면 멀티캐스팅으로 모든 노드가 변경사항을 반영한다.
서버의 수 만큼 세션을 복제해서 저장하기 때문에 서버가 증가하면 그만큼 네트워크 트래픽은 증가한다. 서버의 수가 증가해 세션을 복제하는 만큼 메모리 사용량도 증가할 수 밖에 없다.
그래서인지 규모가 작은 클러스터에 적용하는 것이 좋다고 언급돼 있다.
2. Primary-Secondary session replication
이 방식은 Primary 서버와 Secondary 서버에만 세션을 복제 저장하는 방식이라 각 서버마다 세션을 복제 저장할 필요는 없어서 메모리가 절약된다.
Primary Secondary 이외의 서버는 JSESSIONID만 복제해서 저장하기 때문이다.
하지만 이외의 서버는 세션정보를 얻기위해서는 Primary, Secondary 서버에 요청을 보내야한다. 이런 부담을 나의 작은 EC2가 짊어지게 할 수는 없고 더 큰 규모의 클러스터 환경을 고려한 설계를 하고 싶다.
Redis Session Clustering
결국 Redis (In-memorry key-value DB) 를 이용해 세션을 고정하기로 결정했다.
Redis는 각 서버마다 세션 저장을 위한 메모리를 확보할 필요 없이 EC2에 한 번 띄우면 다수의 서버가 공유할 수 있게된다.
서버가 추가되더라도 해당 서버에 Redis의 정보만 추가해주면 서버마다 세션 복제/저장을 할 필요가 없어 메모리도 절약된다.
WAS 하나가 내려가더라도 바로 옆에 있는 WAS로 트래픽을 끌어올 수 있고, 세션도 Redis에 따로 유지하기 때문에 문제가 발생하지 않는다.
특히 Redis는 스프링과 결합해 사용하기 매우 쉽다.
Gradle 에
위 의존성을 추가하고
메인 App 클래스에
위 어노테이션을 추가하고 사용하면 된다.
당연히 Docker Compose 구성 시 Redis 서버의 정보를 알려주고 EC2 보안 그룹에서 Redis 포트를 개방해야 사용할 수 있다.
다음 고민
이번 프로젝트에서는 EC2하나를 사용했다.
하지만 이런 방식으로는 큰 규모의 서비스에서 장애를 대응할 수 없다.
아무리 로드밸런싱으로 부하를 분산했다 한들 WAS 2개가 올라가있는 EC2가 내려가면 특급 장애가 발생하기 쉽다.
게다가 Redis도 WAS가 올라가 있는 EC2위에 같이 올라가 있기 때문에 세션도 모두 잃는다.
로드 밸런싱을 하더라도 여러 EC2에 부하를 분산해야 하나의 EC2가 죽더라도 정상적으로 서비스가 가능하다.
또한 Redis도 특정 EC2에 종속된다면 안된다.
다음 프로젝트에서는 더 큰 규모로 Sacle Out 환경을 구성해볼 생각이다.
'Spring' 카테고리의 다른 글
[Spring Security JWT 로그인 (1)] 어떤 로그인 방식이 좋을까 (1) (0) | 2024.03.11 |
---|---|
[Spring Security JWT 로그인 (2)] JWT가 탈취된다면 어떻게 대응해야 할까 (0) | 2024.03.10 |
S3를 이용한 사용자의 고아 이미지 처리 (1) | 2023.12.02 |
코드의 중복과 Null 처리 (0) | 2023.09.25 |
@Query 어노테이션으로 직접 작성한 JPQL은 AUTO Flush 할까? (0) | 2023.07.20 |