서버 분산 처리 환경에서 데이터의 불일치 문제 - 세션 클러스터링(Redis로 별도 session storage 만들기)

devdo·2022년 5월 30일
1

Project

목록 보기
6/11
post-thumbnail

서버 분산시 문제점 - 데이터 정합성 불일치

기본적으로 많은 서버시스템의 구성이 Scale-out 방식으로 이루어 집니다.

이런 웹서버를 구축할 때 서버 분산 시스템으로 Scale-out방식을 선택하는데 서버를 분산•확장할 시에는 몆 가지 문제점이 생길 수 있습니다.

그 중의 하나가 네트워크로부터 온 다수 유저들의 request들이 동시에 올 수 있어 가져온 데이터의 정합성이 불일치 되는 문제입니다.

예를 들어, 사용자가 Session 로그인할 시 로그인했음에도
세션이 저장되지 않는 서버에 요청이 온다면 사용자는 Session 정보를 받지 못하겠지요?


별도의 Redis Cache서버 외 Session서버 만들기

이를 해결하기 위한 redis 를 사용하였습니다.
별도의 cache 서버는 다음 블로그를 참조 해주세요
https://velog.io/@mooh2jj/Redis-캐싱적용해서-read-작업-성능-개선하기

세션을 관리하는 별도의 session storage로 만들고자 했습니다. 세션 조회와 삭제등과 같은 관리는 모두 이 서버에 요청이 오도록 하는 것입니다.

그리고 사용자가 증가함에 따라 캐시 저장소에 등록되는 데이터도 함께 늘어나게 될 것이며, 마찬가지로 Redis에 서버에 저장되는 로그인 정보를 담은 세션과 인증번호의 건수 또한 늘어나게 될 것입니다.

Redis는 In-memory Data Stroe이기 때문에 Physical Memory 이상을 사용하게 되면 swap이 발생하게 됩니다. swap이 한번 발생하게 되면 Redis가 디스크를 계속 읽게 되어 따라서 성능이 크게 저하될 수 있습니다. 즉, 성능 향상을 위해 Redis를 사용하고 있는데 그 이점이 사리지지 않기 위해

세션을 저장하는 서버와 캐시 데이터를 저장하는 서버를 분리해 조금 더 효율적인 메모리 관리가 가능할 것이라 생각했습니다.

ward-study-reservation Project에서는 세션서버에서의 세션저장은 기본적으로 Httpsession 객체를 통한 로그인과 로그아웃 기능에 구현하였습니다.


구현 코드

아래는 구현 코드입니다.

Redis에 세션을 관리하기 위해선 아래의 의존성을 추가하고, Redis config 클래스를 작성합니다.

  • build.gradle
// redis-session
implementation 'org.springframework.session:spring-session-data-redis'

  • RedisConfig
@Configuration
public class RedisConfig {

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

	// Redis Session, Cache 서버(Port)의 분리
    @Value("${spring.redis.session.port}")
    private int redisSessionPort;

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

    @Bean
    public LettuceConnectionFactory connectionFactory() {
        return new LettuceConnectionFactory(redisHost, redisPort);
    }

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

  • LoginService
@Service
@RequiredArgsConstructor
public class LoginServiceImpl implements LoginService {

    private final HttpSession httpSession;	// HttpSession 처리로 로그인, 로그아웃 구현
    private final String USER_ID = "USER_ID";

	@Override
    public void login(User user) {
        httpSession.setAttribute(USER_ID, user.getId());
    }
	
    @Override
    public void logout() {
        httpSession.removeAttribute(USER_ID);
    }

}

HttpSession으로 Redis 세션저장소에 관리하기

  • HttpSession - 세션을 손쉽게 생성하고 관리하게 할 수 있는 인터페이스
  • UUID로 세션 ID를 생성해주며, JSESSIONID라는 이름의 cookie를 설정해준다.
  • 로그인시, redis 세션 저장소에 key-value 형태로 로그인한 userId를 httpSession 객체를 통해 set해준다.
  • 반대로 로그아웃시, remove를 해준다.
  • Redis에 저장시 JSESSIONID -> SESSION 으로 이름이 바뀐다.

설정

application.yml

spring:
  session:
    store-type: redis	# 세션저장소 redis로 지정

  redis:
    cache:
      host: localhost
      port: 6379
    session:
      host: localhost
      port: 6380

docker 로 session용 redis 서버 별도 올리기

redis 설치는 이 블로그를 참조하시길 바랍니다.

$ docker run --name redis-session -d -p 6380:6379 redis
$ docker exec -it redis-session redis-cli

Redis 세션저장소에서 확인하기

1) 로그인시

localhost:8081/users/login

로그인 후 Redis에 session 데이터들이 저장됩니다.

아래는 redis-cli 콘솔창의 session 데이터들이 저장된 내용들입니다.

여기서 session에 해당되는 값들은 3개로 나옵니다.

3개!
"spring:session:sessions:expires:b15c2c33-9a99-4933-bd4a-fb3e870f1914"
"spring:session:expirations:1666114800000"
"spring:session:sessions:b15c2c33-9a99-4933-bd4a-fb3e870f1914"

여기서 key값으로 설정한 USER_ID값은 어떻게 확인할까요?

그전에 알아야 할 것이 각각,
Spring redis session에 세션을 저장하면 조회할 수 있는 데이터들인데 타입이 다 다르다는 것입니다.

1. spring:session:sessions:expires (string)
2. spring:session:expirations (set)
3. spring:session:sessions (hash)

여기서 sessiondp 저장한 USER_ID를 확인할려면 hash 타입에서 값을 가져와서 확인할 수 있었습니다. 명령어로,

hkeys "spring:session:sessions:b15c2c33-9a99-4933-bd4a-fb3e870f1914"
// 해당 "sessionAttr:USER_ID" 있음을 확인!

hget "spring:session:sessions:b15c2c33-9a99-4933-bd4a-fb3e870f1914" "sessionAttr:USER_ID"

USER_ID가 넘버값으로 나옵니다!

참고로 값이 복잡하게 되어있는데 이 값이 해쉬값이기 때문입니다.
redis config 클래스에서 직렬화 설정에 따라 저장하는 방식을 다르게 할 수 있습니다.


2) 로그아웃시

로그인시에 USER_ID가 session에 저장되는 것을 확인할 수 있었고

로그아웃은 session에서 USER_ID 값이 사라지게 할 수 있을까요?
확인해보았습니다.

post localhost:8081/users/logout

post방식의 로그아웃 요청을 보내고

다시 redis에서 해시값을 확인하였습니다.

hget "spring:session:sessions:b15c2c33-9a99-4933-bd4a-fb3e870f1914" "sessionAttr:USER_ID"

확인해보니, session에 저장된 USER_ID 값이 사라졌음을 확인할 수 있었습니다.



출처 : https://github.com/f-lab-edu/ward-study-reservation

profile
배운 것을 기록합니다.

0개의 댓글