spring boot에서 redis 사용하는 방법
지난 포스트 중 자바에서 redis(Jedis)를 사용한 포스트가 있었다.
jedis 라이브러리를 직접 불러와서 사용했었지만 이번에는 spring boot에서 starter로 제공하는 라이브러리로 사용하는 것을 해봤다.
이것 또한 jedis를 사용하는 것과 다르지 않다.
spring에서 redis와 관련한 라이브러리를 추상화시켜서 사용할 수 있게 해준 것이다.
무슨말이냐면 자바에서 주로 사용하는 redis 라이브러리로는 jedis와 lettuce가 있다.
두 라이브러리를 공통으로 추상화해서 둘 중에 어떤 것을 쓸 것인지는 설정을 통해 사용할 수 있게 하는 것이다.
바로 사용해본다.
Get, Set
Redis는 In Memory key/value Database로 NoSQL DB다. 특히 자바에서는 캐시나 세션 관리, pub/sub 메세지 처리로 쓰인다.
기본적인 get,set부터 시행착오를 함께한다.
1. Maven dependency를 추가한다.
1 2 3 4 | <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency> | cs |
2. setting을 위한 Configuration bean 적용
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | @Configuration public class RedisConfiguration { @Bean public RedisConnectionFactory redisConnectionFactory() { LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(); return lettuceConnectionFactory; } @Bean public RedisTemplate<String, Object> redisTemplate() { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory()); redisTemplate.setKeySerializer(new StringRedisSerializer()); return redisTemplate; } } | cs |
RedisConnectionFactory 인터페이스를 확장한 LettuceConnectionFactory 또는 JedisConnectionFactory를 생성해서 빈으로 등록하면 된다.
(* 참고로 springboot 2.0이상부터는 auto-configuration으로 위의 빈(redisConnectionFactory, RedisTemplate, StringTemplate)들이 자동으로 생성되기 때문에 굳이 Configuration을 만들지 않아도 즉시 사용가능하다.)
말 그대로 Redis에 연결하기위한 Connection 설정을 위한 객체를 만든다고 생각하면 된다.
또한 lettuceConnectionFactory.setHost("192.168.0.78")로 지정할 수 있고 마찬가지로 .setPassword("password");를 사용해서 redis 연결에 필요한 설정을 줄 수 있다.
하지만 권장하지 않는 방법이고 application.properties나 .yml 같은 설정파일에서 설정하는 것이 더 좋다.
1 2 3 4 5 | spring.redis.lettuce.pool.max-active=10 spring.redis.lettuce.pool.max-idle=10 spring.redis.lettuce.pool.min-idle=2 spring.redis.port=6379 spring.redis.host=127.0.0.1 |
위의 설정을 application.properties 에서 설정한 예제다.
다시 돌아가서 RedisTemplate<String, Object>는 get/set을 위한 객체다.
실제로 사용할 때는 RedisTemplate로부터 Operation객체를 받아 사용한다.
Operation객체는 기본적인 value나 list, hashMap등을 위한 Operation이 따로 있다.
3. 실제 사용
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @Service public class GetSetService { @Autowired RedisTemplate<String, Object> redisTemplate; public void test() { //get/set을 위한 객체 ValueOperations<String, Object> vop = redisTemplate.opsForValue(); vop.set("jdkSerial", "jdk"); String result = (String) vop.get("jdkSerial"); System.out.println(result);//jdk } } | cs |
일반적으로는 어떻게 사용하는지는 모르겠다.
어떤 예제에서는 @Resource로 Operation을 주입받아 사용한다.
그런데 필자는 개인적으로 직관성을 위해 등록한 빈 자체를 가져와서 직접 Operation을 꺼내쓰는 방법을 택했다.
위의 예제에서는 메서드에서 꺼내썼지만 해당 service의 멤버 변수에 등록해서 사용하는게 좋아보인다.
위와 같이 간단한 설정만 하면 쉽게 데이터를 넣고 뺄 수 있다.
그런데 문제가 있다. 자바에서만 사용하면 상관없는데 redis-cli를 켜서 실행해보면 이상한 데이터가 들어가있다.
이런 이유는 redis가 기본적으로 바이트 배열로 데이터를 저장하기 때문인것도 있고, 자바가 serialize하는 과정에서 "jdk"라는 데이터만 저장하는 것이 아니라 데이터의 클래스 정보도 추가로 저장하기 때문이다.
이 문제를 해결하는 방법은 serialize를 다른 것으로 지정해주고 json같은 방식을 이용하면 된다.
자바 직렬화 문제인데 이것에 대해서는 우아한형제들 기술블로그에 잘 설명이 되어있다.
http://woowabros.github.io/experience/2017/10/17/java-serialize2.html
테스트해본다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | @Configuration public class RedisConfiguration { @Bean public RedisConnectionFactory redisConnectionFactory() { LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(); return lettuceConnectionFactory; } @Bean public RedisTemplate<String, Object> redisTemplate() { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory()); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(DataData.class)); return redisTemplate; } } | cs |
변경된 점은 redisTemplate에 .setValueSerializer에 Jackson2JsonRedisSerializer로 주고받을 데이터를 미리 설정해놓은 것이다.
(DataData 라고 만든 임의의 DTO 클래스는 아래와 같다)
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 | public class DataData implements Serializable{ private static final long serialVersionUID = 1L; private String sourceId; private String itemId; public String getSourceId() { return sourceId; } public void setSourceId(String sourceId) { this.sourceId = sourceId; } public String getItemId() { return itemId; } public void setItemId(String itemId) { this.itemId = itemId; } public DataData(String sourceId, String itemId) { super(); this.sourceId = sourceId; this.itemId = itemId; } public DataData() {} @Override public String toString() { return "DataType [sourceId=" + sourceId + ", itemId=" + itemId + "]"; } } | cs |
변경된 service는 아래와 같다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | @Service public class GetSetService { @Autowired RedisTemplate<String, Object> redisTemplate; public void test() { //get/set을 위한 객체 ValueOperations<String, Object> vop = redisTemplate.opsForValue(); //자료형 생성 DataData setData = new DataData(); setData.setItemId("jeong"); setData.setSourceId("pro"); //set vop.set("key", setData); DataData getData = (DataData) vop.get("key"); System.out.println(getData.getItemId());//jeong System.out.println(getData.getSourceId());//pro } } | cs |
결과는 아래와 같다.
redis-cli를 이용해서 확인해보면 자바가 아닌 다른곳에서도 사용할 수 있게 json형태로 잘 들어가 있는 것을 확인할 수 있다.
Publish, Subscribe
pub/sub은 말 그대로 어떤 서비스는 어떤 메시지를 Publish 할 수 있고(일종의 생산자) 어떤 서비스는 그 메시지를 받기 위해 subscribe를 등록하는 구조다.
이것으로 어떤 이벤트가 발생했을 때 알아채고 어떤 action을 하는 예제를 만들어 본다.
여기서는 subscribe하는 부분만 만들어본다. publish는 다른 블로그를 참조한다.(예제에서는 redis-cli사용)
(옵저버패턴과 비슷하게 이해하면 된다.)
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 | @Configuration public class RedisConfiguration { @Bean public RedisConnectionFactory redisConnectionFactory() { LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(); return lettuceConnectionFactory; } @Bean public RedisTemplate<String, Object> redisTemplate() { RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>(); redisTemplate.setConnectionFactory(redisConnectionFactory()); redisTemplate.setKeySerializer(new StringRedisSerializer()); redisTemplate.setValueSerializer(new Jackson2JsonRedisSerializer<>(DataData.class)); return redisTemplate; } @Bean MessageListenerAdapter messageListenerAdapter() { return new MessageListenerAdapter(new RedisService()); } @Bean RedisMessageListenerContainer redisContainer() { RedisMessageListenerContainer container = new RedisMessageListenerContainer(); container.setConnectionFactory(redisConnectionFactory()); container.addMessageListener(messageListenerAdapter(), topic()); return container; } @Bean ChannelTopic topic() { return new ChannelTopic("Event"); } } | cs |
기존의 get/set을 위한 bean말고도 추가된 것들이 있다.
결과적으로 설명하면 특정 메세지를 받기 위한 채널이 필요하고, 그 채널로 들어온 메세지를 처리할 action을 정의한 MessageListenerAdapter가 필요하다. 그것을 container에 등록하는 것이다.
메세지를 처리하는 MessageListenerAdapter를 생성하는 부분에서 new RedisService()는 아래와 같다.
1 2 3 4 5 6 7 8 9 10 11 | @Service public class RedisService implements MessageListener{ public static List<String> messageList = new ArrayList<String>(); @Override public void onMessage(Message message, byte[] pattern) { messageList.add(message.toString()); System.out.println("Message received: " + message.toString()); } } | cs |
MessageListenr를 확장한 클래스를 만들었다. 오버라이드한 onMessage()는 메세지를 subscribe했을 때 수행할 메서드다.
위의 예제에서는 messageList에 받을 메세지를 추가하고, 단순하게 메세지를 출력하는 것으로 구성했다.
redis-cli를 통해 publish를 해봤다
subscribe하고 있는 것이 있어서 (integer) 1로 출력되었다.
publish하는 방법은 위와 같이 publish 해당채널(Event) 메세지(jeong pro)를 적어주면 된다.
실제로 출력되는 것을 볼 수 있다.
Spring boot에서 get,set과 pub,sub을 테스트해봤다. 실무에 활용할 때는 더 상세한 전략이 필요하다.
예제로 참고만 하도록 한다.