티스토리 뷰

728x90
반응형

지난 글에 적었듯이 레디스는 다양한 자료형을 지원한다.

 

해서 조금씩 공부하면서 현재 진행 중인 프로젝트의 캐싱이나 채팅방, 인증번호 저장 등을

 

모조리 레디스를 사용하도록 교체할 꿈을 꾸고 있는데, 바로 적용하려니 기본 연산이 잘 들어오지 않아 힘들었다.

 

따라서 이번 글은 레디스에서 지원하는 자료형의 연산과 기본 명령어 등을 연습하려고 한다.

 

먼저 스프링부트 프로젝트를 하나 생성해서 내장 레디스를 사용하기 위한 의존성을 추가한다.

dependencies {
	implementation 'org.springframework.boot:spring-boot-starter-web'
	implementation 'it.ozimov:embedded-redis:0.7.2'
}

나머지 의존성은 필요에 따라 추가하면 되겠다.

 

계속해서 레디스 설정파일을 구성한다.

package com.example.redis;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.repository.configuration.EnableRedisRepositories;

@Configuration
@EnableRedisRepositories
public class RedisConfig {

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

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

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        return new LettuceConnectionFactory(redisHost, redisPort);
    }

    @Bean
    public RedisTemplate<?, ?> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<byte[], byte[]> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);
        return redisTemplate;
    }
}

이어서 내장 레디스를 위한 설정 파일이다.

package com.example.redis;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import redis.embedded.RedisServer;

import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;

@Profile("local")
@Configuration
public class EmbeddedRedisConfig {

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

    private RedisServer redisServer;

    @PostConstruct
    public void redisServer() {
        redisServer = new RedisServer(redisPort);
        redisServer.start();
    }

    @PreDestroy
    public void stopRedis() {
        if (redisServer != null) {
            redisServer.stop();
        }
    }
}

"local"인 경우에만 내장 레디스를 사용하기로 했으니까 yml파일도 수정해 준다.

spring:
  profiles:
    active: local
  redis:
    lettuce:
      pool:
        max-active: 10
        max-idle: 10
        min-idle: 2
    port: 6379
    host: localhost

오늘 사용할 클래스는 StringRedisTemplate이다.

 

RedisTemplate을 상속받은 클래스이자 자료형의 대부분인 문자열을 효과적으로 직렬화하는 클래스라고 한다.

 

실제 사용할 땐 RedisTemplate을 직접 사용하기보단 StringRedisTemplate을 사용하는 것 같은데 그건 좀 더 보고..

 

하여간 코드를 뜯어보면 단순하게 생겼다.

package org.springframework.data.redis.core;

import org.springframework.data.redis.connection.DefaultStringRedisConnection;
import org.springframework.data.redis.connection.RedisConnection;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.StringRedisConnection;
import org.springframework.data.redis.serializer.RedisSerializer;

public class StringRedisTemplate extends RedisTemplate<String, String> {

	public StringRedisTemplate() {
		setKeySerializer(RedisSerializer.string());
		setValueSerializer(RedisSerializer.string());
		setHashKeySerializer(RedisSerializer.string());
		setHashValueSerializer(RedisSerializer.string());
	}

	public StringRedisTemplate(RedisConnectionFactory connectionFactory) {
		this();
		setConnectionFactory(connectionFactory);
		afterPropertiesSet();
	}

	protected RedisConnection preProcessConnection(RedisConnection connection, boolean existingConnection) {
		return new DefaultStringRedisConnection(connection);
	}
}

RedisSerializer라는 클래스를 이용해 직렬화를 하는 듯 보인다.

 

계속해서 실습에 사용할 컨트롤러 클래스를 작성하자.

package com.example.redis;

import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.*;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
public class RedisController {
    private final StringRedisTemplate stringRedisTemplate;
    private static String key = "gnidinger";

}

실습 하나마다 API 엔드포인트를 하나씩 따서 콘솔창에서 결과를 확인할 생각이다.

 

위 코드에서 key값은 그때그때 새로 정해주기도 귀찮고 지금은 별 의미도 없는 것 같아 전역변수로 설정해 주었다.

 

참고로 각 타입을 위한 메서드와 객체는 아래와 같다.

 

Method Object
opsForValue() ValueOperations
opsForList() ListOperations
opsForSet() SetOperations
opsForZSet() ZSetOperations
opsForHash() HashOperations
opsForStream() StreamOperations
opsForGeo() GeoOperations
opsForHyperLogLog() HyperLogLogOperations
opsForCluster() ClusterOperations

 

String

 

@GetMapping("/test")
public void testString() {

    final ValueOperations<String, String> stringStringValueOperations = stringRedisTemplate.opsForValue();

    stringStringValueOperations.set(key, "1");
    final String result_1 = stringStringValueOperations.get(key);

    System.out.println("result_1 = " + result_1);

    stringStringValueOperations.increment(key);
    final String result_2 = stringStringValueOperations.get(key);

    System.out.println("result_2 = " + result_2);
}
result_1 = 1
result_2 = 2

먼저 가장 활용도가 높은 문자열이다.

 

위에 적었듯이 템플릿을 이용해 해당 메서드를 호출하면 위와 같은 객체가 반환된다.

 

재미있는 것은 set을 이용해 문자열 타입으로 숫자를 입력하면 increment() 메서드로 그 값을 1 증가시킬 수 있다는 점이다.

 

물론 반대 기능을 하는 메서드인 decrement() 역시 존재한다.

 

키를 이용해 값을 꺼내올 땐 get() 메서드가 사용된다.

 

List

@GetMapping("/test2")
public void testList() {

    final ListOperations<String, String> listOperations = stringRedisTemplate.opsForList();

    listOperations.rightPush(key, "H");
    listOperations.rightPush(key, "E");
    listOperations.rightPush(key, "L");
    listOperations.rightPush(key, "L");
    listOperations.rightPush(key, "O");

    listOperations.rightPushAll(key, " ", "G", "N", "I", "D", "I", "N", "G", "E", "R");

    final String char_1 = listOperations.index(key, 1);
    System.out.println("char_1 = " + char_1);

    final Long size = listOperations.size(key);
    System.out.println("size = " + size);

    final List<String> result = listOperations.range(key, 0, 14);
    System.out.println("result = " + Arrays.toString(result.toArray()));
}
char_1 = E
size = 15
result = [H, E, L, L, O,  , G, N, I, D, I, N, G, E, R]

다음으로 많이 쓰이는 리스트이다.

 

리스트의 오른쪽에 값을 추가하는 rightPush()와 왼쪽에 추가하는 leftPush()가 존재한다.

 

또한 index(K, V)를 이용해 리스트와 리스트의 인덱스를 지정해 값을 불러올 수 있다.

 

Set

@GetMapping("/test3")
public void testSet() {

    final SetOperations<String, String> setOperations = stringRedisTemplate.opsForSet();

    setOperations.add(key, "H");
    setOperations.add(key, "E");
    setOperations.add(key, "L");
    setOperations.add(key, "L");
    setOperations.add(key, "O");

    Set<String> gnidinger = setOperations.members(key);
    System.out.println("members = " + Arrays.toString(gnidinger.toArray()));

    Long size = setOperations.size(key);
    System.out.println("size = " + size);

    gnidinger.forEach(System.out::println);
}
members = [L, H, O, E]
size = 4
L
H
O
E

다음으로 Set이다.

 

일반 컬렉션 프레임워크를 사용할 때와 큰 차이는 없다.

 

SortedSet

@GetMapping("/test4")
public void testZSet() {

    ZSetOperations<String, String> zSetOperations = stringRedisTemplate.opsForZSet();

    zSetOperations.add(key, "H", 2);
    zSetOperations.add(key, "E", 5);
    zSetOperations.add(key, "L", 10);
    zSetOperations.add(key, "L", 20);
    zSetOperations.add(key, "O", 30);

    Set<String> range = zSetOperations.range(key, 0, 5);
    System.out.println("range = " + Arrays.toString(range.toArray()));

    Long size = zSetOperations.size(key);
    System.out.println("size = " + size);

    Set<String> scoreRange = zSetOperations.rangeByScore(key, 0, 13);
    System.out.println("scoreRange = " + Arrays.toString(scoreRange.toArray()));
}
range = [H, E, L, O]
size = 4
scoreRange = [H, E]

Sorted Set, 혹은 ZSet은 내부 자료를 정렬해서 보관하고 있는 Set이라고 할 수 있다.

 

즉, 모든 값은 유일하며 정해진 기준에 의해 이진 탐색트리를 이용해 정렬까지 되어 있는 구조라 할 수 있다.

 

위 코드에서 값을 저장할 때 뒤에 숫자로 Score를 부여하며, 이 값의 크기에 따라 정렬이 결정된다.

 

혹은 키값을 이용해 저장된 값을 정렬해 출력하는 것도 가능하다.

 

Hash

@GetMapping("/test5")
public void testHash() {

    HashOperations<String, Object, Object> hashOperations = stringRedisTemplate.opsForHash();

    hashOperations.put(key, "Hello", "gnidinger");
    hashOperations.put(key, "Hello2", "gnidinger2");
    hashOperations.put(key, "Hello3", "gnidinger3");

    Object hello = hashOperations.get(key, "Hello");
    System.out.println("hello = " + hello);

    Map<Object, Object> entries = hashOperations.entries(key);
    System.out.println("entries = " + entries.get("Hello2"));

    Long size = hashOperations.size(key);
    System.out.println("size = " + size);
}
hello = gnidinger
entries = gnidinger2
size = 3

Hash는 말 그대로 키가 가리키는 값 안에 새로운 키-값 자료를 형성할 수 있는 구조이다.

 

무슨 소린지 잘 모르겠지만 아마도 채팅방 등과 같은 기능을 구현할 때 유용하게 쓰이는 듯하다.

 

키 값이 가리키는 채팅방 안에 또 채팅이라는 값들이 존재할 테니.

 

기본 연산은 간단한 편이다.

 

이런 식으로 레디스가 제공하는 기본 자료형과 그 연산에 대해 연습해 보았다.

 

조금은 친해졌을.. 지도?

 

다음번엔 (아마도) 레디스 캐싱을 프로젝트에 적용하는 법에 대해서 적어볼 생각이다.

 

채팅방에 당장 적용하고 싶지만 일단 쉬운 것부터.

반응형
댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/06   »
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
글 보관함