brunch

You can make anything
by writing

C.S.Lewis

by 에디의 기술블로그 Aug 26. 2018

HashMap&Redis with Spring Data

- 레디스 무식자의 Spring Boot Data Redis 연동 스터디

주말에 집에서 쉬면서 생각의 흐름대로 작성한 글입니다. 그래서 내용이 매우 지루하고 재미가 없습니다. Redis에 관심이 없으시다면 이 글은 안 읽으셔도 됩니다. 매우 재미 없는 글이지만, 혹시... 그래도 시간이 조금이라도 되신다면 한번 읽어보시기를 추천합니다. 보시고 내용이 잘못된 점이 있다면 피드백을 부탁드립니다.  


저는, 레디스 초보입니다. 작은 의견이라도 소중하게 받아들이겠습니다.


Overview

최근에 맡게 된 프로젝트가 있는데, 해당 프로젝트가 메모리 및 성능 이슈가 발생하여 조치가 필요한 상황이다. 해당 시스템의 문제점을 먼저 검토하고, 다양한 해결 방법을 고민해본다. 최종적으로는 스프링 부트 환경에서 Redis를 연동하는 방법에 대해서 정리한다.


사내 시스템은  회사의 허락 없이는 상세하게 공개할 수 없습니다. 그래서 해당 글의 내용은 현재 상황과 유사하게, 필자가 만든 가상의 환경입니다. 사내 시스템은 더 복잡한 이슈가 이것저것 많습니다.


목차

1.1 가상의 레거시 프로젝트

1.2 나의 미션

2.1 관련 연구 - Scale up  vs Scale out, Nosql

2.2 관련 연구 - Redis vs MongoDB

2.3 관련 연구 - HashMap

2.4 Redis 구성하기 Master/Slave, Sentinel

3.1 Redis 연동 스터디 - Java Jedis 연동

3.2 Redis 연동 스터디 -  Spring Boot Data Redis

3.3 Redis 연동 스터디 -  Spring Boot Redis Embedded

3.4 Redis 연동 스터디 -  Spring Data Redis Sentinel

4. [방법1] @RedisHash 를 활용, HashMap 타입으로 저장

4.1 패키지 구조

4.2 Redis HashMap Key 설계하기

5. [방법2] @Cacheable 를 활용, 직렬화 데이터 저장

6. 마무리



1장. 개요

1.1 가상의 레거시 프로젝트, 그리고 문제점

NoSQL 개념이 없던 시절 만든 레거시 API 프로젝트가 있다. 해당 API는 다양한 콘텐츠 데이터를 Java HashMap 의 메모리 캐시에 저장하고 사용자의 요청이 들어오면 해당 HashMap에서 데이터를 꺼내서 응답하는 컨텐츠 제공 API 서버이다. 컨텐츠 원본 데이터는 퍼시스턴스 DB로 MySQL 서버에 저장되어 있고, 컨텐츠 관리자의 작업으로 MySQL과 Java HashMap이 동기화 작업이 이루어진다. 하지만, MySQL 원본 데이터를 가져와서 Java HashMap 에 넣는 과정은 매우 복잡한 비즈니스 로직으로 코딩이 되어있다. 해당 로직이 성능적으로도 부하가 크기 때문에 사용자의 요청이 있을 때는 MySQL에서 직접 조회하는 것이 아니라 API 서버에 미리 저장해둔 HashMap 캐시에서 바로 꺼내서 사용자에게 전달해주는 API이다. 해당 시스템은 4대의 물리 서버로 구성되어 있고, 앞단에 L4로 로드밸런싱을 수행한다. 4대의 시스템은 모두 동일한 결과를 응답해야 하기 때문에 각자 가지고 있는 HashMap 데이터는 동기화가 되어야 한다. 



해당 시스템은 시간이 지나면 지날수록 메모리가 커질 수밖에 없다. Java HashMap Cache 의 사이즈가 시간이 지날수록 너무 커졌다. 수십 기가의 메모리를 차지하고 있다. 더 큰 문제는 데이터의 동기화 문제이다. 동기화는 두 가지 측면에서 이슈가 있는데,


API와 다른 API 서버 간의 동기화, 즉 각각의 API 서버에 저장되어 있는 HashMap  Cache 의 동기화 이슈

API와 원본 데이터를 저장하고 있는 Mysql(RDBMS) 동기화 이슈


만약 API 서버가 갑자기 죽어버리면 메모리에 저장된 캐시 데이터는 모두 날아가버린다. 해당 API 서버를 정상 복구하기 위해서는 RDBMS 의 데이터를 모두 가져와서 Java HashMap 에 저장해야 서비스를 할 수 있는데, 몇 년 동안 쌓인 HashMap 메모리 데이터를 동기화하는 작업은 빨리 끝날 것 같지가 않다. 그래서 해당 API를 재부팅하기를 꺼려했고, 해당 시스템을 건들지 않기 위해서 관련 사이드(부가) 시스템에 덕지덕지 옵션처럼 지저분한 기능이 추가되었다. 그사이에 시스템은 언제 터질지 모르는 폭탄이 되어있었다.

서비스 시스템을 오래 운영하다 보면, 이런 경우는 자주 발생한다. 다른 회사에서도 비슷한 사례가 많을 것이다. 현재 시스템의 문제를 지적하기보다는, 해결책을 찾고, 간결하게 해결할 수 있는 방법을 찾아보자. 


1.2 최종 미션(실제 진행은 안함)

많은 이슈가 있지만 간단하게 정리하면 아래와 같다. 

1. Java HashMap Cache를 다른 저장소로 이동해야 한다. (MySQL 의 원본 데이터를 가져와서 시스템에 맞는 가공된 데이터로 변경&저장을 해야 한다. )

2. MySQL의 원본 데이터를 가져와서 HashMap Cache 에 넣는 과정은 매우 복잡한 비즈니스 로직이 포함되어 있다. 가능하면, 비즈니스 로직은 건드리지 않고 Cache 데이터만 이전하는 게 좋을 듯하다. 

3. 일부 데이터가 아닌, 전체 데이터를 동기화하는 과정은 빠르게 진행되어야 한다. 현재는 1시간 이상 걸리는 상황이다. 

4. 서비스 무중단 상황에서 이전해야 한다. 즉, 해당 작업을 진행하는 순간에도 시스템은 정상적으로 돌아가야 한다. 물론, 이전 시 발생할 수 있는 장애를 대비한 롤백 시나리오도 준비해야 한다.


이번 글에서는 최종 미션에 대해서는 생략하겠다. 미션 수행을 위한 사전 검토 내용만 이 글에서 정리한다. 



2. 관련 연구 - 기초 개념 정리

2.1 Scale up vs Scale out , Nosql


Scale up vs Scale out

Scale up : 단일 시스템의 서버 하드웨어 성능을 증가하는 방법

Scale out : 동일한 사용의 하드웨어를 추가해서 분산 시스템으로 구성하는 방법


해당 시스템은 각 API 서버가 Scale up 의 방법으로 운영이 되었다. 사실 L4를 통해서 트래픽 로드밸런싱이 되고 있기는 하지만, 각 API를 보면 명백하게 Scale up 프로젝트이다. Java HashMap Cache 가 시간이 지날수록 커지는 상황이고, 메모리 부족으로 인해서 지속적으로 Cache관리 또는 메모리 증설을 해야 하지만, 시간이 지날수록 어려움이 커질 수밖에 없는 시스템이다. 또한, Scale up을 진행하기 위해서는 서비스 중단이 불가피하다. 만약, 해당 시스템이 Scale out 아키텍처로 구축이 되었다면 어땠을까? Cache를 분산해서 저장할 수 있다면, 좀 더 지혜롭게 서비스를 운영할 수 있을 것이다. 그래서 해당 Cache를 이전해야겠다는 생각을 하게 되었고, 그 방법으로 찾은 것이 바로 NoSQL이다. Scale out 기반으로 설계된 것이 바로 NoSQL 이기 때문이다. 


NoSQL

NoSQL에 대해서는 대부분 개발자가 필자보다 훨씬 잘 알고 있을 것이다. 필자는 NoSQL 사용 경험이 많지는 않다. 처음 사용은 5년 전쯤인데, 당시(전 회사) 컨텐츠 캐시 DB 의 역할로 Redis를 사용했었다. 현재 회사에서는 간단한 데이터 조회, 저장 용도로 사용 중이며, Redis, MongoDB 등을 사용한다. NoSQL에 대한 분류는 데이터의 저장 방식에 따라 분류되기도 하는데, 자세한 내용은 '이것이 레디스다 79page'를 참고하길 바란다. 저작권으로 인해서, 책의 내용을 그대로 쓸 수가 없다. 종류는 아래와 같다. 

Key-Value

Document

Column

Graph

IMDG(?)


Key, Value Model DB

Key-Value 모델 중 Redis를 대중적으로 가장 많이 사용하다. Amazon DynamoDB 가 2위이고, 아주 고전적인 Memcached 가 3위에 랭크되어 있다. 참고로 Amazon DynamoDB는 멀티 모델이다. 즉, Key-Value 외에 DocumentDB 의 역할도 가능하다. 또한 Memcached는 Nosql이라고 보기는 어렵다고 한다. "이것이 레디스다, 31page"를 참고하자. 

https://db-engines.com/en/ranking/key-value+store


어쨌든 Key-Value 저장소로 가장 많이 사용하는 레디스의 주요 특징은 아래와 같다.

Key-Value 캐시에 아주 적합하다.

인메모리 데이터 저장소이다.

다양한 자료구조를 지원한다.

사용하기 쉽다. 

성능이 매우 우수하다.

무료이다.

많은 사람들이 사용하기 때문에, 관련 자료를 찾기 쉽다


https://stackshare.io/redis


Document Model DB

Document 모델은 Key-Value을 개념적으로 확장한 구조이다. 하나의 Key 에 구조화된 문서(Document)를 저장한다. Mongo DB를 가장 많이 사용하고, Amazon DynamoDB, Couchbase 등이 업계에서 많이 사용하는 것 같다. 

https://db-engines.com/en/ranking/document+store


NoSQL을 잘 모르는 필자는, Document DB와 Key-Value DB의 차이점에 대해서 쉽게 이해하지 못한다. 혹시 주변에 잘 아는 개발자는 차분하게 필자에게 설명해주길 바란다. 어쩃든 아는 만큼만 설명을 하려고 했지만, 일단 자세한 설명은 잠시 후에 다시 정리하고자 한다. 


Column Model DB

Column 모델 DB는 하나의 키에 여러 개의 칼럼 이름과 칼럼 값의 쌍으로 이루어진 데이터를 저장하고 있다. 카산드라, HBase를 많이 사용한다. 

https://db-engines.com/en/ranking/wide+column+store

대부분의 Column Model 은  "읽기" 보다는 "쓰기"에 최적화되어있다고 한다. 필자가 구현하는 시스템은 "읽기"에 더 적합하기 때문에 Column Model DB는 검토 대상에서 제외하였다. 


Graph Model DB

조금 생소한 그래프 DB는 Neo4j 가 압도적으로 많이 사용되고 있다. 최근에(아니.. 몇 년 전에) 카카오에서 S2Graph 에 대한 세미나를 들었던 기억이 있다. 카카오에서 현재도 시스템에 사용하고 있는지는 잘 모르겠지만, 어쨌든 해당 세미나에서 들었을 때 카카오에서 적합한 GraphDB를 찾기 어려웠다고 들은 기억이 있다. 또한, "이것이 레디스다 95page"에서도 GraphDB는 Scale out 에는 적합하지 않다고 설명이 되어있다. 그래서 역시 검토 대상에서 제외하였다. 


참고로 필자가 최근에 Graph DB로 프로젝트를 진행하였는데, Graph Model DB가 비즈니스 로직이 매우 복잡해져서, 프로그래밍 하기에는 상대적으로 다른 NoSQL 보다는 힘들다고 생각했다. 어쨌든 Graph DB 자체는 좋지만, 해당 시스템에는 적합하지 않다고 생각했다. 


IMDG

IMDG (인메모리 데이터 그리드)인 HazelCast와 Ignite 도 캐시 솔루션으로 훌륭하다. HazelCast는 최근에 필자가 검토한 글을 참고하길 바란다. HazelCast는 좋기는 하지만 노드가 3대 이상인 경우에는 관리 툴을 유료로 사용해야 하는 단점이 있다. Ignite는 사용자가 너무 없어서 사용하기에는 부담스럽다. 


어떤 NoSQL을 사용할지는 정말 중요하다!!

신중하게 결정이 필요하다. 시스템 특성에 적합하지 않은 Column DB와 Graph DB 등 제외하니 Key-Value 모델인 Redis,  Document 모델인 MongoDB가 후보로 압축되었다. 추가로 아래와 같은 내용도 함께 고려하였다.

대중적으로 많이 사용하는 오픈소스 

성능이 비교적 뛰어난 오픈소스

커뮤니티가 활성화되어서, 사용 사례 공유가 많고 문의가 가능한 오픈소스

회사에서 현재 사용 중인 오픈소스(운영 리소스 측면)

고민을 해보니 Redis와 MongoDB 가 적합할 것 같다는 막연한 생각이다. 이제 본격적으로 Redis와 MongoDB에 대해서 알아보자. 



2.2 Redis vs MongoDB

일단 생략

비교할 시간도 없고 필자가 잘 모르기 때문에, 일단 Redis 먼저 공부하고, MongoDB 는 추가로 공부해서 비교하기로 하자. 


2.3 HashMap

네이버 d2에 작성된 글을 참고하자.

https://d2.naver.com/helloworld/831311



2.4 Redis 구성하기 Master/Slave, Sentinel

설치 방법 및 클러스터 구성에 대해서는 아주 간략하게만 작성하겠다. 물리 서버가 2대라고 가정하고 1대는 Master, 1대는 Slave로 구성할 것이다. Master는 읽기/쓰기 모두 가능하고, Slave는 읽기만 가능하다.


2.4.1 설치

자세한 내용은 생략한다. 2대의 서버에 Redis를 설치한다.  두 대의 서버 모두 기본 포트인 6379 포트를 사용할 것이다. 


기본 설정

etc/redis.conf 파일을 열고 패스워드 설정 및 bind 포트 0.0.0.0을 설정한다. 추가로, OS가 6379 포트가 외부에 막혀있다면 해당 포트도 열어주길 바란다. 아주 기본적인 내용이니 상세한 내용은 생략한다.


cli 콘솔 접속

redis-cli -a 비번


2.4.2 Master/Slave 구성

Slave 의 서버에는 redis.conf 에서 아래와 같이 Master 서버의 IP와 포트, 패스워드를 설정해야 한다.


slaveof 마스터아이피 포트

masterauth 패스워드


자 이렇게 설정하고, Master, Slave 서버를 재시작하면 심플하게 Master-Slave 환경이 구성이 된다. 


2.4.3 Master/Slave 테스트


읽기만 가능한 Slave 서버

Master는 읽기/쓰기 가 가능하고, Slave는 읽기만 가능한 서버 구성이다. Slave 서버에서 데이터 SET을 시도하면, 아래와 같이 에러 메시지가 나올 것이다.

(error) READONLY You can't write aginst a read only slave. 


데이터 리플리케이션(동기화)

Master 서버에서 신규 key/value를 입력해보자. Slave 서버에서도 동일하게 해당 key/value를 조회할 수 있다. 만약 Slave 서버가 죽어버린 시점에서 Master 에 데이터가 입력되면 어떻게 될까? Slave 서버가 재시작되어 서버가 올라오면서 자동으로 데이터를 동기화한다. 상세한 내용은 아래 레퍼런스를 참고하자.

http://redisgate.jp/redis/configuration/replication.php


2.4.4 Sentinel(센티널) 설정

만약 Master 서버가 죽으면 어떻게 될까? 이런 경우는 흔하지는 않겠지만, 언제든지 발생할 수 있다. 이런 경우에는 Slave를 Master로 올리고 다른 Slave 서버 중 1대가 Master 역할을 수행해야 한다. 이 방법을 적용하기 위해 사용되는 도구가 Redis-Sentinel(센티널)인데, Sentinel 은 아래와 같은 기능을 한다. 

모니터링 (Monitoring)

자동 장애 조치 (Automatic)

알림 (Notification)

현재 A 서버에 Master Redis(6379), B서버에 Slave Redis(6379)를 세팅하였고, B서버에 Sentinel 인스턴스를 하나 띄워보겠다. 포트는 26379로 설정하겠다. 


사실 운영 환경에서는 Sentinel(센티널)은 최소 3개 이상, 홀수로 만드는 것을 권장한다. 필자는 테스트 환경이다. 


자.. 아무튼, sentinel.conf라는 컨피그 파일을 수정하자. 필자의 OS에서는 etc/redis-sentinel.conf 파일이다. 개발 환경에 따라서는 컨피그 위치가 다를 수는 있다. 


센티널이 동작할 포트와 센티널이 감시할 Master 정보를 입력한다. 6379는 포트이고, Sentinel 이 한대이기 때문에 1을 입력해준다. 그리고 master 의 비번 설정도 한다. 


port 26379

sentinel monitor mymaster 119.XXX.XXX.XXX 6379 1

sentinel auth-pass mymaster 비번(Master Redis 의 비번)


Sentinel를 실행해보자. 실행 로그 경로는 /var/log/redis/sentinel.log 이다. 


service redis-sentinel start 



redis-cli -p 26379 를 입력하면 Sentinel 콘솔에 접속이 가능하다. 그리고 info를 실행하면 sentinel 정보를 확인할 수 있다. 


Master 서버가 갑자기 죽으면 어떻게 될까?

Master Redis서버가 다운되면, Slave 서버가 Master 의 역할을 수행한다. 그리고, 죽었던 서버가 다시 살아나면 해당 서버는 Slave로 구성된다. 결국 Master와 Slave 가 서로 역할을 바꾼 것이다. Sentinel 설정은 아래 링크를 참고하자. 

http://redisgate.jp/redis/sentinel/sentinel.php

https://jdm.kr/blog/159



TO-BE

최종적으로는 캐시디비를 별도로 구축하게 된다면 아래와 같이 구성이 가능할 것이다.


TO-BE




2장에서는 관련 기술을 연구하였다. 최종적으로는 Redis 서버를 설치하였고, 안정적인 서비스 운영을 위해서 Master/Slave 및 Sentinel 구성을 하였다. 테스트를 위해서 간단하게 설정을 구성하였다. 실서비스 환경에서는 고려해야 할 사항이 더 많을 것이다. 입개발은 그만하고, 이제 본격적으로 Spring 환경에서 Redis를 

연동해보겠다. 



3장. Spring Redis 연동

3.1 Java Redis 연동

입 개발은 그만하고 이제 코드를 보자. Java에서 Redis를 연동해보자. 자바에서 Redis를 연동하기 위한 라이브러리 중 가장 많이 알려진 것이 바로 Jedis이다. 


Connection Poll Open

Jedis 라이브러리를 사용해서 데이터 입출력을 하기 위해서는 먼저 커넥션풀을 연결해야 한다.


JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
JedisPool pool = new JedisPool(jedisPoolConfig, "119.205.***.**", 6379(포트), 1000(타임아웃), "****(패스워드)");
Jedis jedis = pool.getResource();



Strings


jedis.set("key_test01", "1");
System.out.println(jedis.get("test01"));



Sets


jedis.sadd("key_food:key_test02", "apple");
jedis.sadd("key_food:key_test02", "orange");
jedis.sadd("key_food:key_test02", "banana");
jedis.sadd("key_food:key_test02", "orange"); //중복 테스트
Set<String> foods = jedis.smembers("key_food:key_test02");
foods.forEach(a -> System.out.println(new String(a.getBytes(), StandardCharsets.UTF_8)));



Hashs


jedis.hset("key_member:key_n3140", "name", "sieun");
jedis.hset("key_member:key_n3140", "team", "portalteam");
Map<String, String> stringMap = jedis.hgetAll("key_member:key_n3140");
System.out.println(stringMap.get("name"));
System.out.println(stringMap.get("team"));



Lists

생략

Soreted Sets

생략


Connection Poll Close

작업이 끝나면 커넥션을 종료하자. 

pool.close();


Jedis 라이브러리에서 사용 가능한 메서드는 redis.clients.jedis 를 까서 직접 확인해보자. 


자세한 내용은 아래 링크에서 확인하자.

http://www.baeldung.com/jedis-java-redis-client-library


Jedis 라이브러리를 사용해서 Redis 에 커넥션을 연결하고 데이터를 입출력할 수 있다.


하지만, 필자는 Java 애플리케이션에서 Jedis 라이브러리를 쌩으로 사용하진 않을 것이다. Spring 환경에서 연동을 고민해보자. 



3.2 Spring Boot Data Redis

Spring Data 란?

Spring Data 프로젝트는 데이터 연동을 위한 스프링 기반의 프로그래밍 모델을 제공하는데, 친숙하고 일반적이면서 사용하기 쉬운 추상화된 프로그래밍을 할 수 있도록 지원한다. 관계형 DB, 비 관계형 DB, 맵리듀스, 클라우드 기반의 데이터 서비스 등 다양한 데이터 저장소를 사용하기 쉽게 프로그래밍 모델을 제공한다. 특징을 정리하면 아래와 같다.

Powerful repository and custom object-mapping abstractions  

Dynamic query derivation from repository method names

Implementation domain base classes providing basic properties

Support for transparent auditing (created, last changed)

Possibility to integrate custom repository code

Easy Spring integration via JavaConfig and custom XML namespaces

Advanced integration with Spring MVC controllers

Experimental support for cross-store persistence

Spring Data Redis에 대해서는 레퍼런스를 참고하자. 

https://projects.spring.io/spring-data-redis/

http://arahansa.github.io/docs_spring/redis.html


Spring Data Redis Dependency, Configuration 설정

Spring Data Redis를 적용하기 위해서, 아래와 같이 디펜던시를 먼저 추가한다.


//build.gradle

dependencies {
   compile('org.springframework.boot:spring-boot-starter-data-redis')
   compile('org.springframework.boot:spring-boot-starter-web-services')
   compileOnly('org.projectlombok:lombok')
   testCompile('org.springframework.boot:spring-boot-starter-test')
}


아래와 같이 프로퍼티 설정을 한다.


//application.properties

spring.redis.host=119.205.***.***
spring.redis.password=1234
spring.redis.port=6379
spring.redis.timeout=0
spring.data.redis.repositories.enabled=true


오토컨피그레이션 설정 및 빈 생성 로직을 추가한다.


//RedisConfig

@Configuration
@ComponentScan("컴포넌트스캔 패키지 경로")
public class RedisConfig {

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

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

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

    @Bean
    public JedisPoolConfig jedisPoolConfig() {
        JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
        jedisPoolConfig.setMaxIdle(30);
        jedisPoolConfig.setMinIdle(10);
        jedisPoolConfig.setTestOnBorrow(true);
        jedisPoolConfig.setTestOnReturn(true);
        return jedisPoolConfig;
    }

    @Bean
    public JedisConnectionFactory redisConnectionFactory() {
        JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(jedisPoolConfig());
        jedisConnectionFactory.setHostName(redisHost);
        jedisConnectionFactory.setPort(redisPort);
        jedisConnectionFactory.setPassword(password);
        jedisConnectionFactory.setUsePool(true);
        return  jedisConnectionFactory;
    }


    @Bean
    public RedisTemplate<String , Object> redisTemplate() {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory());
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new StringRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(new StringRedisSerializer());
        template.setEnableDefaultSerializer(false);
        template.setEnableTransactionSupport(true);
        return template;
    }
}


역시, Spring Boot 에 연동은 간단하다. 


3.3 Spring Boot Redis Embedded

지금 위에 설정은 모두 외부 Redis 에 연동하는 방법이다. 혹시, 임베디드 Redis로 구성하는 방법도 가능하다.  라이브러리를 사용해야 한다.


Gradle 디펜던시


compile group'com.github.kstyrc'name'embedded-redis'version'0.6'


컨피그 설정


@Component

public class EmbeddedRedisConfiguration {

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

    private int redisPort;

    private RedisServer redisServer;

    @PostConstruct

    public void startRedis() throws IOException {

        redisServer = new RedisServer(redisPort);

        redisServer.start();

    }

    @PreDestroy

    public void stopRedis() {

        redisServer.stop();

    }

}


@Configuration

public class RedisConfig {

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

    private String redisHost;

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

    private int redisPort;

    @Value("${spring.redis.database}")

    private int redisDatabase;

    /**

     * Factory

     *

     * @return

     */

    @Bean

    public JedisConnectionFactory jedisConnectionFactory() {

        JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory();

        jedisConnectionFactory.setHostName(redisHost);

        jedisConnectionFactory.setPort(redisPort);

        jedisConnectionFactory.setDatabase(redisDatabase);

        jedisConnectionFactory.setUsePool(true);

        return jedisConnectionFactory;

    }

    /**

     * redis Template

     *

     * @return

     */

    @Bean

    public RedisTemplate<String, Object> redisTemplate() {

        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();

        redisTemplate.setKeySerializer(new StringRedisSerializer());

        redisTemplate.setValueSerializer(new StringRedisSerializer());

        redisTemplate.setConnectionFactory(jedisConnectionFactory());

        return redisTemplate;

    }

}


https://github.com/sieunkr/spring-data/tree/master/spring-data-redis-embedded

설명이 자세하지 못해서 죄송하다. 어쩔 수 없다. 잘 모르기 때문에 필자도 상세하게 쓸 수가 없다. 창천향로 님의 블로그를 참고하자. 정말 강력 추천하는 블로그이다. 스페셜 원탑 블로그이다.

http://jojoldu.tistory.com/297

어쨌든 필자는, 실서비스에서 임베디드 Redis를 사용할 계획은 없다. 

실서비스에서 임베디드 캐시를 사용하는 아키텍처는 추후에 확장성 측면에서 좋지 않다.


3.4 Spring Data Redis Sentinel 연동하기

자세한 내용은 생략한다. 스프링 레퍼런스를 확인하자. 

https://docs.spring.io/spring-data/redis/docs/2.0.9.RELEASE/reference/html/#redis:sentinel






4장.[방법1]@RedisHash 활용,HashMap 타입으로 저장

4장에서는, @RedisHash 어노테이션을 활용하여, Redis 에 HashMap 타입으로 데이터를 저장해본다. 


4.1 패키지 구조

간단하게 구현한 필자의 허접한(?) 프로젝트 구조는 아래와 같다. 


4.2 HashMap Key 설계하기

Spring Data Redis - Entity, Repository

주저리 글을 작성하다 보니, 근본적인 목표를 잠시 잊고 있었다. 이 글은 Java 의 HashMap 에 저장된 캐시 데이터를 Redis 저장소로 마이그래이션 하는 과정이다. Spring Data Redis에서는 @RedisHash("해시맵루트키") 어노테이션을 제공한다. @RedisHash 를 Entity 클래스 상단에 선언하고,  @ID 필드를 추가하자. @ID는 해시 데이터를 위한 해시코드 키값이 자동으로 적용이 된다. 아래 샘플을 참고하자.


@RedisHash("peoples") 

public class Person {  

     @Id String id;   

    String firstname;   

    String lastname;   

    Address address; 

}


해당 클래스로 Person 을 4번 만들었다고 가정하자. 먼저 하나의 키에 여러 개의 요소가 저장되는 Set Data 가 생성된다. 이때 값에는 Person 하나하나에 생성되는 HashData 의 Key 값들을 저장하게 된다. 

Redis 콘솔에서 데이터를 확인해보자. 


redis-cli -a 비밀번호

127.0.0.1:6379> smembers persons  //persons 키에 주어진 요소들 반환

127.0.0.1:6379> scard persons  //persons키에 주어진 요소들의 개수 반환


그리고 Set데이터에서 값이었던 person:Hash_id는 각각의 Person Hash Data의 키 값이 된다. 키 하나에 여러 개의 필드-값의 쌍으로 구성된 Hash Data로 구성된다.

레디스 콘솔에서 "127.0.0.1:6379>keys *" 명령어로 persons:Hash_id 의 키 리스트를 확인할 수 있다. 

keys *  는 함부로 사용하면 안 된다. 해당 명령어를 수행하는 중에 레디스의 다른 작업은 중지 상태가 된다.

persons:Hash_id 는 HashData 의 Key이다. 레디스 콘솔에서 명령어는 h로 시작하는 명령어를 사용해서 데이터를 관리할 수 있다. 


혹시 여기까지 이해가 되었는가? 안되었는가? 필자의 글이 이상한가?  피드백을 부탁한다.


1. 이해가 안 되었다면 직접 Redis를 설치하고, 스프링 부트에 연동해보길 바란다.

2. 이해가 잘 되었고, 키 설계에 문제가 없다.  --> 감사하다.

3. 이해가 되었는데, 필자의 이론이 잘못되었다. --> 댓글로 피드백을 해주길 바란다. 


(필자는 레디스 초보&무식자이다. 이 글은 공유하는 목적보다는 피드백을 받기 위한 목적이 더 크다.)


HashData의 필드로 검색을 하고 싶다면 어떻게 해야 할까?

지금까지 작업으로 Java의 Person 에 매핑되는 데이터가 Redis 에 Hash Data로 저장되어있다. 각각의 Hash Data는 Hash Key를 통해서 찾을 수 있고, 해당 Hash Key는 persons이라는 Key 에 Set Data 에 저장이 되었다. 드디어 Person 의 리스트를 조회하는 것은 가능할 것 같다. 


근데, 만약 이름이 "홍길동"인 Person을 찾아야 한다면 어떻게 해야 할까?? 지금 상황으로는 두 가지 방법이 있는데 둘 다 좋지 않다.


1. 이름이 홍길동인 HashData의 Hash Key 값을 알고 있다면, 해당 HashKey 값으로 데이터를 바로 찾으면 된다. 

2. HashKey값을 모른다면, 모든 Person 데이터를 가져온 다음에, 가져온 데이터 중에서 name=홍길동 인 데이터를 다시 찾아야 한다. 


두 가지 방법 모두 심플한 방법은 아니다. 어떻게 하면 좋을까? Redis는 Key-Value 모델이라는 점을 잊지 말자.

Redis는 Key-Value 모델이다.


name이 "홍길동"인 데이터의 Hash Key 요소들을 하나의 Key의 Set Data 에 저장하면 된다. persons:name:홍길동 이라는 Key를 통해서 이름이 홍길동인 데이터 요소를 포함하고 있는 한 개의 Set Data를 찾을 수 있다. 


콘솔에서 127.0.0.1:6379> smembers persons:name:홍길동 를 실행하면 2개의 Hash Key를 찾을 수 있다. 물론 해당 HashKey로 두 명의 홍길동 데이터를 각각 찾을 방법이 생긴 것이다. 


자... 주저리주저리 했는데, 이걸 적용하기 위해서 Spring 의 Person 클래스에서 @Indexed 어노테이션을 적용하자. 


@RedisHash("persons")
public class Person implements Serializable {
    private static final long serialVersionUID = 1L;

    private @Id
    String id;


    private @Indexed
    String name;

    생략...


그리고 데이터를 저장하면,  "persons:name:이름" 의 Key 들이 생성될 것이다. 샘플 소스는 아래 링크를 참고하면 된다.


https://github.com/sieunkr/spring-data/tree/master/spring-data-redis



5장. [방법2] @Cacheable 활용, 직렬화 데이터 저장

4장에서는 Redis 의 HashMap 타입을 활용하였다. 5장에서는 HashMap 타입이 아닌 일반적인 ket/value 타입으로 저장하는 방법을 검토한다.


5.1 @EnableCaching, @Cacheable 

생략


5.2 레디스 연동

실제 레디스 연동 샘플을 간단하게 구현하였다. 


디펜던시


compile('org.springframework.boot:spring-boot-starter-data-redis')



@EnableCaching


@SpringBootApplication
@RequiredArgsConstructor
@EnableCaching
public class CacheableApplication

생략...


@Cacheable


@Cacheable(value = "person", key = "#nid")

public Map findByNid(String nid) {
    return personRepository.findByNid(nid);
}


레디스 저장 결과

아래와 같이 person::nid 의 Key 로 저장이 된다

해당 데이터는 직렬화 된 데이터가 저장되어 있다. get 하면, 아래와 같이 확인할 수 있다

 

허접한 필자의 샘플 코드는 아래와 같다.

https://github.com/sieunkr/spring-data/tree/master/spring-data-redis-cacheable


5.3 Serializable

생략




6장. 마무리

이번 글에서는 Java HashMap 데이터를 Redis 로 이전하기 위해서, Spring Boot 에서 Redis 를 연동하는 방법에 대해서 검토하였다. 주저리주저리 작성하였다. 역시 글을 쓰는 것은 어려운 일이다. 어쨋든... 해당 프로젝트는 검토만 하였고, 실제 업무에서 도입할 수는 없었다. 내부 플랫폼은 생각했던 것보다 더 복잡하게 얽혀 있고, 쉽게 개선할 수 있는 상황은 아니었다. 나중에 혹시라도 진행하게 되어서 잘 마무리 된다면 회사 공식 기술블로그에 올릴 예정이다. 개인 기술블로그(브런치)에는 회사 관련 프로젝트는 앞으로 발행하지 않을 예정이다.


해당 글에서 잘못된 내용이 있다면,  꼭 의견 부탁드립니다.
매거진의 이전글 IT업계 기술 블로그 리스트

작품 선택

키워드 선택 0 / 3 0

댓글여부

afliean
브런치는 최신 브라우저에 최적화 되어있습니다. IE chrome safari