개발자 이야기/Spring

Redis로 Session Store 적용하기

oneny 2023. 11. 28.
728x90
반응형

Redis로 Session Store 적용하기

 

로그인 방식에 대한 고찰 : JWT vs Session

JWT vs Session 보통 로그인 방식을 어떤 방식으로 구현했는지 보면 JWT를 사용한 토큰 기반 인증방식 또는 Session을 사용한 세션 기반 인증방식이 있다. 그리고 주위 개발자분들께 회사에서 어떠한

m-kang97.tistory.com

위 게시글에서 JWT 방식과 Session 방식 중 Session 방식을 선택했다. 세션은 간단히 말하면 서비스를 사용하는 클라이언트의 상태 정보를 의미하고, 어플리케이션은 현재 서비스에 로그인되어 있는 클라이언트가 누구인지, 그 클라이언트가 어떤 활동을 하고 있는지 저장하고 있으며, 유저가 서비스를 떠나면 세션 스토어에서 유저의 정보는 삭제한다. 하지만 Session 방식을 선택했을 때의 확장성 문제를 생기고, Redis를 Session Store로 사용하여 해결하기로 했다.

 

세션 스토어로서의 레디스

유저가 로그인해 있는 동안에는 세션의 데이터를 끊임없이 읽고 쓰게 되므로 빠른 응답 속도는 필수적이다. 위 그림처럼 레디스를 세션 스토어로 사용해 서버, 데이터베이스와 분리시켜 놓은 뒤 여러 서버에서 세션 스토어를 바라보도록 구성한다면 세션의 확장성 문제를 해결할 수 있다.

 

RDB가 아닌 레디스를 사용하는 이유

관계형 데이터베이스를 사용하는 경우에는 서비스가 커져 유저가 많아질수록 디스크에 접근해 데이터를 검색하는데 있어 서비스 전반적인 응답 속도를 저하시키는 요인이 될 수 있다. 하지만 레디스는 모든 데이터를 메모리에 저장하는 인메모리 데이터 저장소이기 때문에 데이터를 검색하고 반환하는 것이 관계형 데이터베이스보다 훨씬 빠르고 접근하기도 간편하므로 데이터를 가볍게 저장할 수 있다. 레디스에서는 평균 읽기 및 쓰기 작업 속도가 1ms 미만이며, 초당 수백만 건의 작업이 가능함을 의미한다. 또한 레디스는 키-값 형식으로 사용이 간단하며 string, set, hash 등의 자료 구조를 제공하기 때문에 사용자 데이터를 저장하기에 용이하다.

 

세션 스토어를 사용할 때의 데이터 흐름

일반적인 세션 스토어에서는 유저가 로그인하면 세션 데이터는 세션 스토어에 저장된다. 유저가 로그인해 있는 동안, 즉 세션이 활성화되어 있는 동아에는 어플리케이션은 유저의 데이터를 데이터베이스가 아닌 세션 스토어에만 저장한다. 예를 들어, 유저가 최근 봤던 아이템, 혹은 장바구니에 담긴 아이템은 세션 스토어에만 담겨 있다. 유저가 로그아웃할 때 세션은 종료되며 이때 데이터의 종류에 따라 데이터베이스에 저장해 영구적으로 보관할 것인지, 삭제할 것인지가 결정된다. 최근에 봤던 상품 리스트는 휘발시켜도 되지만 장바구니에 담아 놨던 상품들은 데이터베이스에 저장시켜 다음에 로그인했을 때 확인할 수 있도록 할 수 있다.

 

Spring Session을 통해 Redis 적용하기

이제 실제 프로젝트에 적용해보자.

 

의존성 추가

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

위 의존성은 Sprinb Boot 기반의 어플리케이션에서 Redis를 사용하기 위한 의존성으로, Redis와 관련된 여러 구성 및 설정이 자동으로 활성화되며, Spring Data Redis 라이브러리와의 통합을 쉽게 구현할 수 있다. Spring Data Redis는 Redis와 상호 작용할 때 사용되는 데이터 액세스 기능을 제공한다.

아래 의존성은 Spring 기반의 어플리케이션에서 세션 관리를 위한 Spring Session을 Redis를 이용하여 사용하기 위한 라이브러리이다. Spring Session은 세션 데이터를 유지하고 관리하기 위한 추상화를 제공하며, 다양한 스토어를 사용할 수 있다. 여기서는 Redis를 세션 스토어로 사용하도록 설정했다.

 

application.yml

Spring Boot 3.0부터는 spring.session.store-type을 통해 Spring Session에 대한 store type을 명시적으로 구성하는 것은 더 이상 지원하지 않고 classpath에서 여러 session store repository 구현이 감지되는 경우 auto-configure 해야 하는 SessionRepository를 결정하는데 fixed order가 사용된다.

 

RedisSessionConfig.class

@Configuration
@EnableRedisHttpSession
public class RedisSessionConfig {

    @Value("${spring.data.redis.host}")
    private String redisSessionHost;

    @Value("${spring.data.redis.port}")
    private int redisSessionPort;

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory redisConnectionFactory) {
       RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
       ObjectMapper objectMapper = new ObjectMapper();
       redisTemplate.setConnectionFactory(redisConnectionFactory);
       redisTemplate.setKeySerializer(new StringRedisSerializer());
       redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer(objectMapper));
       return redisTemplate;
    }

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
       RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
       redisStandaloneConfiguration.setHostName(redisSessionHost);
       redisStandaloneConfiguration.setPort(redisSessionPort);
       redisStandaloneConfiguration.setPassword("1234567890");
       return new LettuceConnectionFactory(redisStandaloneConfiguration);
    }
}

위 코드를 통해 설정한 내용들을 하나씩 살펴보자.

 

@EnableRedisHttpSession

@EnableRedisHttpSession을 통해 Spring Session을 설정하고 Redis를 세션 저장소로 사용하도록 만든다. 위 주석에 따르면 @Configuration이 있는 클래스에 해당 어노테이션을 추가하여 springSessionRepositoryFilter라는 이름의 SessionRepositoryFilter를 빈으로 생성하고, 이 빈은 RedisSessionRepository를 사용하여 세션을 관리한다.

  • SessionRepositoryFilter: Spring Session에서 제공하는 필터로, HTTP 요청을 처리하면서 세션 관리를 수행하는 역할을 한다. 이 필터는 세션 데이터를 지정된 SessionRepository 구현체에 저장하고 검색한다.
  • RedisSessionRepository: Spring Session에서 제공하는 인터페이스인 SessionRepository를 구현한 구현체 중 하나로, 세션 데이터를 Redis에 저장하고 관리하는 역할을 수행하는데 도메인 객체(Entity)를 Redis hash로 저장할 수 있다.

 

RedisConnectionFactory

RedisConnectionFactory는 Spring Data Redis에서 제공하는 인터페이스로, Redis 데이터베이스와의 연결을 설정하고 관리하는 역할을 한다. 이 인터페이스를 구현하는 클래스는 Redis 서버와의 실제 연결을 설정하고 필요한 기능을 제공한다. 일반적으로 Spring Boot 어플리케이션에서는 LettuceConnectionFactory나 JedisConnectionFactory와 같은 구현체를 사용한다.

 

  TPS Redis CPU Redis Connection 응답 속도
jedis no connection pool 31,000 20% 35 100ms
jedis use connection pool 55,000 69.5% 515 50ms
lettuce 100,000 7% 6 7.5ms

Lettuce, Jedis 모두 유명한 Redis Client 오픈소스이다. 현재 Spring Boot 2.0 부터는 Redis Client default로 Lettuce를 사용하고 있다. Jedis의 경우, 멀티 스레드 환경에서 하나의 Jedis 인스턴스를 공유하고 싶을 때, 스레드 안전성을 보장하지 않고 Pooling 연결 방식이다. Lettuce는 netty 기반으로 멀티 스레드 환경에서 상태를 가지고 공유될 수 있다. 따라서, 멀티 스레드 어플리케이션이 Lettuce와 상호 작용하는 동시성을 가진 스레드의 개수와 상관없이 하나의 연결만 사용하면 된다.

위 내용은 이동욱님의 기술 블로그(Jedis 보다 Lettuce를 쓰자)를 가져온 내용인데 Lettuce가 압도적인 성능 차이를 가지는 것을 확인할 수 있다.

 

RedisTemplate

RedisTemplate은 Spring Data Redis에서 제공하는 클래스로, Redis 데이터베이스와 상호 작용하는 편리한 인터페이스를 제공하여 Redis 연산을 수행할 수 있도록 도와준다.

  • redisTemplate.setConnectionFactory(redisConnectionFactory): RedisTemplate이 사용할 RedisConnectionFactory를 설정한다.
  • redisTemplate.setKeySerializer(new StringRedisSerializer()): Redis에 저장되는 키는 문자열이기 때문에  StringRedisSerializer를 설정하면 문자열 직렬화를 사용한다.
    • StringRedisSerializer를 사용하면 Class 타입을 지정할 필요가 없으며 스레드 간의 문제가 발생하지 않는다는 장점이 있다.
  • redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer()):
    • GenericJackson2JsonRedisSerializer는 객체의 클래스 지정 없이 모든 Class Type을 JSON 형태로 저장할 수 있는 Serializer이다. 즉, Class Type에 상관 없이 모든 객체를 직렬화해준다는 장점을 가지고 있지만 Object의 class 및 package까지 전부 함께 저장하게 되어 다른 프로젝트에서 Redis에 저장되어 있는 값을 사용하려면 package까지 일치시켜줘야 한다는 단점이 있다.

이 설정을 통해 키는 기본적으로 문자열로 저장되기 때문에 추가적인 직렬화 설정이 필요하지 않고, StringRedisSerializer를 사용하여 문자열 키를 사용하고 있다. 값은 여러 타입의 객체를 저장할 수 있으므로 JSON 형식으로 직렬화하여 저장할 수 있도록 GenericJackson2JsonRedisSerializer를 사용했다. 이를 통해 Redis에 저장된 값들은 JSON 형태로 표현되며, 필요할 떄는 다시 역직렬화하여 사용할 수 있다.

 

결과 확인

로그인을 요청하면 Redis에 저장되는 것을 확인할 수 있다.

 

maxInactiveIntervalInSeconds 기본값은 1800이다. 30분이 적용되어 ttl 명령어를 통해 남은 시간을 확인할 수 있고, spring:session:sessions에는 다음과 같이 저장되어 있다.

  • creationTime: 세션 생성시간
  • lastAccessedTime: 마지막 세션 조회 시간
  • sessionAttr: 세션에 저장한 데이터
  • maxInactiveInterval: 만료시간

 

만약 maxInactiveIntervalInSeconds에 설정된 값이 지나면 위처럼 ttl 명령어 실행 시 key가 삭제되었으면 -2를 리턴하기 때문에 해당 세션 정보가 삭제되는 것을 확인할 수 있다.

 

출처

개발자를 위한 레디스: 효율적인 개발을 위한 인메모리 데이터베이스 사용 가이드

Spring Data Redis's Property-Based Configuration

Redis를 로그인 Session Storage로 이용하기

Jedis 보다 Lettuce를 쓰자

Spring 분산 환경에서 세션 관리하기(Redis Session Clustering)

Spring Redis 역직렬화 삽질기 (feat. RedisSerializer)

728x90
반응형

'개발자 이야기 > Spring' 카테고리의 다른 글

로그인 방식에 대한 고찰 : JWT vs Session  (0) 2023.11.25

댓글

추천 글