Spring

Spring과 Redis를 연동하여 Session Clustering

민철킹 2021. 11. 19. 19:17

안녕하세요!😄

 

현재 진행 중인 프로젝트의 백엔드 서버에 적용된 Session Clustering에 대해 이야기 해보려 합니다.

여기서는 Load Balancing, Reverse Proxy와 같은 개념이 등장합니다.

이젠에 작성된 해당 글을 참조해주세요!


세션 클러스터링이란?

Session Clustering이란 말 그대로 SessionClustering 즉, 군집화한다고 생각하면 이해가 빠를 것 같습니다.

우리가 웹사이트에 로그인하는 과정을 생각해봅시다.(쿠키-세션 방식을 이야기합니다.)

image

먼저, 클라이언트는 서버로 자신의 인증 정보를 전달합니다.

서버는 인증 정보를 검증한 후 인증된 사용자라면 해당 사용자의 정보를 서버 내 Session에 저장하게 됩니다.

서버가 한 대만 존재한다면 사용자의 요청은 항상 동일한 서버로만 갈 것이기 때문에 Session이 만료되지 않는 한 별도의 로그인 요청 없이 인증을 통과할 수 있습니다.

하지만, 현재 저희 서버와 같이 두 대의 서버가 존재하고 Load Balancing을 사용해 사용자의 요청을 분산하여 전달하고 있다면 어떨까요?😯

image

사용자의 요청이 로그인한(자신의 세션 정보가 저장된) 서버로 간다는 보장이 없습니다.

최악의 경우에는 페이지를 이동할 때마다 로그인 정보를 요구할 수도 있는것이죠..😱😱

이러한 대참사를 막기 위해 사용하는 것이 바로 Session Clustering입니다.

여러 서버에 대한 Session을 공유하게 하여 사용자의 요청이 어느 서버로 가든 Session 정보가 존재한다면 인증 시켜줍니다.


적용해보기

RedisKey-Value 구조로써 매우 빠르고 가볍습니다.

또한, 저희가 다루는 Spring Boot와 매우 잘 연동되어 있기 때문에 코드 몇 줄만 추가하는 것으로 간단하게 Session Clustering을 적용할 수 있습니다.

레디스에 대한 자세한 정보는 아래글을 참고해주세요.

레디스

 

먼저, 의존성을 추가해야합니다.

image


org.springframework.boot:spring-boot-starter-data-redis

spring-boot-starter-data-redisspring 애플리케이션에서 redis에 접근할 수 있게 해주는 라이브러리입니다.

공식 문서에서는 이를 아래와 같이 언급합니다.

더 큰 Spring Data 제품군의 일부인 Spring Data Redis는 Spring 애플리케이션에서 Redis에 대한 쉬운 구성 및 액세스를 제공합니다. 상점과 상호 작용하기 위한 저수준 및 고수준 추상화를 모두 제공하여 사용자를 인프라 문제로부터 해방시킵니다.

 

 

의존성을 주입했다면 다음은 ymlredis 설정을 추가해야합니다.

spring:
  redis:
    host: 호스트
    password: 비밀번호(없다면 생략해도됨)
    port: 6379(6379가 redis 기본포트)

 

위와 같이 설정을 했다면 ConnectionFactorySpring Bean으로 등록하기위해 Configuration 클래스를 만들어

야합니다.

@Configuration
public class RedisConfiguration {

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

    @Value("${spring.redis.password}")
    private String password;

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

    @Bean
    public LettuceConnectionFactory lettuceConnectionFactory() {
        final RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration(host, port);
        redisStandaloneConfiguration.setPassword(RedisPassword.of(password));
        return new LettuceConnectionFactory(redisStandaloneConfiguration);
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        final RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(lettuceConnectionFactory());
        template.setDefaultSerializer(new StringRedisSerializer());
        return template;
    }
}

 

 

yml에 등록한 설정을 @Value를 사용하여 가져와 RedisStandaloneConfiguration을 생성해주는데

image

RedisStandaloneConfiguration의 생성자는 위 3개(기본 생성자, host, host+port)만 존재하므로

hostport를 인수로 넘겨 생성한 후 setPassword를 사용하였습니다.

이를 통해 최종적으로 ConnectionFactory를 생성하여 반환합니다.

 



Lettuce외에도 redis client로써 Jedis가 존재하지만

현 시점에서는, Jedis에 많은 단점이 존재(멀티 쓰레드 불안정, Pool 한계)하여 deprecated되고 Lettuce를 권장하고 있습니다.

이에 대해 궁금하다면 향로님의 블로그글을 참조해주세요! Lettuce Vs Jedis



위 설정으로 인해 우리는 redis에 접근할 수 있게 되었습니다.

 

이제 저장을 해야겠죠?😊

 

redis에 저장하기 위한 방법은 두 가지가 존재합니다.

  • RedisRepository
  • RedisTemplate

 

RedisRepository는 도메인 객체(Entity)를 Redis Hash로 저장할 수 있습니다.

 

RedisTemplateRedisRepository처럼 도메인 객체를 저장할 수도 원하는 타입(String, Set, Hash 등)을 저장할 수도 있습니다.

 

더 유연한 것이죠!

 

 

저희는 현재 도메인 객체를 저장하는 용도가 아니라 Session 저장소로써 redis를 사용하고자 하므로 RedisTemplate을 사용하면 될 것 같네요.

 

다시 위의 코드를 살펴봅시다.

    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        final RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(lettuceConnectionFactory());
        template.setDefaultSerializer(new StringRedisSerializer());
        return template;
    }

저는 String 타입의 key와 Object 타입의 value를 템플릿으로 지정하였습니다.

 

그 이유는 저희가 spring security를 통해 spring session을 사용하고 있기 때문인데요.

 

 

결과적으로 클라이언트가 로그인에 성공했을 시 redis에 저장될 값은 아래와 같습니다.

  • spring:session:sessions:expires:(session id) ==> 해당 세션의 만료 키
    • 저장될 값 타입 : String
  • spring:session:expirations:(expire time) ==> expire time에 삭제될 세션 정보(만료되면 해당하는 세션 정보 삭제)
    • 저장될 값 타입 : Set
  • spring:session:sessions:(session id) ==> 세션 생성 시간, 마지막 세션 조회 시간, 최대 타임아웃, 해당 세션에 저장한 데이터
    • 저장될 값 타입 : Hash

이렇게 저장될 값 타입이 각자 다르기 때문에 Object로 지정했습니다.

 

 

다음으로 template에 위에서 만든 ConnectionFactory를 넣어주고, DefaultSerializer를 통해 key

value에 사용할 역/직렬화를 StringRedisSerializer로 사용하도록 하였습니다.

 

 

여기까지가 기본 설정이지만, 저희는 한 단계가 더 남았죠!

 


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

앞서 얘기했듯이 저희는 spring session을 사용하고 있기 때문에 spring sessionredis에 저장될 수 있도록 설정을 해줘야 합니다.

 

위에서는 spring session을 저장하기 위한 사전 작업이었던 것이죠!

 

이 또한, Spring의 고도화된 추상화 덕분에 설정 몇 줄을 추가하는 것으로 끝이 납니다.

spring:
  session:
    store-type: redis

끝입니다! 😉😉 (진짜에요..)

 

 

이제부터 우리가 Spring Bean으로 등록한 ConnectionFactory를 사용하여 redis에 접근하며 지정된 templatespring session이 저장될 것입니다.

 


 

image

저희 서버에 성공적으로 배포되어 세션 클러스터링이 적용된 모습입니다.

 

이제부터는 별도의 외부 서버에 존재하는 redis에 세션이 저장될 것이며 두 대의 내부 서버는 이 세션을 공유하게 되었습니다! 😀

반응형

'Spring' 카테고리의 다른 글

yml에서 List Object 사용법  (0) 2022.04.29
@ModelAttribute와 @RequestBody 그리고 Setter  (4) 2021.11.24
패키지 구조 설계  (0) 2021.07.17
Thymeleaf와 Spring  (0) 2021.07.04
Spring MVC의 HiddenMethod 기능 사용하기  (0) 2021.06.21