멀티 서버 환경에서 세션을 유지하는 방법

멀티 서버 환경에서 세션을 유지하는 방법

세션은 서버의 로컬에 종속되어있죠!! 어… 그러면 서버를 여러 대 사용해야 할 때는 어떻게 해야 할까요??

서버는 현재 접속한 사용자가 누군지 구분해서 서비스를 제공해야 합니다. 이를 위해 쿠키, 세션, 토큰 등을 사용하죠

오늘은 그 중에서 세션을 살펴보겠습니다. 그리고 그냥 세션이 아니라 멀티 서버 환경 아래에서 세션을 관리하는 방법에 대해서 살펴보겠습니다.

우선 세션을 사용하지 않는 어플리케이션 하나를 보겠습니다.

Api.java

@Slf4j
@RestController  
@RequiredArgsConstructor  
public class Api {  
  
	Map<String, String> map = new HashMap<>();  
  
	 @GetMapping("/registerName")  
	 public boolean registerName(@RequestParam String name){
     	log.warn("name is already registered");
		if (map.containsKey("name")) {  
		return false;  
	  }  
	  map.put("name", name);
      log.info("name registered, " + name);
	  return true;  
	 }  
  
	 @GetMapping("/getName")  
	 public String getName(){  
		return map.get("name");  
	 }
 }

서버에 접속하면 이름을 등록하고, true를 리턴하고 이미 등록된 이름이 있다면 false를 리턴하는 코드입니다.

두 대의 서버를 올리고 로드 밸런싱에 의해 두 대의 서버에 골고루 접속한다고 가정하고 각자 다른 서버에서 요청을 보내보겠습니다.

우선 서버를 다른 포트로 각각 올리고

java -Dserver.port=8080 -jar redisSession-0.0.1-SNAPSHOT.jar 
java -Dserver.port=8081 -jar redisSession-0.0.1-SNAPSHOT.jar 

8080포트에서 먼저 이름을 등록을 해보겠습니다

등록이 잘되죠 그럼 8081 포트로 다시 한 번이름을 등록해보겠습니다.

이런…

같은 세션이지만 별도의 처리 로직이 존재하지 않아서 또 이름이 등록되는 모습입니다…

로그를 보면 각 서버에 이름이 각각 등록되는 모습을 볼 수 있습니다.

나는 사과! 나는 바나나! ??

그림으로 보면 다음과 같은 상황이죠

처음 사용자는 8080포트로 Server1에 연결되어서 작업을 수행했지만, 추후에 로드 밸런싱에 의해서 다른 서버(Server2)에 연결되었고 해당 서버에는 사용자의 정보가 없으니… 또다시 이름이 등록되는 것이죠

이렇게 되면 사용자는 매우 불편하겠죠.

사용자는 계속 페이지를 이동하면서 서버에 접속할텐데 연결되는 서버가 변경될 때 마다 인증을 새로 해야되니까요 위와 같은 문제는 어떻게 해결할 수 있을까요??

어플리케이션 수준 - Redis

첫 번재 방법으로 레디스를 사용하는 방법이 있습니다.

레디스(Redis)는 Remote Dictionary Server의 약자로서 “키-값” 구조의 비정형 데이터를 저장하고 관리하기 위한 오픈 소스 기반의 비관계형 데이터베이스 관리 시스템(DBMS)이다

위키백과 레디스

레디스는 캐싱 및 글로벌 분산 락을 위해 사용할 수도 있지만, 세션 스토리지(Session Storage)로 사용할 수도 있습니다!!

스프링에서 해당 기능을 사용하기 위해서는 일단 아래의 의존성을 포함시킵니다.

implementation 'org.springframework.session:spring-session-data-redis'

그리고 기존 yml파일에 세션 스토리지로 레디스를 사용하겠다고 알려줍니다

spring:
	session:
    	storage-type: redis

그리고 코드를 아래와 같이 변경해줍니다.

Api.java

@RestController  
@RequiredArgsConstructor  
public class Api {
	@GetMapping("/registerName")  
	public boolean registerName(HttpSession httpSession, @RequestParam String name){  
		 if (httpSession.getAttribute("name") == null) {  
		  httpSession.setAttribute("name", name);  
		  return true;  
		 }  
	 return false;  
}  
  
	@GetMapping("/getName")  
	public String getName(HttpSession httpSession){  
	 return (String)httpSession.getAttribute("name");  
	}
}

위와 같이 변경하면 레디스에 세션 정보가 기록되어 사용자의 정보를 여러 서버에서 공유합니다.

이름 등록은 8080포트로 진행

8081 포트로 접근해서 이름을 다시 등록하려 하면 실패한다!!

다른 포트로 접근해도 이름을 얻을 수 있다.

세션이 레디스에 저장되어있음

로그

위의 로그를 보면 8080포트의 경우 이름 등록에 성공했다는 로그가 뜨지만, 8081포트는 이름 등록에 실패했다는 로그가 뜨는 모습을 볼 수 있습니다

세션정보를 서버 내부에 저장하는 것이 아니라, 글로벌 캐시인 레디스에 저장하기 때문에 어느 서버에 접속을 해도 사용자를 확인할 수 있고 사용자에게 올바른 정보를 리턴할 수 있습니다!!


클라우드 수준 - Sticky Session

그리고 클라우드에서도 세션을 저장할 수 있습니다. 바로 로드 밸런서의 고정 세션을 사용하면 됩니다.(Sticky Session)

고정 세션이란 클라이언트가 서버로 접속을 하면 해당 서버의 접속 정보를 로드 밸런서가 기억을 하고 있다가 동일한 클라이언트가 접속하면 계속 특정 서버로만 요청을 보낼 수 있게 해주는 기능입니다

예를 들어 로드 밸런서가 관리중인 서버가 2개이고, 클라이언트가 2개의 요청을 보내면 위 그림과 같이 부하 분산을 위해 2개의 서버로 트래픽을 나눠서 처리하도록 합니다.

하지만 Sticky Session을 사용하면, 로드 밸런서는 클라이언트가 접속했던 서버의 정보를 기억해서 클라이언트가 이전에 접속했던 서버에 계속 접속하도록 해주는 것이죠!!

위와 같은 방법을 사용하면 레디스와 같은 별도의 툴을 사용할 필요가 업습니다!!

같은 서버에 계속 접속하기 때문에 서버 내부에 저장된 고객 정보만으로도 세션 유지가 가능하기 때문이죠!!

그럼 한 번 제대로 만들어 지는지 확인해보도록 하겠습니다

인스턴스 생성

우선 AWS 에서 인스턴스를 2개 만들어보겠습니다.

각 인스턴스를 생성할 때, 현재 접속중인 서버의 정보를 표기하기 위해 다음과 같은 init script 를 포함시켰습니다.

#!/bin/bash
yum update -y
yum install -y httpd
systemctl start httpd
systemctl enable httpd
echo "<h1>Response From $(hostname -f)</h1>" > /var/www/html/index.html

그럼 2개의 인스턴스에 접속해 보면

짠!

위와 같이 나옵니다!!

클라이언트가 현재 접속중인 서버의 정보를 알려주죠

이를 통해 우리는 Sticky Session이 제대로 활성화 되어 있는지 확인할 수 있습니다.

그럼 ALB를 사용하기 위해 2개의 인스턴스를 가지고 대상 그룹을 만들어 보겠습니다.

대상 그룹 (Target Group) 지정

우선 인스턴스를 지정할 것이므로 위와 같이 선택을 하고

미리 만들어 놓은 인스턴스들은 대상 그룹으로 지정합니다.

그럼 이렇게 2개의 인스턴스가 대상 그룹으로 지정되었습니다!!

ALB 생성 및 등록

위의 대상 그룹을 사용하는 로드 밸런서를 생성해보겠습니다.

일단 http 로 접속을 할 것이므로 ALB를 선택해줍니다

그리고 가용 영역을 선택합니다

위에 ap-northeast 2b는 사실 선택할 필요가 없는 가용영역입니다. 제 인스턴스들은 모두 ap-northeast 2a에 존재하기 때문이죠!

그런 다음 http만 허용하는 보안 그룹을 생성해서 보안 그룹으로 지정해줍니다.

그리고 대상 그룹으로 방금 만들어 놓은 대상 그룹을 지정하고 생성해줍니다.

그럼 이렇게 완성됩니다!

쨘!!

그리고 DNS를 통해 접속을 해보면

두 개의 인스턴스에 돌아가면서 접속을 하는 모습을 볼 수 있습니다!

여러 번 들어오는 트래픽을 로드 밸런서가 적절하게 분배시키는 모습을 볼 수 있습니다.

Sticky Session 활성화

그럼 이제 고정 세션을 활성화해보도록 하겠습니다.

대상 그룹 설정으로 간 다음.

밑으로 내리다 보면 Stickness 라는게 비활성화 되어 있는데 활성화 해보겠습니다.

그러면 이렇게 리프레쉬를 계속 해도

같은 인스턴스로만 접속을 하는 모습을 볼 수 있습니다.

내가 만든 쿠키~

그럼 로드 밸런서는 어떻게 같은 인스턴스로 접속을 유도하는 것일까요??? 바로 쿠키를 사용해서 입니다.

클라이언트는 서버에 접속할 때 ALB로 부터 AWSALB, AWSALBCORS 와 같은 쿠키를 받습니다. 그리고 다시 접속을 시도 할 때 해당 쿠키를 다시 ALB에 넘겨주고, ALB는 쿠키 정보를 기반으로 어느 인스턴스에 다시 붙일지를 결정하죠

이렇게 ALB에서 쿠키를 만들 것인지, Custom으로 쿠키를 만들어서 던져줄 것인지도 정할 수 있습니다. 그리고 세션 유지 시간 또한 정할 수 있죠

주의해야 할 점이 하나 있는데요 바로 쿠키를 Custom으로 만들 경우, 쿠키 이름을 AWSALB와 같이 로드 밸런서가 사용하는 이름으로 사용하면 안된다는 점입니다!!


출처

AWS Certified Solutions Architect Associate 시험합격! - ALB, Sticky Session 백엔드 개발자를 위한 한 번에 끝내는 대용량 데이터 & 트래픽 처리 - Redis Part