본문 바로가기
NoSQL/Redis

Spring - Redis 연동하기

by 띵앤띵 2022. 8. 24.
728x90
반응형

1. Redis 특징과 주요 사용처

특징

  • Redis는 List, Set, Sorted Set, Hash 등과 같은 Collection을 지원합니다.
    • List : redis Strings의 묶음 Collection
    • Set : 중복되지 않는 값을 데이터로 갖는 Colelction
    • Sorted Set : 가중치(Score)를 갖고 가중치(Score)에 따른 정렬된 순서로 중복되지 않는 값을 데이터로 가지는 Colelction
    • Hash : Key 하나에 여러개의 Field와 Value로 구성된 즉, Key 내부에 또 다른 Key - value로 이루어진 Collection
  • Dump 파일과 AOF(Append Of File) 방식으로 메모리 상의 데이터를 디스크에 저장할 수 있습니다.
  • Master/Slave Replication 기능을 통해 데이터의 분산, 복제 기능을 제공하며 Query Off Loading 기능을 통해 Master는 Read/Write를 수행하고 Slave는 Read만 수행할 수 있습니다.
  • 파티셔닝(Partitioning)을 통해 동적인 스케일 아웃(Scale Out)인 수평 확장이 가능합니다.
  • Expiration 기능은 일정 시간이 지났을 때 메모리 상의 데이터를 자동 삭제할 수 있습니다.
  • Redis는 Single Thread -> Atomic 보장 (원자성)

주요 사용처

  • Remote Data Store
    • 여러 서버의 Data 공유를 위해 사용될 수 있음.
    • 특히, Redis의 경우 Single Thread 이므로 Race Condition 발생 가능성이 낮다는 것을 활용할 필요가 있을 경우
  • 인증 토큰 개발 (JWT)
  • Ranking Board (Sorted Set)
  • 유저 API Limit
  • Job QUeue

 

2. Mac에서 Redis 설치하기

# 설치
brew install redis

# 버전확인
redis-server --version

# 실행 - redis를 Homebrew 서비스로 실행하면 재부팅후에도 자동으로 실행됩니다.
brew services start redis

# 중단
brew services stop redis

# 터미널 접속하는 방법
redis-cli -h localhost -p 6379

# 전체 키 조회
keys *

# 키 전체 삭제
flushall

 

3. 의존성 및 yml 세팅

3.1 build.gradle

# redis 의존성
implementation 'org.springframework.boot:spring-boot-starter-data-redis'

# test를 위한 embedded redis
testImplementation group: 'it.ozimov', name: 'embedded-redis', version: '0.7.2'

 

3.2 application.yml

spring:
  redis:
    lettuce:
      pool:
        max-active: 5 #pool에 할당될 수 있는 커넥션 최대수 (음수로 하면 무제한)
        max-idle: 5   #pool의 "idle" 커넥션 최대수 (음수로 하면 무제한)
        min-idle: 2   #풀에서 관리하는 idle 커넥션의 최소 수 대상 
    host: 127.0.0.1
    port: 6379
    password: redisPass

 

4. RedisConfig.java

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

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

    @Value("${spring.redis.password}")
    private String redisPassword;

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration();
        redisStandaloneConfiguration.setHostName(redisHost);
        redisStandaloneConfiguration.setPort(redisPort);
        redisStandaloneConfiguration.setPassword(redisPassword);
        LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(redisStandaloneConfiguration);
        return lettuceConnectionFactory;
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(redisConnectionFactory());
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setValueSerializer(new StringRedisSerializer());
        return redisTemplate;
    }

}
  • redisTemplate : RedisTemplate을 통해 RedisConnection에서 넘겨준 byte 값을 객체 직렬화합니다. Redis와 통신할 때 사용합니다.

 

5. RedisController.java

@RestController
public class RedisController {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @PostMapping("/redisTest")
    public ResponseEntity<?> addRedisKey() {
        ValueOperations<String, String> vop = redisTemplate.opsForValue();
        vop.set("yellow", "banana");
        vop.set("red", "apple");
        vop.set("green", "watermelon");
        return new ResponseEntity<>(HttpStatus.CREATED);
    }

    @GetMapping("/redisTest/{key}")
    public ResponseEntity<?> getRedisKey(@PathVariable String key) {
        ValueOperations<String, String> vop = redisTemplate.opsForValue();
        String value = vop.get(key);
        return new ResponseEntity<>(value, HttpStatus.OK);
    }
}

가장 일반적인 REST Controller의 모습이다. 아까 만들어 놓은 RedisTemplate을 주입받고 opsForValue() 라는 메서드를 통해 Redis에 값을 넣을수도 꺼낼수도 있다. 

 

필자는 포스트맨보다는 부메랑(Boomerang) 이라는 크롬 플러그인을 애용하는데 이를 통해 제대로 연동이 되는지 테스트를 해보겠다. 

 

 

redis 연동 샘플 (POST)

 

일단은 값을 넣어주는 역할을 하는 /redisTest 만 POST method로 호출을 해본다. CREATE 응답 코드인 201 SUCCESS가 나오며 Redis로 값이 들어간것 같다. 확인을 해보자. 

 

redis 연동 샘플 (GET)

 

확인을 할때는 Controller에 만들어 놓은대로 PathVariable에 key를 넣어준다. /redisTest/yellow 라고 요청을 날려본다. 조회이므로 GET method를 사용해서 요청을 날리면 앞서 넣었던 yellow의 value에 해당하는 banana가 나오는것을 확인할 수 있다. 잘 되는것 같다. 

 

혹시 모르니 CLI에서 값이 잘 들어가 있는지 확인을 한번 더 해보자. Redis에서 모든 key를 조회하는 keys * 를 날려보면 

이전에 넣었던 oing이라는 값에 추가한 yellow, green, red가 잘 들어가 있는것을 확인할 수 있다. 

 

6. RedisRepository

Spring Data Redis 의 Redis Repository 를 이용하면 간단하게 Domain Entity 를 Redis Hash 로 만들 수 있습니다.

다만 트랜잭션을 지원하지 않기 때문에 만약 트랜잭션을 적용하고 싶다면 RedisTemplate 을 사용해야 합니다.

 

6.1 PersonRedisRepository.java

public interface PersonRedisRepository extends CrudRepository<Person, String> {
}

 

6.2 Person.java (Entity)

@Getter
@RedisHash(value = "people", timeToLive = 30)
public class Person {

    @Id
    private String id;
    private String name;
    private Integer age;
    private LocalDateTime createdAt;

    public Person(String name, Integer age) {
        this.name = name;
        this.age = age;
        this.createdAt = LocalDateTime.now();
    }
}
  • Redis 에 저장할 자료구조인 객체를 정의합니다.
  • 일반적인 객체 선언 후 @RedisHash 를 붙이면 됩니다.
    • value : Redis 의 keyspace 값으로 사용됩니다.
    • timeToLive : 만료시간을 seconds 단위로 설정할 수 있습니다. 기본값은 만료시간이 없는 -1L 입니다.
  • @Id 어노테이션이 붙은 필드가 Redis Key 값이 되며 null 로 세팅하면 랜덤값이 설정됩니다.
    • keyspace 와 합쳐져서 레디스에 저장된 최종 키 값은 keyspace:id 가 됩니다.

 

7. Example

@SpringBootTest
public class RedisRepositoryTest {

    @Autowired
    private PersonRedisRepository repo;

    @Test
    void test() {
        Person person = new Person("Park", 20);

        // 저장
        repo.save(person);

        // `keyspace:id` 값을 가져옴
        repo.findById(person.getId());

        // Person Entity 의 @RedisHash 에 정의되어 있는 keyspace (people) 에 속한 키의 갯수를 구함
        repo.count();

        // 삭제
        repo.delete(person);
    }
}
  • JPA 와 동일하게 사용할 수 있습니다.
  • 여기서는 id 값을 따로 설정하지 않아서 랜덤한 키값이 들어갑니다.
  • 저장할때 save() 를 사용하고 값을 조회할 때 findById() 를 사용합니다.

 

redis-cli 로 데이터 확인

  • id 를 따로 설정하지 않은 null 값이라 랜덤한 키값이 들어갔습니다.
  • 데이터를 저장하면 member  member:{randomKey} 라는 두개의 키값이 저장되었습니다.
  • member 키값은 Set 자료구조이며, Member 엔티티에 해당하는 모든 Key 를 갖고 있습니다.
  • member:{randomKey} 값은 Hash 자료구조이며 테스트 코드에서 작성한 값대로 field, value 가 세팅한 걸 확인할 수 있습니다.
  • timeToLive 를 설정했기 때문에 30초 뒤에 사라집니다. ttl 명령어로 확인할 수 있습니다.

 

7.2 Sample TEST 작성

@SpringBootTest
class PersonRedisRepositoryTest {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    @Autowired
    private PersonRedisRepository personRedisRepository;

    @Test
    @DisplayName("person redis 객체 저장테스트")
    void test1(){
        // given
        Person person = new Person("Park", 20);

        // when
        // 저장
        personRedisRepository.save(person);

        // then
        // `keyspace:id` 값을 가져옴
        personRedisRepository.findById(person.getName());
        assertEquals("Park", person.getName());
        assertEquals(20, person.getAge());

        // Person Entity 의 @RedisHash 에 정의되어 있는 keyspace (people) 에 속한 키의 갯수를 구함
        long count = personRedisRepository.count();

        // 삭제
        personRedisRepository.delete(person);

    }


    @Test
    @DisplayName("테스트2")
    void testStrings() {
        // given
        ValueOperations<String, String> valueOperations = redisTemplate.opsForValue();
        String key = "nameKey";

        // when
        valueOperations.set(key, "thinkandthing");

        // then
        String value = valueOperations.get(key);
        assertThat(value).isEqualTo("thinkandthing");
    }

    @Test
    void testSet() {
        // given
        SetOperations<String, String> setOperations = redisTemplate.opsForSet();
        String key = "setKey";

        // when
        setOperations.add(key, "h", "e", "l", "l", "o");

        // then
        Set<String> members = setOperations.members(key);
        Long size = setOperations.size(key);

        assertThat(members).containsOnly("h", "e", "l", "o");
        assertThat(size).isEqualTo(4);
    }

    @Test
    void testHash() {
        // given
        HashOperations<String, Object, Object> hashOperations = redisTemplate.opsForHash();
        String key = "hashKey";

        // when
        hashOperations.put(key, "hello", "world");

        // then
        Object value = hashOperations.get(key, "hello");
        assertThat(value).isEqualTo("world");

        Map<Object, Object> entries = hashOperations.entries(key);
        assertThat(entries.keySet()).containsExactly("hello");
        assertThat(entries.values()).containsExactly("world");

        Long size = hashOperations.size(key);
        assertThat(size).isEqualTo(entries.size());
    }

    @Test
    void redisSetTest() {
        // Redis Template
        ValueOperations<String, String> vop = redisTemplate.opsForValue();
        // Redis Set Key-Value
        vop.set("key1", "data1"); // 만료제한 없이 생성
        vop.set("key-expire", "data-expire", Duration.ofSeconds(60)); // 60초 뒤에 해당 키가 만료되어 소멸된다.
    }

    @Test
    void redisListTypeTest(){
        redisTemplate.opsForList().rightPush("key", "value");
        RedisOperations<String, String> operations = redisTemplate.opsForList().getOperations();
        System.out.println(operations.opsForList().range("chatNumber", 0, 10));

    }


}

 

 

 

 

 

출처 : https://velog.io/@backtony/Spring-Redis-%EC%97%B0%EB%8F%99%ED%95%98%EA%B8%B0

출처: https://oingdaddy.tistory.com/239 [SI Supply Depot:티스토리]

출처 : https://bcp0109.tistory.com/328

출처 : https://kitty-geno.tistory.com/135

반응형

'NoSQL > Redis' 카테고리의 다른 글

[Redis] 데이터 타입 Sets  (0) 2022.09.23
[Redis] 데이터 타입 Lists  (0) 2022.09.23
[Redis] 데이터 타입 String  (0) 2022.09.23
Redis UI 툴 추천  (0) 2022.08.24
[Redis] Redis 설치 및 간단한 사용 방법 (Mac)  (0) 2022.08.24

댓글