개발노트

25.03.05 Redis란 무엇인가? & RedisTemplate 사용법 본문

DataBase

25.03.05 Redis란 무엇인가? & RedisTemplate 사용법

ddong-kka 2025. 3. 5. 00:15

개요

redis를 사용해보려한다. 개념과 특징을 정리하고 자료형의 주요 메서드를 알아본 다음 RedisTemplate 를 구현하는 법도 정리해본다.

 


Redis란?

Redis(Remote Dictionary Server) 오픈 소스 인메모리 데이터 저장소로 주로 캐시, 세션 저장소, 메시지 브로커 등의 용도로 사용된다.

Redis의 특징

 

인메모리 데이터 저장소

  • 데이터를 메모리 ( RAM )에 저장하여 읽기/쓰기 속도가 매우 빠름
  • 디스크보다 접근 속도가 빠르므로 캐싱에 최적

Key-Value 구조

  • 지원 데이터 타입
    • 문자열(String)
    • 리스트(List)
    • 셋(Set)
    • 정렬된 셋(Sorted Set)
    • 해시(Hash)

퍼시스턴스(Persistence) 지원

  • 데이터를 디스크에 저장하여 데이터 손실 방지 가능 
  • 디스크 저장 방식
    • RDB (Redis Database Snapshot): 일정 간격으로 저장
    • AOF (Append Only File): 모든 명령을 로그로 기록

분산 처리 및 스케일링

  • Redis Cluster를 활용하면 수평 확장(Sharding) 가능
  • 여러 Redis 노드를 Replication(복제) 설정하여 부하 분산

Pub/Sub 기능

  • 메시지 브로커 역할 가능 (예: 채팅 시스템, 알림 서비스)

 


Redis 자료형

 

String (문자열)

  • 가장 기본적인 데이터 타입 (Key-Value 구조)
  • 문자열뿐만 아니라 숫자 값도 저장 가능
SET key "Hello"   # key에 "Hello" 저장
GET key           # "Hello"

SET count 1       # 숫자 저장 가능
INCR count        # count = count + 1  (존재하지않는 키값이면 자동으로 생성)
DECR count        # count = count - 1
APPEND key " Redis"  # "Hello Redis" (기존 값에 추가)
STRLEN key        # 문자열 길이 반환 (11)

 

List (리스트, 배열)

  • 순서가 있는 데이터 저장 (배열과 유사)
  • FIFO(Queue)나 LIFO(Stack) 구조로 활용 가능
  • index는 동일하게 0 부터 시작
LPUSH list "A"   # 리스트 앞에 "A" 추가
LPUSH list "B"   # 리스트 앞에 "B" 추가
RPUSH list "C"   # 리스트 끝에 "C" 추가

LRANGE list 0 -1 # ["B", "A", "C"] (전체 목록 조회)
LPOP list        # "B" (왼쪽에서 하나 제거)
RPOP list        # "C" (오른쪽에서 하나 제거)
LLEN list        # 리스트 길이 반환 (1)

 

Set (집합)

  • 중복을 허용하지 않는 데이터 저장
  • 빠른 멤버 조회 및 집합 연산 가능
SADD mySet "A"   # "A" 추가
SADD mySet "B"   # "B" 추가
SADD mySet "A"   # 중복 값이므로 추가되지 않음

SMEMBERS mySet   # ["A", "B"]
SISMEMBER mySet "A"  # true (A가 존재하는지 확인)
SREM mySet "B"   # "B" 삭제
SCARD mySet      # 개수 반환 (1)

 

교집합 찾는 예시

SADD user1_likes "골프" "테니스" "농구"
SADD user2_likes "테니스" "축구"

SINTER user1_likes user2_likes  # 공통 관심사 ["테니스"]

 

Sorted Set (정렬된 집합, 랭킹 시스템)

 

  • Set과 유사하지만 각 값에 점수(Score)가 있음
  • 점수를 기준으로 자동 정렬됨
ZADD ranking 100 "User1"  # User1 점수 100
ZADD ranking 200 "User2"  # User2 점수 200

ZRANGE ranking 0 -1   # ["User1", "User2"] (오름차순 조회)
ZREVRANGE ranking 0 -1 # ["User2", "User1"] (내림차순 조회)
ZRANK ranking "User1"  # 0 (User1 순위)
ZREM ranking "User1"   # User1 삭제

 

 

 

Hash (JSON과 유사한 필드-값 저장)

  • Key-Value 안에 또 다른 Key-Value를 저장 (Object 저장 가능)
  • 데이터베이스의 행(Row)과 비슷한 구조
HSET user:1 name "승현"   # user:1의 name 필드 저장
HSET user:1 age 27       # user:1의 age 필드 저장

HGET user:1 name         # "승현" (특정 필드 값 가져오기)
HGETALL user:1           # {"name": "승현", "age": "27"} (전체 데이터 조회)

HDEL user:1 age          # age 필드 삭제
HEXISTS user:1 age       # false (age 필드가 존재하는지 확인)

 

 

Pub/Sub (메시지 브로커 역할)

  • 비동기 이벤트 시스템을 구축할 때 사용
  • 게시(Publish)와 구독(Subscribe) 방식
SUBSCRIBE channel1    # 채널 구독
PUBLISH channel1 "Hello Redis"   # 메시지 전송

 

정리

String → 기본 문자열 저장
List → 순서가 있는 배열 (Queue, Stack 가능)
Set → 중복 없는 데이터 저장
Sorted Set → 점수 기반 정렬된 데이터
Hash → JSON과 유사한 구조
Pub/Sub → 실시간 메시지 시스템

 

 


 

RedisTemplate

Spring Boot에서 Redis와 쉽게 상호작용할 수 있도록 제공하는 템플릿 클래스이다.

Redis는 Key-Value 기반의 데이터 저장소이며, Java에서 효율적으로 사용하려면

직렬화, 데이터 타입, 연결 관리등의 작업이 필요하다.

 

이를 쉽게 처리할 수 있도록 도와주는 것이 RedisTemplate이다.

 

직렬화 설정(String, JSON 등)을 통해 데이터 저장 방식을 조정 가능
객체(Object)를 JSON으로 변환하여 저장 가능 (GenericJackson2JsonRedisSerializer 사용)
다양한 Redis 명령어를 쉽게 사용할 수 있음 (opsForValue, opsForList, opsForHash 등)

기능 설명 Redis 명령어 예시
opsForValue() 문자열(String) 데이터 저장 SET key value, GET key
opsForList() 리스트(List) 데이터 저장 LPUSH key value, LRNAME Key
opsForSet() 집합(Set) 데이터 저장 SADD key value, SMEMBERS key
opsForHash() 해시(Hash) 데이터 저장 HSET key field value, HGET key field
opsForZSet() 정렬된 집합(Sorted Set) 데이터 저장 ZADD key score value, ZRANGE key

 

 


사용 예시

@Configuration
public class RedisConfig {

    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
        redisTemplate.setConnectionFactory(connectionFactory);

        // 문자열(Key) 직렬화: 사람이 읽을 수 있도록 StringRedisSerializer 사용
        redisTemplate.setKeySerializer(new StringRedisSerializer());
        redisTemplate.setHashKeySerializer(new StringRedisSerializer());

        // 값(Value) 직렬화: JSON 형식으로 저장
        redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        redisTemplate.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

        redisTemplate.afterPropertiesSet();
        return redisTemplate;
    }
}

 

@Autowired
private RedisTemplate<String, Object> redisTemplate;

public void saveStringData() {
    redisTemplate.opsForValue().set("message", "Hello, Redis!");
    String value = (String) redisTemplate.opsForValue().get("message");
    System.out.println("저장된 값: " + value);
}


// 실행 결과 : 저장된 값: Hello, Redis!

 


객체(Object) 저장 및 조회 (JSON 직렬화)

enericJackson2JsonRedisSerializer을 사용하면 Java 객체를 JSON으로 저장

import java.io.Serializable;

public class User implements Serializable {
    private String name;
    private int age;

    public User() {}

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
}

 

public void saveUser() {
    User user = new User("승현", 25);
    redisTemplate.opsForValue().set("user:1001", user);

    User savedUser = (User) redisTemplate.opsForValue().get("user:1001");
    System.out.println("저장된 유저: " + savedUser.getName() + ", 나이: " + savedUser.getAge());
}

// 실행 결과 : 저장된 유저: 승현, 나이: 25

 


리스트(List) 저장 & 조

public void saveListData() {
    redisTemplate.opsForList().rightPush("numbers", "one");
    redisTemplate.opsForList().rightPush("numbers", "two");

    List<Object> numbers = redisTemplate.opsForList().range("numbers", 0, -1);
    System.out.println("저장된 리스트: " + numbers);
}

// 실행 결과 : 저장된 리스트: [one, two]

 


해시(Hash) 저장 & 조회

public void saveHashData() {
    redisTemplate.opsForHash().put("user:1002", "name", "Jane");
    redisTemplate.opsForHash().put("user:1002", "age", 30);

    String name = (String) redisTemplate.opsForHash().get("user:1002", "name");
    Integer age = (Integer) redisTemplate.opsForHash().get("user:1002", "age");

    System.out.println("저장된 유저 정보: 이름=" + name + ", 나이=" + age);
}

// 실행 결과 : 저장된 유저 정보: 이름=Jane, 나이=30

집합(Set) 및 정렬된 집합(Sorted Set) 저장 & 조회 / 랭킹 조회 / 조회수

@RestController
@RequiredArgsConstructor
public class ArticlesController {

    private final RedisTemplate<String, Object> redisTemplate;


    /**
     *
     * -- 1. 내 블로그 별 조회수를 Redis 로 확인하고 싶다.
     * --   1. 블로그 url의 path는 '/articles/{id}' 형식이다.
     * --   2. 로그인 여부와 상관없이 새로고침 될때마다 조회수가 하나 증가한다.
     * --   3. 이를 관리하기 위해 적당한 데이터 타입을 선정하고,
     * --   4. 사용자가 임의의 페이지에 접속할 때 실행될 명령을 작성해보자.
     *
     * -- String - INCR(++), DECR(--)
     * -- INCR articles:{id}
     * INCR articles:1
     * INCR articles:2
     */
    @GetMapping("/articles/{id}")
    @ResponseBody
    public String getViews(@PathVariable("id") Long id){

        redisTemplate.opsForValue().increment("articles:" +id);

        return String.valueOf(redisTemplate.opsForValue().get("articles:" +id));
    }


    /**
     * -- 2. 블로그에 로그인한 사람들의 조회수와 가장 많은 조회수를 기록한 글을 Redis로 확인하고싶다.
     * --   1. 블로그 url의 path는 '/articles/{id}' 형식이다.
     * --   2. 로그인 한 사람들의 계정은 영문으로만 이뤄져 있다.
     * --   3. 이를 관리하기 위해 적당한 데이터 타입을 선정하고,
     * --   4. 사용자가 입의의 페이지에 접속할 때 실행될 명령을 작성해보자.
     * --   4. 만약 상황에 따라 다른 명령이 실행되어야 한다면, 주석으로 추가해보자.
     *
     * -- Set
     * sadd articles:1 alex
     * sadd articles:1 brad
     * sadd articles:1 chad
     * scard articles:1
     *
     * sadd articles:2 alex
     * sadd articles:2 chad
     * scard  articles:2
     *
     * sadd articles:1 alex
     *
     * -- sadd의 결과에 따라 명령어를 실행하거나 말거나
     * -- 0은 스킵
     * -- 1인 경우? sorted set
     * zadd articles:ranks 1 articles:1
     * zincrby  articles:ranks 1 articles:1
     *
     * -- 가장 높은 조회수를 보고싶다면
     * zrevrange  articles:ranks 0 0
     * zrange articles:ranks 0 0 REV
     */

    @GetMapping("/articles/rank/{id}/{username}")
    public String getRank(@PathVariable("id") Long id, @PathVariable("username")String username){
        String userKey = "articles:" + id;
        String rankKey = "articles:ranks";

        // 사용자 조회 여부 확인 (sadd : 새로운 유저가 조회하면 1 , 아니면 0 반환)
        Long added = redisTemplate.opsForSet().add(userKey,username);

        // 새로운 사용자라면 조회수 증가
        if (added != null && added == 1){
            redisTemplate.opsForZSet().incrementScore(rankKey,userKey,1);
        }

        Long views = redisTemplate.opsForSet().size(userKey);

        // 특정 게시글을 조회수 확인
        return  "Article " + id + " 조회수 : " + views;
    }

    @GetMapping("/articles/top")
    public String getTopArticle() {
        String rankKey = "articles:ranks";

        // 조회수가 가장 높은 게시글 가져오기 (최상위 1개)
        Set<ZSetOperations.TypedTuple<Object>> topArticle =
                redisTemplate.opsForZSet().reverseRangeWithScores(rankKey, 0, 0);

        if (topArticle != null && !topArticle.isEmpty()) {
            ZSetOperations.TypedTuple<Object> article = topArticle.iterator().next();
            return "가장 많이 본 게시글: " + article.getValue() + " (조회수: " + article.getScore() + ")";
        }
        return "조회된 게시글이 없습니다.";
    }

}

'DataBase' 카테고리의 다른 글

25.04.16 Redis 에서 루아(Lua) 스크립트란?  (0) 2025.04.16
25.03.08 캐싱 전략 과 예시  (0) 2025.03.08