Spring Data Redis(spring-data-redis)를 활용한 간단 개발
Spring Data Redis를 활용하여 간단한 Redis 연계 방법을 기록한다.
- 환경은 Spring Framework 4.2.x 이상
- Maven Project 환경을 가정한다.
- pom.xml의 dependency에 라이브러리 의존 추가
<!-- Spring Data Redis : Server Type -->
<dependency>
<groupId>redis.clients</groupId>
<artifactId>jedis</artifactId>
<version>2.7.3</version>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>1.7.5.RELEASE</version>
</dependency>
라이브러리 버전은 순전히 예시이며 실제 구성시 Redis 버전 및 Spring Framework 버전의 의존을 명확히 확인하여 적용한다.
만약에 당장 Redis 서버를 구성하기는 어렵고 당장 테스트한번 해보고 싶다면 좀 오래되었기는 했지만 Embedded Redis서버 라이브러리 의존을 추가하여 Java 코드를 통해 Redis 서버를 로컬에서 실행해볼 수 있다.
<dependency>
<groupId>com.github.kstyrc</groupId>
<artifactId>embedded-redis</artifactId>
<version>0.6</version>
</dependency>
Embedded Redis 라이브러리는 많이 오래된 Redis 서버를 내장하고 있다. 상용목적으로 사용하기에는 무리가 있다.
- Spring Context 설정(XML)
- Spring Context 설정 경로에 ‘context-cache-redis.xml’ 파일을 만들어서 아래와 같이 작성해본다.
각 속성에 대한 설명은 주석 참조 (어디까지나 ‘간단 개발’을 위한 예시이다)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:p="http://www.springframework.org/schema/p"
xmlns:cache="http://www.springframework.org/schema/cache"
xmlns:util="http://www.springframework.org/schema/util"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd
http://www.springframework.org/schema/util http://www.springframework.org/schema/util/spring-util.xsd">
<!--=======================================================================
= Spring Data Redis (Jedis Wrapper) 설정
==========================================================================-->
<!--
Redis Client
-->
<bean id="jedisConnFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<property name="hostName" value="127.0.0.1" /> <!-- Redis IP -->
<property name="port" value="6379" /> <!-- Redis Port -->
<!-- <property name="password" value="guest" /> 패스워드가 있을 경우 주석 해제 및 설정 -->
<property name="timeout" value="5000" /> <!-- Connection Timeout -->
<property name="usePool" value="true" /> <!-- Connection Pool 사용 여부 -->
<property name="poolConfig">
<bean class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="5" /> <!-- 최대 Connection Pool 사이즈 -->
<property name="maxWaitMillis" value="10000" /> <!-- Connection Pool 최대 대기 시간 -->
</bean>
</property>
</bean>
<!-- 만약에 Sentinel을 이용한 이중화 구성을 시도할 경우 아래 설정을 참고하자 (두 대의 Sentinel 예시)
<bean id="jedisConnFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">
<constructor-arg name="sentinelConfig">
<bean class="org.springframework.data.redis.connection.RedisSentinelConfiguration">
<property name="master">
<bean class="org.springframework.data.redis.connection.RedisNode">
<property name="name" value="실제 서버의 마스터 노드의 이름" />
</bean>
</property>
<property name="sentinels">
<set>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="Sentinel 1번 Host" />
<constructor-arg name="port" value="Sentinel 1번 포트" />
</bean>
<bean class="org.springframework.data.redis.connection.RedisNode">
<constructor-arg name="host" value="Sentinel 2번 Host" />
<constructor-arg name="port" value="Sentinel 2번 포트" />
</bean>
</set>
</property>
</bean>
</constructor-arg>
<property name="password" value="패스워드-없으면 주석처리" />
<property name="timeout" value="5000" />
<property name="usePool" value="true" />
<property name="poolConfig">
<bean class="redis.clients.jedis.JedisPoolConfig">
<property name="maxTotal" value="최대 Connection Pool 사이즈" />
<property name="maxWaitMillis" value="10000" />
</bean>
</property>
</bean>-->
<!--
JSON Redis template definition: Jackson2를 이용한 JSON 데이터 저장 Template
(POJO 또는 Map 객체를 JSON 형태로 Redis에 저장할 수 있다)
-->
<bean id="jsonRedisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="jedisConnFactory" />
<property name="enableTransactionSupport" value="false" />
<property name="keySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>
<property name="valueSerializer">
<bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer" />
</property>
<property name="hashKeySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>
<property name="hashValueSerializer">
<!-- 예전 버전은 POJO 타입 고정 이었지만(즉, 타입별로 Template 생성ㄷㄷ) 이제는 타입 자체를 Cache에 같이 저장한다 -->
<bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer" />
</property>
</bean>
<!-- Default Redis template definition: 예제 전용 (Redis에 객체를 직렬화하여 저장) -->
<bean id="defaultRedisTemplate" class="org.springframework.data.redis.core.RedisTemplate">
<property name="connectionFactory" ref="jedisConnFactory" />
<property name="enableTransactionSupport" value="false" />
<property name="keySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>
<property name="valueSerializer">
<bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
</property>
<property name="hashKeySerializer">
<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />
</property>
<property name="hashValueSerializer">
<bean class="org.springframework.data.redis.serializer.JdkSerializationRedisSerializer" />
</property>
</bean>
<!--
Redis Cache Manager를 이용한 개별 Cache 관리 및 @Cache Annotation 연동 예시
-->
<bean id="redisCacheManager" class="org.springframework.data.redis.cache.RedisCacheManager">
<constructor-arg ref="jsonRedisTemplate" />
<property name="cacheNames">
<list value-type="java.lang.String">
<description>@Cache Annotation과 연계하려는 Cache를 아래에 등록한다 (자동생성)</description>
<value>examlpleCache</value>
</list>
</property>
<property name="expires">
<map>
<description>위에 등록한 Cache이름 별로 Cache Expire Timeout을 설정해야 한다 (단위: 초)</description>
<entry key="examlpleCache" value="60" />
</map>
</property>
</bean>
<!--======================================================================
= Redis Cache 설정
=========================================================================-->
<!--
응용에서 사용하는 Cache Bean은 아래에 정의한다
('redisOperations' 속성은 객체 및 데이터 특성에 따라 설정한다: 객체를 JSON 형태로 Cache에 저장하는 경우 'jsonRedisTemplate' 설정)
-->
<!-- Sample Cache Bean -->
<bean id="redisSampleCache" class="org.springframework.data.redis.cache.RedisCache">
<!-- Cache Name -->
<constructor-arg name="name" value="sampleCache" />
<!-- Prefix: 반드시 Cache Name + ":" 으로 설정 -->
<constructor-arg name="prefix" value="sampleCache:" />
<!-- Rest Template 참조 -->
<constructor-arg name="redisOperations"><ref bean="defaultRedisTemplate" /></constructor-arg>
<!-- Cache 만료 시간: 초 -->
<constructor-arg name="expiration" value="60" />
</bean>
</beans>
- RedisCache.prefix 속성: Redis에 저장되는 Cache 데이터들의 Key 중복 위험을 막기 위해 Cache Key앞에 붙이는 Group 구분자. 포스트 작성자는 Cache name + “:” 값으로 설정할 것을 권장한다.
- 단일 Redis Database에서 여러 캐시를 상호 구분하여 사용해야 하는 경우 데이터 관리하기 용이하다.
- Redis 연계 서비스 클래스를 만드는 예시는 아래와 같다.
포스트 작성자는 간단한 개발에는 Spring Cache(org.springframework.cache.Cache == RedisCache) 추상 타입을 더 선호한다…
주석을 꼭 읽어봅시다
# CacheServiceImpl.java
/**
* <PRE>
* CacheService(Redis) 구현 클래스
* </PRE>
*
* @author 윤순혁
* @version 1.0
* @see CacheService
*/
@Service( "redisCacheService" )
public class CacheServiceImpl implements CacheService {
private static final Logger LOGGER = LoggerFactory.getLogger( CacheServiceImpl.class );
/**
* Redis Cache(sampleCache) Resource (Spring Cache 기본 인터페이스: 일반적인 업무기능 개발편의를 제공)
* 주의: RedisCache는 ValueOperation으로 동작한다.
*/
@Resource( name = "redisSampleCache" )
private Cache cache;
/**
* Redis Template Resource (Redis 연동과 관련된 직접적인 기능을 활용 가능)
*/
@Resource( name = "defaultRedisTemplate" )
private RedisTemplate<String, Object> redisTemplate;
/**
* Cache 초기화
*/
@PostConstruct
public void initializeCache() {
try {
// TODO 이곳에 Sample Cache 초기화 데이터를 추가하면 된다.
putData( "키 입니다", "값 입니다" );
} catch ( Exception ex ) {
LOGGER.error( "[Sample Cache] Cache Initialize Error", ex );
}
}
/* (non-Javadoc)
* @see com.lgcns.msp.guide.cache.service.CacheService#putData(java.lang.String, java.lang.Object)
*/
@Override
public <T> void putData( String key, T value ) {
// Spring Cache를 활용한 Redis 연동 예시
cache.put( key, value );
// Redis Template을 활용한 Value Operation 수행 예시
/*
* (필독)
* Redis Cache Key를 ':' 문자열을 붙인 패턴으로 저장하는 이유
* Redis Key를 구분자 및 인덱스로 활용 가능하기 때문에
* 만약 단일 Redis Database에서 여러 캐시를 상호 구분하여 사용해야 하는 경우 데이터 관리하기 용이하다.
* (참조: https://redislabs.com/blog/5-key-takeaways-for-developing-with-redis#.WDeob7KLQ1s)
* PS.
* 데이터 타입을 'Hash'로 하여 서로 다른 Cache를 키로 구분하고 데이터관리를 Map 형태로 처리하는 방법도 있다
* (단, 데이터가 급증 할 경우 Index Time 증가한다)
*/
redisTemplate.boundValueOps( Joiner.on( ':' ).join( cache.getName(), key ) ).set( value, 60, TimeUnit.SECONDS );
}
/* (non-Javadoc)
* @see com.lgcns.msp.guide.cache.service.CacheService#getData(java.lang.String, java.lang.Class)
*/
@Override
public <T> T getData( String key, Class<T> paramClass ) {
// Cache에서 Data를 가져온다
return cache.get( key, paramClass );
}
/* (non-Javadoc)
* @see com.lgcns.msp.guide.cache.service.CacheService#removeData(java.lang.String)
*/
@Override
public void removeData( String key ) {
// Spring Cache를 활용한 Redis 연동 예시
cache.evict( key );
// Redis Template을 활용한 Value Operation 수행 예시
redisTemplate.delete( Joiner.on( ':' ).join( cache.getName(), key ) );
}
}
-
테스트는 Controller 개발 또는 Mock 등을 통해 수행한다.
-
기타: 테스트를 위한 로컬에서 Embedded Redis 실행
-
‘1번’ 과정에서 Embedded Redis 라이브러리 의존을 추가했다면 아래와 같이 Java를 개발하여 실행 가능하다.
-
주의: Embedded Redis 라이브러리 원리는 linux/windows redis 파일을 내장하고 있다가 임시 경로에 복사하고 실행하는 Java Process Wrapper이다. 따라서 Java 프로그램만 Kill할 경우 Redis 프로세스는 수동으로 Kill 해야한다. 그러니 Redis 정지도 함수 호출을 통해 정지한다.*
# EmbeddedRedisServer.java
/**
* <PRE>
* Simple Embedded Redis Server
* 주의1: 로컬 테스트 목적 이외에는 사용 금지 (프로젝트 서버 목적으로 사용할 수 없음)
* 주의2: 반드시 명령어 입력을 통해서 프로그램을 종료해야 합니다.
* </PRE>
*
* @author 윤순혁
* @version 1.0
*/
public class EmbeddedRedisServer {
private static final int REDIS_PORT = 6379;
public static void main( String[] args ) {
try (Jedis jedis = new Jedis( "127.0.0.1", REDIS_PORT ); Scanner input = new Scanner( System.in )){
RedisServer redisServer = RedisServer.builder()
.port( REDIS_PORT )
.setting( "daemonize no" )
.setting( "appendonly no" )
.build();
redisServer.start();
System.out.println( "\nEmbedded Redis Server Started" );
System.out.println( "==============================================================================" );
System.out.println( "주의사항: 반드시 명령어 입력을 통해서 프로그램을 종료해야 합니다." );
System.out.println( " (그렇지 않으면 Redis 데몬을 '작업관리자'에서 프로세스 종료 시켜야 함)" );
System.out.println( "==============================================================================" );
System.out.println( "기능 (콘솔 창에 명령어 입력 후 Enter)" );
System.out.println( "Redis 상태보기: status" );
System.out.println( "Redis 종료: stop" );
System.out.println( "==============================================================================" );
String inputLine = "";
jedis.connect();
while ( !inputLine.equals( "stop" ) ) {
System.out.print( "[기능 입력] # " );
inputLine = input.nextLine();
switch ( inputLine ) {
case "stop":
redisServer.stop();
System.out.println( "\nEmbedded Redis Server Stopped" );
break;
case "status":
System.out.println( jedis.info() );
break;
default:
break;
}
}
System.exit( 0 );
} catch ( Exception redisError ) {
System.err.println( "Embedded Redis Server error: " + redisError.getMessage() );
}
}
}