지난글에 이어 이번에는 원래  REST 라이브러리를 이용해서 SpringBoot기반  API 어플리케이션을  개발하기 위한 환경을 구성하는 걸로 쓸려고 했는데, 
그전에 NonSQL로 많이 알려져 있는 Redis설정에 대해서 잠시 짚고 가려고 한다.


Redis 소개


Redis는 이미 많이 알려진 기술이지만 간단히 소개하자면,
in-memory 기반의 데이타 스트럭처 저장기술로  database 서버, 데이타캐싱 및 메세시 구독/발행(subscribe/publish)등의 메세지브로커 기술로도  활용이 가능한 시스템이라 할 수 있다.

Redis 사이트에 있는 소개글이다.
Redis is an open source (BSD licensed), in-memory data structure store, used as a database, cache and message broker. It supports data structures such as strings, hashes, lists, sets, sorted sets with range queries, bitmaps, hyperloglogs and geospatial indexes with radius queries. Redis has built-in replication, Lua scripting, LRU eviction, transactions and different levels of on-disk persistence, and provides high availability via Redis Sentinel and automatic partitioning with Redis Cluster.

장점
저장하는 데이터구조가 key-value방식의 단순한 구조로 되어 있고, 메모리에 저장되는 방식이라 저장속도가 빠르다.
클러스터링, 샤딩 및 Master-Slave방식의 이중화도 지원하기 때문에 고가용성-high availability-을 제공한다.
다양한 플랫폼을 지원하며, 현존하는 대부분의 언어로 클라이언트 API를 제공한다. 


잠시, 본인이 현재 실무에서 Redis를 사용하고 있는 환경에 대해서 도움이 혹시 될까 공유한다.

Redis는 현재 본인이 관리하고 있는 API서버 - SpringBoot기반 -의 이력관리를 목적으로 사용하고 있다.
10개정도의 외부의 시스템들이 API서버를 통해 데이타를 주고받고 있다보니 트러블발생시 데이타확인 및 재처리등의 FailOver를 위해 
이력데이타의 관리가 꼭 필요한 상황이었다.

기존까지는 오라클에 직접 저장하는 방식으로 사용하였는데  송수신전문전체를 저장하기 위해서 
어쩔 수 없이 CLOB(오라클의 대형문자 저장유형)을 써야 했고, 대량의 이력데이타가 계속 누적되면서 DB서버 전반에 걸쳐 성능에 안 좋은 영향을 주게 되었다.
특히, 트러블발생시 이력데이타를  조회해서 확인이 필요한데,  query의 조건을 잘못 주고( 예를 들면 한달치 기간) 실행하는 경우  먹통이 되는 경우도 발생한다.

- API서버의 API송수신 데이타유형은 모두 JSON
- 전문내용을 검색할 필요가 없으며, 시스템간 트러블발생시 요청/응답전문의 확인목적으로 JSON 구조그대로 저장필요.
- 일정기간 보관후 삭제필요.  
- 호출시스템별  API사용빈도, 전송데이타건수 등의 집계성데이타 필요. 
- 이력관리시 API서버의 본 처리성능에 영향을 최소화해야 함

전제조건을 이렇게 정리하고 검토한 결과 Redis를 활용하는게 적합하다는 판단을 내렸다. 
데이타를 일정기간만 보관하는 부분은 날짜별 이력key를 정의, expire API를 사용하여 key의 유효기간을 지정하는 방법으로 처리했다.

잠시 다른 이야기로 샌것 같은데, 현재 업무에서 적용된 상세 내용은 기회가 되면 별도로 다루어보도록 하겠다.



1. 스프링부트 Redis 라이브러리 추가

아래와 같이 메이븐 파일(pom.xml)에 아래처럼 redis라이브러를 추가한다.
1
2
3
4
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-redis</artifactId>
</dependency>
cs

spring-boot-starter-redis은  내부적으로 아래와 같이 하위 라이브러리 의존성을 관리하고 있다는 점.
따라서 위처럼만 설정하면 하위의 의존 라이브러들을 같이 가져오게 된다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
<dependencies>
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter</artifactId>
  </dependency>
  <dependency>
    <groupId>org.springframework.data</groupId>
    <artifactId>spring-data-redis</artifactId>
  </dependency>
  <dependency>
    <groupId>redis.clients</groupId>
    <artifactId>jedis</artifactId>
  </dependency>
</dependencies>
cs

스프링부트 기반에서는 스프링부트가 의존성 관리를 하는 라이브러리에 대해서는 버전을 신경쓸필요가 없다.
Redis 라이브러리를 추가하면 스프링부트 버전에 맞는 버전의 Redis 라이브러리들이 자동으로 추가된다.

스프링부트를 쓰다 보면 이런 편리함과  장점들이 계속 나타나기 때문에 기존방식으로 돌아갈 필요성을 전혀 느끼지 못한다.




2. Redis Configuration 설정

스프링부트 메인클래스가 Configuration 클래스이기 때문에,  Redis Configuration설정을 메인클래스의 @Bean 메소드로 추가해도 되지만
향후 소스관리측면에서나, 리팩토링측면에서 별도의 Redis용 @Configurationq빈 클래스를 따로 만들어 관리하는게 좋을 것이다.

개인적으로는 스프링부트의 메인클래스는 가능하면 기본상태로 두고,
필요에 따라 추가되는 각 모듈에 대한 설정은 별도의 @Configuration클래스를 만들어서 관리하는게 좋은 방법이라고 생각한다.

기존의 xml기반 Spring 프로젝트의 방식으로 본다면,
application-context.xml을 모듈별로 분리하는냐, 통합해서 하나로 관리하느냐의 차이이다.

아래와 같이 Redis용 설정클래스를 만든다.
나중에 추가될 모듈들의 설정클래스들을 따로 관리하기 위해 config패키지를 만들어 그 안에 생성했다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
package com.springboot.demo.config;
 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.jedis.JedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import redis.clients.jedis.JedisPoolConfig;
 
@Configuration
public class RedisConfig {
 
  @Bean
  public JedisPoolConfig jedisPoolConfig() {
    JedisPoolConfig jedisPoolConfig = new JedisPoolConfig();
    return jedisPoolConfig;
  }
 
  @Bean
  public JedisConnectionFactory jedisConnectionFactory(JedisPoolConfig jedisPoolConfig) {
    JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(jedisPoolConfig);
    return jedisConnectionFactory;
  }
 
  @Bean(name="redisTemplate")
  public RedisTemplate redisTemplateConfig(JedisConnectionFactory jedisConnectionFactory) {
    RedisTemplate redisTemplate = new RedisTemplate();
 
    redisTemplate.setKeySerializer(new StringRedisSerializer());
    redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
    redisTemplate.setConnectionFactory(jedisConnectionFactory);
 
    return redisTemplate;
  }
}
cs
RedisConfig.java

전통적인 DatatSource의 설정과 크게 다르지 않다. Redis커넥션풀 빈을 만들고, Redis커넥션팩토리에 주입해서 커넥션 팩토리 빈을 만들어 RedisTemplate빈에 주입,
RedisTemplate 인스턴스를 생성하여 빈으로 등록한다.

Redis클라이언트(이하 jedis)도 커넥션팩토리와 커넥션 풀설정을 모두 지원한다.

jedis가 제공하는 커넥션팩토리와 커넥션 클래스의 프로퍼티를 확인해보면 Redis기본값을 설정되어 있고, 속성값들은 개발환경에 맞게 변경할 수 있다.

설치되어 있는 Redis서버의 주소나  포트가 다르게 설정되어 있는경우,아래와 같이 수정하면 된다. 
JedisPoolConfig 나 JedisConnectionFactory 클래스를 보면, 설정할 수 있는 Property를 확인 할 수 있다.

JedisConnectionFactory jedisConnectionFactory = new JedisConnectionFactory(jedisPoolConfig);
jedisConnectionFactory.setHostName(“서버호스트명”);
jedisConnectionFactory.setPort(포트번호);
이런식으로 각자의 환경에 맞게 속성값을 설정하면 된다.

Serializer 설정
Redis의 key, value에 데이터 저장시 적용할 Serializer를 지정한다.
key는 일반 문자열을 쓸테니까 StringRedisSerializer 로 지정, value는 JSON형태로 활용해야 하므로 Java Object - > JSON 변환을 지원하는 GenericJackson2JsonRedisSerializer 로 지정했다.

redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());


데이타를 저장할때 Java오브젝트의 구조를 그대로 넣어야 할 필요가 있거나, 
최종적으로 꺼내서 제어,전달해야 할 데이타형태가 JSON일 경우에는  GenericJackson2JsonRedisSerializer를 쓰는것이 좋다

value에 단순한 문자열만 관리된다면  StringRedisSerializer를 써도 상관없다.


주의해야 할 점.
Selializer를 명시하지 않으면 RedisTemplate는 key, value모두 Default Serializer로 JdkSerializationRedisSerializer를 사용한다.
이 시리얼라이즈는 key에 지정할 경우 아주 치명적인(?) 문제가 있는데, key를 생성할때 내부적으로 key값 앞에 아래와 같은 UniCode가 붙어버린다.

\xac\xed\x00\x05t\x00\x03 + key

예를 들어 key를 “spring”으로 만들었다면, 실제 key는 "\xac\xed\x00\x05t\x00\x03spring"으로 만들어진다.
redis-cli에서 “keys spring”로 key가 조회되지 않는다.  
"keys \xac\xed\x00\x05t\x00\x03spring”으로 해야 조회된다. 
자바 소스내에서는 Redis 클라이언트 라이브러리로 key이름을 “spring”으로 조회하면 또 정상적으로 조회된다. (도대체 이건 뭐지...)

Java 프로그램에서는 조회되고 redis-cli로 조회되지 않고,... 이런 현상을 몰라 정말 한참을 해맸던 경험이 있다.
뭐. 이유는 궁금하지도 않고,  key에 지정할 Serializer는 무조건 JdkSerializationRedisSerializer는 피하는게 좋다.
가급적 꼭 써야할 Serializer를 명시하도록 하자.



3. RedisTemplate 빈 사용.

2번의 @Configuration 클래스를 추가함으로써 Redis를 사용할 준비는 끝났다. (Bean간의 설정은 모두 SpringBoot의 AutoConfiguration이 알아서 해준다.)

이제 비즈니스 로직을 구현할  클래스내에서 redisTemplate 빈을 주입(DI)받아 쓰면 된다.

그전에 정상적으로 레디스 설정이 되었는지 테스트를 해보자.
아래처럼 간단하게 테스트 클래스를 만든다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
package com.springboot.demo;
 
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.test.context.junit4.SpringRunner;
 
@RunWith(SpringRunner.class)
@SpringBootTest
public class RedisTest{
 
    @Autowired
    private RedisTemplate redisTemplate;
 
    @Test
    public void testDataHandling() throws Exception {
 
        String key = "key:springboot";
        redisTemplate.opsForValue().set(key, "Hello");
        String value = (String)redisTemplate.opsForValue().get(key);
 
        Assert.assertEquals("Hello", value);
    }
}
cs

테스트를 수행하기전에 우선 Redis서버를 구동한다.
Redis서버가 설치되어 있지 않다면, 아래의 사이트에 가서 다운받은 후 설치한다.


Redis를 쓰게 되면 redis-cli에서 명령을 실행해야 할 일이 많이 생기는데, 
https://redis.io/commands 페이지를 잘 활용할 수 있도록 하자. 
Redis클라이언트 터미널에서 사용할 수 있는 모든 명령어가 설명과 함께 상세히 설명되어 있고, 명령어를 직접 화면에서  테스트해볼 수 있다.

Redis서버를 설치하고 구동하였으면 테스트를 수행한다. Default로 호스트명은 "localhost", 포트는 "6379"일 것이다.

IDE 화면으로 돌아와서 테스트를 수행한다. 테스트를 수행하면 스프링부트가 구동되고 테스트 메서드의 내용이 실행된다.
스프링부트기반의 JUnit테스트 클래스는 반드시 아래의 어노테이션이 있어야 한다.
@RunWith(SpringRunner.class)
@SpringBootTest

테스트 콘솔창에  초록색 바가 보이면 정상.

굳이 Redis의 커넥션 여부를 테스트할 필요가 없다.
SpringBoot가 시작될때, 즉, RedisTemplate 빈이 Spring컨텍스트에 등록될때는 Redis커넥션 수행 후 등록되기 때문에
커넥션에 문제가 있는 경우, SpringBoot가 정상적으로 실행되지 않는다.

이제 Redis를 사용해야 할  비즈니스 로직 클래스에서 redisTemplate Bean을 주입받아 사용하면 된다.

테스트에서는 Redis에서 제공하는 데이타 유형중 가장 간단한 SetOperation(단일값유형) 유형으로 테스트를 해보았는데,
주로 사용할 데이타 유형은 하나의 Key에 데이타가 누적으로 저장되야 하는 방식이므로 비즈니스로직에서는 ListOperation 유형을 반환하는  opsForList API를 사용하게 될 것이다.

참고로 Redis가 지원하는 Data Structure는 크게 다섯가지 유형으로 구분돤다.

1.  strings 
2. hashes 
3. lists
4. sets
5. sorted sets
6. geo, hyperloglog (써보지 않아서 모른다.)

각각의 유형에 대해서는 https://redis.io/commands에 보면 자세히 설명되어 있다.

위의 테스트에서는 1번 유형을 사용하였고, 실제로는 3번 lists 유형을 사용할텐데, 저장된 Object를 자바의 List객체로 얻을 수 있는 range API를 제공한다.

다음 글에는 opsForList를 이용해서 Java의 List유형을 데이타를 저장하는 방법과 저장된 List를 꺼내서 화면에 조회되도록 하는 방법등을 다뤄볼 예정이다. 



  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기