카샤의 만개시기

마이크로서비스 시작하기 (4편) - 세션 클러스터링 본문

Java/MSA

마이크로서비스 시작하기 (4편) - 세션 클러스터링

SKaSha 2019. 6. 19. 16:39

HTTP는 기본적으로 Connectionless하고 Stateless한 프로토콜이지만 로그인 상태 유지 등을 위한 목적으로 Session 혹은 Cookie를 이용하여 Stateful하게 사용합니다.
그런데 하나의 WAS에서 Session을 이용하여 서비스를 제공하는 서버에서 사용자가 증가해 서버를 증설하게 되었을때 각 WAS는 개별적으로 세션을 관리하기 때문에 세션이 공유 되지 않습니다.
이는 로드벨런서를 통해 로그인 요청이 1번 WAS에 들어와 로그인 처리를 하였지만 권한이 필요한 다른 요청이 2번 WAS에 들어왔을때 해당 WAS의 세션에는 권한이 인가되어 있지 않아 올바르지 않게 작동한다는 것을 의미합니다.
이 문제를 해결하기 위해서는 두 서버간 세션이 공유되어야 하고 이를 세션 클러스터링이라 합니다.

이를 가장 간단히 구현 할 수 있는 방법은 WAS의 설정 파일에 공유되어야 할 IP와 Port정보를 등록하는 것입니다.
(톰캣 클러스터링 설정 방법 : http://tomcat.apache.org/tomcat-9.0-doc/cluster-howto.html)
하지만 서비스가 Scale Out이 될 때마다 IP와 Port 정보를 지정해주어야 한다는 것은 굉장히 번거로운 작업입니다.
특히나 클라우드를 사용할 경우 Auto Scale을 위해서는 자동으로 세션 클러스터링이 지원 되야합니다.
Spring Session은 이를 위한 유용한 해결책입니다.

스프링 세션으로 HTTP 세션 다루기

implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.springframework.session:spring-session-data-redis'
implementation 'biz.paluch.redis:lettuce:4.5.0.Final'   // redis client lettuce
spring:
  redis:
    port: 6379
    host: 127.0.0.1
    lettuce:
      pool:
        max-active: 10
        max-idle: 10
        min-idle: 2
@EnableRedisHttpSession
public class RedisConfig {
    /**
     springboot 2.0이상부터는 auto-configuration으로 redisConnectionFactory, RedisTemplate, StringTemplate빈들이
      자동으로 생성되기 때문에 굳이 Configuration을 만들지 않아도 즉시 사용가능하다.
     */
    @Bean
    public LettuceConnectionFactory connectionFactory() {
        return new LettuceConnectionFactory();
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory());
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        return redisTemplate;
    }
}

@EnableRedisHttpSession 어노테이션이 SpringSessionRepositoryFilter(Filter의 구현체) 라는 빈을 생성하여 기존에 사용하던 WAS의 HttpSession 구현체에서 Spring Session의 HttpSession 구현체로 바꾸게 됩니다.

Redis 실행과 테스트

Redis를 설치 및 실행하고 redis-cli를 통해 터미널로 접속한 뒤 테스트를 진행하겠습니다.

127.0.0.1:6379> keys *
(empty list or set)

현재 세션은 아무것도 등록이 되어있지 않기 때문에 output이 비어있는것을 알 수 있습니다.
아래와 같이 컨트롤러를 작성하고 /test/redis에 접속하면 세션이 새로 등록되게 되고 이를 터미널을 통해 조회해 볼수 있습니다.

@Value("${CF_INSTANCE_IP:127.0.0.1}")
private String ip;

@GetMapping("/test/redis")
public Map redisTest(HttpSession session){
    UUID uid = Optional.ofNullable(UUID.class.cast(session.getAttribute("uid")))
            .orElse(UUID.randomUUID());
    session.setAttribute("uid", uid);

    Map m = new HashMap<>();
    m.put("instance_ip", this.ip);
    m.put("uuid", uid.toString());
    return m;
}    
127.0.0.1:6379> keys *
1) "spring:session:expirations:1560917700000"
2) "spring:session:index:org.springframework.session.FindByIndexNameSessionRepository.PRINCIPAL_NAME_INDEX_NAME:user"
3) "spring:session:sessions:expires:8046782b-8bfc-4349-bef2-fff7db37b373"
4) "spring:session:sessions:8046782b-8bfc-4349-bef2-fff7db37b373"
5) "spring:session:expirations:1560918300000"

Redis를 통하여 세션을 공유하기 때문에 여러번 접속하거나 다중화 된 다른 어플리케이션 서버를 통하여 요청을 하더라도 같은 결과를 반환하는것을 알 수 있습니다.
터미널에서 flushall을 입력하게 되면 모든 데이터가 지워지는 것을 알수 있고
/test/redis에 다시 접속하게 되면 새로운 세션이 등록되는 것 역시 알수 있습니다.

NEXT

마이크로서비스 시작하기 (5편) - 분산 트랜잭션

Comments