본문 바로가기
java

redis를 이용한 tomcat 세션 클러스터링

by misankim 2023. 3. 30.

AWS 샘플 아키텍쳐에 대해 설명할 때, 고가용성 및 부하분산 구성을 위해 WAS 이중화가 필요하며, 다중화된 WAS를 운영하기 위해서는 엘라스틱캐시(redis)를 이용한 세션 클러스터링의 필요성을 자주 언급하곤 합니다. 하지만 구체적으로 어떻게 세션 클러스터링을 하는지 방법을 몰라 한번 해본 김에 기술 공유차 작성합니다.

1. 세션과 쿠키의 관계

먼저 세션 클러스터링을 구성하려면 세션이 어떤 역할을 하는지 알아야할 것 같습니다. 다들 아시겠지만 정리를 겸해서 작성해봅니다.

HTTP는 stateless(상태 비 저장) 프로토콜입니다. 때문에 이전에 통신한 내용으로 인해 다음 통신에 영향을 주진 않습니다. 그저 요청에 따른 응답을 하는 것이죠. 이 특징은 정적 페이지(html, 이미지 등)에 대한 요청/응답 시의 특징과 같습니다.

정적 페이지와는 다르게 동적 페이지는 입력한 값에 따른 연산(혹은 DB를 연동한 데이터 처리)를 수행하며, 이런 동적 페이지를 통해 로그인/회원가입 등의 기능을 구현합니다. 동적 페이지 구성 시 기존 서버와 통신한 내용을 stateful(상태 저장)하게 유지시켜야할 필요가 있습니다.

서버는 Memory 혹은 파일의 형태로 세션데이터를 저장하고, 어떤 클라이언트가 해당 세션의 주인인지 식별하기 위해 클라이언트에게 Set-Cookie 헤더를 전송하여 클라이언트 브라우저에 세션ID 등을 Cookie로 저장하도록 합니다. 정리하면 아래와 같습니다.

session = 서버에 저장되는 정보
cookie = 클라이언트(브라우저)에 저장되는 세션에 대한 정보

클라이언트에서 서버로 최초 통신이 이뤄지는 경우 앞서 설명한 것과 같이 서버는 클라이언트로부터 전송된 cookie 정보가 없는 것을 확인하고, Set-Cookie 헤더를 보냅니다. 아래는 HTTP 요청 헤더와 응답 헤더를 표시하도록 구성한 페이지입니다.

클라이언트는 다음 요청부터 브라우저에 저장한 쿠키를 헤더에 포함하여 서버로 전송하고
서버에서는 해당 쿠키에 매칭되는 세션이 만료(timeout)되지 않았다면 다시 Set-Cookie 헤더를 전송하지는 않습니다.

2. WAS(tomcat)가 다수 구성된 경우 발생하는 세션 문제

위의 스크린샷은 tomcat이 한 대일 때의 경우였습니다. 이번에는 haproxy를 이용하여 소프트웨어적인 로드밸런서를 구성하고 그 아래 tomcat 서버를 2대 구성했습니다.

위와 같은 경우 테스트 도메인인 http://www.premisan.shop 에 접속 시 아래와 같이 매번 페이지가 새로고침될 때마다 접속하는 서버가 달라지기 때문에, tomcat1 서버에서 전달받은 쿠키를 tomcat2 서버로 전송하면 유효하지 않은(혹은 만료된) 쿠키로 인식하여 새로운 쿠키를 Set-Cookie 헤더를 통해 전송합니다. 다시 tomcat2 서버로부터 전달받은 쿠키를 tomcat1 서버로 전송하면 동일하게 유효하지 않은 쿠키로 인식하여 Set-Cookie 헤더를 전송합니다. 이러한 현상이 반복하여 발생하고, 마치 아이디/비밀번호를 입력하고 로그인 버튼을 클릭하여 페이지가 새로고침되면 로그인이 풀려있는 상황이 무한 반복됩니다.

 

 

이런 경우 haproxy에 세션 지속성 설정을 추가하여 매번 동일한 서버로 접속하도록 할 수도 있지만, 이번 글에서는 redis를 이용하여 세션 클러스터링을 구성해보려고 합니다.

3. redis를 이용한 tomcat 세션 클러스터링

redis는 인메모리(메모리에 데이터를 저장) 방식의 키-값 데이터 저장소로, No-SQL 데이터베이스로 분류하기도 합니다. redis를 tomcat 세션 저장소로 활용하기 위한 tomcat에서 사용 가능한 redis 클라이언트는 대표적으로 jedis, lettuce, redisson이 있습니다. jedis, lettuce의 경우 sping 프레임워크(java 개발에 필요한 라이브러리 및 템플릿을 모아놓은 환경 혹은 도구)에서 사용 가능한 redis 클라이언트로, jedis, lettuce 를 사용하려면 sping 사용방법도 알아야하는데 java도 제대로 모르는 상태로 하기엔 어려운 점이 있어서 상대적으로 간단해보이는 redisson을 이용하여 구성해보았습니다.

1) redis 구성

redis는 별도의 서버로 구성해도 되지만 저는 docker 에 컨테이너 형태로 올렸습니다.

docker run -d -p 6379:6379 --name redis redis

만약 서버로 올리고 싶으면 아래와 같이 설치합니다.

yum install -y gcc jemalloc-devel varnish kernel-devel

wget http://download.redis.io/redis-stable.tar.gz 
tar xvzf redis-stable.tar.gz
cd redis-stable

cd deps
make hiredis jemalloc linenoise lua
cd ..

make
make install

redis 서버가 정상적으로 실행되었다면 (도커 컨테이너의 경우 "docker exec -it redis bash" 명령어 실행 후) redis-cli 명령어로 쉘을 열어 "ping"이라고 입력했을 때, 아래와 같이 "PONG"이라는 출력이 있다면 정상적으로 실행된 것입니다.

2) tomcat 서버에 라이브러리 추가 및 설정 파일 구성

2대의 톰캣 서버에 동일하게 아래 작업을 수행합니다.

wget "https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-all&v=3.11.2&e=jar" -O redisson-all-3.11.2.jar

wget "https://repository.sonatype.org/service/local/artifact/maven/redirect?r=central-proxy&g=org.redisson&a=redisson-tomcat-8&v=3.11.2&e=jar" -O redisson-tomcat-8-3.11.2.jar

# tomcat이 설치된 경로가 /usr/local/tomcat 일 경우의 예
mv redisson-all-3.11.2.jar redisson-tomcat-8-3.11.2.jar /usr/local/tomcat/lib

vim /usr/local/tomcat/conf/context.xml

# <Context>~</Context> 사이에 아래 내용 추가

<Manager className="org.redisson.tomcat.RedissonSessionManager"
       configPath="${catalina.base}/conf/redisson.conf"
       readMode="REDIS" updateMode="DEFAULT" broadcastSessionEvents="false"/>

vim /usr/local/tomcat/conf/redisson.conf # 기존에 없는 파일이라 새로 생성함, address 값을 구성한 redis 서버의 주소로 수정 후 redis에 암호를 설정한 경우 password 값 수정(암호 설정 없으면 null)

{
  "singleServerConfig": {
    "idleConnectionTimeout": 10000,
    "pingTimeout": 1000,
    "connectTimeout": 10000,
    "timeout": 3000,
    "retryAttempts": 3,
    "retryInterval": 1500,
    "password": null,
    "subscriptionsPerConnection": 5,
    "clientName": null,
    "address": "redis://<redis 서버의 아이피주소 혹은 도메인>:6379",
    "subscriptionConnectionMinimumIdleSize": 1,
    "subscriptionConnectionPoolSize": 50,
    "connectionMinimumIdleSize": 32,
    "connectionPoolSize": 64,
    "database": 0,
    "dnsMonitoringInterval": 5000
  },
  "threads": 0,
  "nettyThreads": 0,
  "codec": {
    "class": "org.redisson.codec.JsonJacksonCodec"
  },
  "transportMode": "NIO"
}

tomcat을 재시작합니다. 그리고 다시 테스트 도메인으로 접속해봅니다. 초기 접속 시 아래와 같이 새로운 쿠키값을 전달받습니다.

redis-cli에서 "keys *" 명령어를 통해 현재 redis에 저장된 모든 키를 불러옵니다. C92BB186738D1CD9BF719AEC99CB7956 세션 아이디를 확인할 수 있습니다.

여러번 페이지를 새로고침하여 기존 저장한 쿠키 값(C92BB186738D1CD9BF719AEC99CB7956)으로 양쪽 서버 모두 통신이 가능한지 확인합니다.

만약 쿠키값이 유효하지 않거나, 만료되었다면 tomcat 서버는 새로운 쿠키값을 Set-Cookie 헤더를 통해 전송하려고 할 것입니다. 아래와 같이 양쪽 서버 모두 동일한 쿠키로 통신이 가능(세션이 유지)되는 것을 알 수 있습니다.

 

4. 마치며

원래는 로그인 등 세션의 지속을 확인할 수 있는 페이지까지 구성해보려고 했으나, jsp 기반의 웹보드가 별로 없거나 오래되었고, 구성했으나 500 에러 발생하는 부분이 있어 페이지 구성까지는 못해봤습니다. 그래도 톰캣 서버에 redis를 연동하여 어떻게 세션 클러스터링을 구성하는지 간략하게나마 테스트해볼 수 있어 이해하는 데에 도움이 되었습니다.

'java' 카테고리의 다른 글

JAVA MySQL Connector replication 을 통해 쓰기/읽기 쿼리 분산  (0) 2023.03.25