Tool/Redis

[Redis] Spring Framework에서 Redis 사용하기 jedis

Jeong Jeon
반응형

우선 현재 프로젝트에서 Redis를 사용하여, 사용자 인증 토큰, 캐싱, 기기 캐싱 및 여러 데몬끼리의 Push를 주고 받는 기능을 구현하였다.

 

본인이 프로젝트에 투입되기 전부터 Jedis Client를 사용하고 있었기때문에, 아래와 같이 정리해 두려고한다.

하지만, java Redis client에 Lettuce라는 아이가 있는데, 이아이의 성능이 훨씬 우월하다고 한다..!(직접 테스트는 안해봤다..)

참조 : https://jojoldu.tistory.com/418

 

Jedis 보다 Lettuce 를 쓰자

Java의 Redis Client는 크게 2가지가 있습니다. Jedis Lettuce 둘 모두 몇천개의 Star를 가질만큼 유명한 오픈소스입니다. 이번 시간에는 둘 중 어떤것을 사용해야할지에 대해 성능 테스트 결과를 공유하

jojoldu.tistory.com

다음 프로젝트때에는 Lettuce를 조금더 확실하게 조사해본뒤, 선택하여 사용해 보아야겠다. (현재는 불가능하니까...)

일단 알아두는것이 가장 중요하다!!!

 

 

성능 비교

  TPS RedisCPU RedisConnection 응답 속도
jedis no connection pool 31,000 20% 35 100ms
jedis use connection pool 55,000 69.5% 515 50ms
lettuce 100,000 7% 6 7.5ms

Lettuce는 TPS/CPU/Connection 개수/응답속도 등 전 분야에서 우위에 있다.

 

 

 

1). pom.xml

  • Redis를 사용하기 위해 Dependency를 추가한다.
    • spring-data-redis
      • Redis 인터페이스를 제공하는 스프링 모듈
      • CrudRepository를 지원하기 때문에 좀 더 직관적으로 사용 가능하다
    • jedis
      • Jedis는 Redis를 자바에서 쉽게 사용할 수 있게 도와주는 라이브러리
      • jedis는 검색해보면 사용법이 쉽게 나와있다.
    • jackson-core-asl
      • Json 형태의 메세지의 toString을 사용하기위해 적용? -> 조금더 알아보자
<!-- redis -->

<dependency>

<groupId>org.springframework.data</groupId>

<artifactId>spring-data-redis</artifactId>

<version>1.8.6.RELEASE</version>

</dependency>

<dependency>

<groupId>redis.clients</groupId>

<artifactId>jedis</artifactId>

<version>2.9.0</version>

</dependency>

<dependency>

<groupId>org.codehaus.jackson</groupId>

<artifactId>jackson-core-asl</artifactId>

<version>1.9.12</version>

</dependency>

 

2). root-context.xml

  • jedisPoolConfig
    •  Autowire를 위한 bean id
    • jedis의 Connection pool을 사용할수 있도록 한다.
  • jedisConnectionFactory
    • jedis의 Connection pool을 생성한다. (jdbc와 비슷)
    • redis에 접근할 수 있는 정보들을 property에 담아 연결시켜준다.
  • jedisConnectionFactoryDataBase0
    • ConnectionPool에서 0번 database에 접근할 pool을 지정해준다.
  • jedisConnectionFactoryDataBase1
    • 1번 database에 접근할 pool을 지정해준다.
<bean id="jedisPoolConfig" class="redis.clients.jedis.JedisPoolConfig" />

<bean id="jedisConnectionFactory" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">

<property name="usePool" value="${test.redis.usePool}" />

<property name="hostName" value="${test.redis.hostName}" />

<property name="port" value="${test.redis.port}" />

<property name="poolConfig" ref="jedisPoolConfig" />

<property name="password" value="${test.redis.password}" />

<property name="database" value="0"/>

</bean>

<bean id="jedisConnectionFactoryDataBase0" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">

<property name="usePool" value="${test.redis.usePool}" />

<property name="hostName" value="${test.redis.hostName}" />

<property name="port" value="${test.redis.port}" />

<property name="poolConfig" ref="jedisPoolConfig" />

<property name="password" value="${test.redis.password}" />

<property name="database" value="0"/>

</bean>

<bean id="jedisConnectionFactoryDataBase1" class="org.springframework.data.redis.connection.jedis.JedisConnectionFactory">

<property name="usePool" value="${test.redis.usePool}" />

<property name="hostName" value="${test.redis.hostName}" />

<property name="port" value="${test.redis.port}" />

<property name="poolConfig" ref="jedisPoolConfig" />

<property name="password" value="${test.redis.password}" />

<property name="database" value="1"/>

</bean>

  • redisTemplate
    • 각 connection pool(DB)마다의 설정을 해준다.
    • keySerializer
      • Redis 자료구조에서 String Type의 Key에 대한 설정 Property를 잡아준다.
    • valueSerializer
      • Redis 자료구조에서 Hash Type의 Value에대한  json 자동 변환을 설정한다. (key:value를 생각하면 이해가 쉬울것이다.)
    • hashKeySerializer
      • Redis 자료구조에서 Hash Type의 Key에 대한 설정 Property를 잡아준다.
    • hashValueSerializer
      • Redis 자료구조에서 Hash Type의 Value에대한  json 자동 변환을 설정한다.
<bean id="redisTemplate" class="org.springframework.data.redis.core.RedisTemplate">

<property name="connectionFactory" ref="jedisConnectionFactory" />

<!-- 키는 스트링으로 고정 -->

<property name="keySerializer">

<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />

</property>

<!-- 값은 json 자동 변환 설정 지원 -->

<property name="valueSerializer">

<bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer">

<constructor-arg value="java.lang.Object" />

</bean>

</property>

<!-- 키는 스트링으로 고정 -->

<property name="hashKeySerializer">

<bean class="org.springframework.data.redis.serializer.StringRedisSerializer" />

</property>

<!-- 값은 json 자동 변환 설정 지원 -->

<property name="hashValueSerializer">

<bean class="org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer">

<constructor-arg value="java.lang.Object" />

</bean>

</property>

</bean>

  • messageListener
    • 키가 삭제 혹은 추가 될때 이벤트를 Listen하게 하여 Controll 할수 있다.
    • 예시는 RedisMessageListener.java를 참고하자
  • redisContainer
    • RedisMessageListenerContainer는 Redis의 channel로 부터 메시지를 수신받아 해당 MessageListenerAdapter에 dispatch하게 되고 역할은 다음과 같다.
    • TaskExecutor를 이용하여 등록된 리스너에게 비동기적으로 메시지 dispatch
    • 하나의 connection과 하나의 thread를 등록된 여러 리스너가 공유
    • RedisConnection과 같은 resource 생성 및 release
    • Exception 발생시 Jedis Exception을 Spring Exception으로 변환
    • 현재는 키가 삭제될 때의 Event를 Listen 하여 사용하였다.
<bean id="messageListener" class="org.springframework.data.redis.listener.adapter.MessageListenerAdapter">

<constructor-arg>

<bean class="com.test.util.RedisMessageListener"/>

</constructor-arg>

</bean>

<bean id="redisContainer" class="org.springframework.data.redis.listener.RedisMessageListenerContainer">

<property name="connectionFactory" ref="jedisConnectionFactory"/>

<property name="messageListeners">

<map>

<entry key-ref="messageListener">

<bean class="org.springframework.data.redis.listener.PatternTopic">

<constructor-arg value="__keyevent@*__:expired" />

</bean>

</entry>

</map>

</property>

</bean>

 

 

3). RedisMessageListener.java

  • 키의 삭제, 추가 등 설정을 할수있다.
  • 현재는 키가 삭제될때의 로그를 쌓을때 사용한다.
import javax.annotation.Resource;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.data.redis.connection.Message;

import org.springframework.data.redis.connection.MessageListener;

import org.springframework.data.redis.core.HashOperations;

import org.springframework.data.redis.core.ValueOperations;

import org.springframework.transaction.annotation.Propagation;

import org.springframework.transaction.annotation.Transactional;

import org.springframework.transaction.interceptor.TransactionAspectSupport;

import org.springframework.util.StringUtils;

import com.test.cms.account.dao.AccountDao;

import com.test.cms.account.vo.AccountVo;

import com.test.cms.constant.Constant;

import com.test.cms.log.dao.LogDao;

import com.test.cms.log.vo.LogVo;

public class RedisMessageListener implements MessageListener {

@Autowired

LogDao logDao;

@Autowired

AccountDao AccountDao;

@Resource(name="redisTemplateDataBase0")

private ValueOperations<String, Object> valueOps;

@Resource(name="redisTemplateDataBase0")

private HashOperations<String, String, Object> hashOps;

/**

* @desc redis 키가 삭제될때 호출 키를 받아서 유저 세션 데이터일경우 로그를 쌓는다.

*/

@Transactional(

propagation=Propagation.REQUIRED,

rollbackFor=Throwable.class)

@Override

public void onMessage(Message message, byte[] pattern){

	try {	

		if(message.toString().startsWith("USER_")) {

			//캐쉬로 부터 정보를 얻어와 로그를 쌓고 캐쉬도 삭제한다.

			AccountVo accountVo = (AccountVo)hashOps.get(Constant.REDIS_USER_CASH, message.toString());

			if(!StringUtils.isEmpty(accountVo)){

				LogVo logVo = new LogVo();

				logVo.setAccountId(accountVo.getAccountId())

				.setLoginActionType(Constant.STATE_TYPE_SESSION_TIMEOUT)

				.setLoginDeviceType(accountVo.getLoginDeviceType())

				.setLoginIp(accountVo.getLoginIp())

				.setCompanyId(accountVo.getCompanyId())

				.setAccountTypeId(accountVo.getAccountTypeId());

				int result = logDao.insertLoginLog(logVo);

				if(result > 0) {

					hashOps.delete(Constant.REDIS_USER_CASH, message.toString());
				}
			}
		}
	}catch (Exception e) {

		e.printStackTrace();

		TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
	}

}

}

 

 

4). Redis사용처.java

  • 1). Spring에서 제공하는 RedisTemplate을 사용하여 Redis Controlling
    • 1. 1번 Database의 List DataType 사용 :  ListOperations<String, String>
    • 2. 2번 Database의 Hash DataType 사용 :  HashOperations<String, String, Object>
    • 3. 0번 Database의 String DataType 사용 : ValueOperations<String, Object>

 

@Autowired

RedisTemplate redisTemplateDataBase2;

1.

@Resource(name="redisTemplateDataBase1")

private ListOperations<String, String> listOpsDataBase1;

2.

@Resource(name="redisTemplateDataBase2")

private HashOperations<String, String, Object> hashOpsDataBase2;

3.

@Resource(name="redisTemplateDataBase0")

private ValueOperations<String, Object> valueOpsDataBase0;

 

  • 2). 각 Redis DataType에 따른 jedis 명령어
jedisPoolConfig = new JedisPoolConfig();

pool = new JedisPool(jedisPoolConfig, hostName, Integer.parseInt(port), Integer.parseInt(timeout), password);

jedis = pool.getResource();

//1번 디비 사용 -> 1번 DB의 ConnectionPool을 사용한다.

jedis.select(1);



//데이터 입력

jedis.set("jeong", "pro");

//데이터 출력

System.out.println(jedis.get("jeong"));//pro

//데이터 삭제

jedis.del("jeong");

System.out.println(jedis.get("jeong"));//null



/* Lists 형태 입출력 */

jedis.lpush("id", "George");

jedis.lpush("password", "123123");

System.out.println(jedis.rpop("id"));//George

System.out.println(jedis.rpop("password"));//123123



/* Sets 형태 입출력 */

jedis.sadd("nicknames", "George");

jedis.sadd("nicknames", "Gee");

jedis.sadd("nicknames", "George");

Set<String> nickname = jedis.members("nicknames");

Iterator iter = nickname.iterator();

while(iter.hasNext()) {

System.out.println(iter.next());

}



/* Hashes 형태 입출력 */

jedis.hset("user", "name", "George");

jedis.hset("user", "job", "software engineer");

jedis.hset("user", "hobby", "coding");



System.out.println(jedis.hget("user","name"));//George

Map<String, String> fields = jedis.hgetAll("user");

System.out.println(fields.get("job"));//software engineer

 

반응형