개성있는 개발자 되기

RedisTemplate 을 이용해서 Multi Pojo get/set 할 때 이슈사항 본문

Open Source/Redis

RedisTemplate 을 이용해서 Multi Pojo get/set 할 때 이슈사항

정몽실이 2020. 3. 11. 19:14

Spring Boot에서 제공하는 RedisTemplate 을 이용하면 쉽게 Redis 데이터를 Get/Set 할 수 있다.

이 때, Data를 Serialize 하는 방법을 설정할 수 있는데 org.springframework.data.redis.serializer 패키지에서 확인할 수 있다.

 

다양한 모델의 데이터를 Redis에서 조회해올 때 겪었던 이슈사항과 최종적으로 택한 방법을 정리하고자 한다.

 

아래 내용은 나와 정말 똑같은 고민을 가진 사람이 쓴 글이다 ㅋㅋ

 

https://cnpnote.tistory.com/entry/SPRING-Spring-RedisTemplate-%EC%97%AC%EB%9F%AC-%EB%AA%A8%EB%8D%B8-%ED%81%B4%EB%9E%98%EC%8A%A4%EB%A5%BC-JSON%EC%9C%BC%EB%A1%9C-serialize%ED%95%A9%EB%8B%88%EB%8B%A4-%EC%97%AC%EB%9F%AC-RedisTemplates%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%B4%EC%95%BC%ED%95%A9%EB%8B%88%EA%B9%8C

 

[SPRING] Spring RedisTemplate : 여러 모델 클래스를 JSON으로 serialize합니다. 여러 RedisTemplates를 사용해야합니까?

Spring RedisTemplate : 여러 모델 클래스를 JSON으로 serialize합니다. 여러 RedisTemplates를 사용해야합니까? Redis에서 내 개체를 저장하기 위해 Spring Redis 지원을 사용하고 있습니다. 다른 모델 클래스를..

cnpnote.tistory.com

 

1. GenericJackson2JsonRedisSerializer

 

첫번째로 시도했던 Serializer이다. 이 Serializer은 별도로 Class Type을 지정해줄 필요없이 자동으로 Object를 Json으로 직렬화 해주는게 장점이다.

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

 

그러나 치명적인 단점이 있었다.

바로, Object의 Class Type을 함께 레디스에 넣는다는 것이다.

"{\"@class\":\"com.prnv.model.WhitePaper\",\"title\":\"Hey\",\"author\":{\"@class\":\"com.prnv.model.Author\",\"name\":\"Hello\"},\"description\":\"Description\"}"

 

 

위와 같이 @class 로, 이 Dto의 패키지까지 모두 포함되어 저장이 된다.

이게 왜 치명적이냐 함은, 이 데이터를 꺼내올때 (GET) @class로 지정된 동일한 패키지에 있는 Dto로 밖에 가져올 수 없다는 것이다.

이런식으로 데이터가 저장된다면, 다른 Application에서는 무조건 저 Class를 생성하고, 무조건 저 루트로 같은 이름으로 Object를 생성해 놔야 한다.

 

MSA 프로젝트란 무엇인가. 여러 Application API 들이 서로 상호작용하는게 기본이다. 이렇게 데이터가 저장된다면 MSA API 들은 Data Class Type 에 묶여버리게 된다.

 

2. Jackson2JsonRedisSerializer

 

위의 Generic Serializer를 버리고 Jackson2JsonRedisSerializer 이 Serializer를 택했다.

이 Serializer은 Class Type 형을 명시한다. 위와 같이 @class 를 포함해서 저장하지 않는다.

단점은, 항상 Class Type을 지정해줘야 한다는 것이다. 

하지만 이부분은 메소드를 공통화 시키면 쉽게 풀리는 일이었다.

    public boolean setHashData(String key, String hashKey, Object value) throws Exception {
        redisTemplate.setHashValueSerializer(new Jackson2JsonRedisSerializer(value.getClass()));
        redisTemplate.opsForHash().put(key, hashKey, value);

        return true;
    }

위와 같이 저장하고자하는 PoJo Type을 파라미터로 받아서, Jackson2JsonRedisSerializer를 생성한다.

 

문제가 해결된듯 보였다. 하지만 또 다른 문제가 발생한다.

바로, 이렇게 되면 각 Class Type은 따로따로 RedisTemplate을 생성해야 한다는 것이다.

 

Serializer는 redisTemplate에 설정하는 것이다. 그리고 이 redisTemplate은 (의도한것은) 모든 서비스에서 불러다 쓰는 공통 Service이다.

 

각 서비스들이 이 Resource 에 접근해서 Seriazlier를 바꿔치기 해버리면 어떻게 될까?

 

동기 호출이면 모를까, 여러 API들이 호출될텐데, 각 스레드들이 redisTemplate에 접근해서 데이터를 Get 하다가 Serializer가 바꿔치기 되버려서 결국 Serialize 에러가 나버린다.

눈물..

 

대안은 두가지였다.

 

1. 각 Dto를 가지는 API 들이 Redis 에서 Get/Set할때 각자 고유한 RedisTemplate을 생성해서 가지고 있는다.

2. Generic으로 돌아와서, 모든 @class 타입을 맞춰버린다.

 

두 가지 다 너무 구렸다. 그래서 생각을 해낸게 JacksonSerializer를 쓰지 말고 StringSerializer를 쓰고 Json으로 파싱하는 것은 우리가 직접 하자!!

 

3. StringRedisSerializer

 

    @Bean
    public RedisTemplate<String, Object> redisTemplateTest() {

        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashValueSerializer(new StringRedisSerializer());

        return redisTemplate;
    }
    /**
     * Redis 데이터 조회
     * @param <T>
     * @param key
     * @param classType
     * @return
     * @throws Exception
     */
    public <T> T getData(String key, Class<T> classType) throws Exception {

        String jsonResult = (String) redisTemplate.opsForValue().get(key);
        if (StringUtils.isBlank(jsonResult)) {
            return null;
        } else {
            ObjectMapper mapper = new ObjectMapper();
            T obj = mapper.readValue(jsonResult, classType);
            return obj;
        }

    }

깔-끔

 

Spring 에서 제공하는 RedisTemplate 메소드가 Develop 되면 모를까.. 영문 자료밖에 없고 힘들었지만, 나름 뿌듯하네

 

참고로, RedisTemplate 하나만으로 여러 스레드들이 작동할 수 있는것은 Lettuce가 멀티 스레드를 지원하기 때문이다.

자세한 내용은 아래 공식문서를 보자

 

https://docs.spring.io/spring-data/redis/docs/current/reference/html/#redis:serializer

 

Spring Data Redis

Some commands (such as SINTER and SUNION) can only be processed on the server side when all involved keys map to the same slot. Otherwise, computation has to be done on client side. Therefore, it is useful to pin keyspaces to a single slot, which lets make

docs.spring.io

 

'Open Source > Redis' 카테고리의 다른 글

Redis-Exporter에서 표시되는 Graph 수치  (0) 2020.03.19
Redis Exporter 프로메테우스 Metrics  (0) 2020.03.19
Redis-cli 사용법  (1) 2020.03.19
Comments