[Spring Boot] Redis 설치 및 연동

01_Redis

📍 Redis(REmote DIctionary Server)

메모리(RAM)에 데이터를 저장하는 키-값(key-value) 데이터 저장소

여러 서버에서 공유할 수 있고 빠름(일반 변수는 빠르지만 서버 간 공유x. RDBMS는 공유되지만 느림)

 

  • 디스크: 일반적인 RDBMS. 느림. 용량 많음. 복잡한 쿼리 가능.
  • 메모리: Redis. 디스크보다 훨씬 빠름. 용량 제약. 복잡한 쿼리 약함.

 

활용 사례 설명
캐시(Cache) DB 조회 결과를 잠시 저장해 다음 요청 때 DB 대신 Redis에서 빠르게 조회
세션 저장소 로그인 세션을 저장 (특히 여러 서버를 운영할 때)
토큰 저장 JWT Refresh Token, 인증번호, 비밀번호 재설정 토큰 등
분산 락(Distributed Lock) 동시에 같은 자원에 접근하는 것을 막기 위한 락
순위표(Leaderboard) 게임 점수, 인기 게시글 순위 등
메시지 큐(Pub/Sub) 채팅, 알림 등 실시간 메시지 전달
속도 제한(Rate Limit) 1분에 N번 이상 요청 시 차단

 

 

키 네이밍 컨벤션

콜론(:)으로 계층 구분: category:id 또는 domain:type:id 형식

명확한 prefix 사용

소문자와 하이픈

 

 

 

📍 Redis 자료구조

String

키 하나에 값 하나 대응

단일 값 저장

활용 예) 인증번호, 토큰, 카운터 등

SET email-code:hongildong@example.com 123456
GET email-code:hongildong@example.com
> "123456"

 

 

List

값을 순서대로 저장

큐(Queue)나 스택(Stack)처럼 사용 가능

활용 예) 최근 검색어, 메시지 큐, 알림 목록

LPUSH messages "안녕"
LPUSH messages "반가워"
LRANGE messages 0 -1
> "반가워", "안녕"

 

 

Set

순서 없이 중복을 허용하지 않는 집합

활용 예) 좋아요 누른 사용자 목록, 태그 관리, 친구 목록 등

SADD tags "spring"
SADD tags "redis"
SADD tags "spring"  // 중복은 추가되지 않음
SMEMBERS tags
> "spring", "redis"

 

 

Sorted Set (정렬된 집합, ZSet)

Set과 비슷하지만 각 값에 점수(score)가 붙어 점수 기준으로 자동 정렬

활용 예) 랭킹 시스템, 인기 게시글 등

ZADD ranking 100 "화연"
ZADD ranking 85 "민수"
ZADD ranking 92 "유진"
ZRANGE ranking 0 -1 WITHSCORES
> "민수" 85, "수진" 92, "유진" 100

 

 

Hash

하나의 키 안에 여러 필드-값 쌍 저장

활용 예) 사용자 정보, 상품 정보 등 객체 형태의 데이터

HSET user:1 name "홍길동" age 23 email "hong@example.com"
HGET user:1 name
> "화연

 

 

 

 

02_Redis 설치

📍 로컬 설치(MacOS)

Homebrew 설치되어 있어야 함

brew install redis
brew services start redis

 

 

 

📍 Linux (Ubuntu) 설치

sudo apt update
sudo apt install redis-server
sudo systemctl start redis

 

 

 

📍 Docker 설치

docker run -d \ # -d: 백그라운드 실행
	--name my-redis # 컨테이너 이름 지정
	-p 6379:6379 # 호스트의 6379 포트를 Redis 기본 포트 6379에 연결
	redis:7.2 # Redis 이미지 버전

 

 

 

📍 Redis CLI

  • 설치 확인
redis-cli ping
> PONG

PONG이 떨어지면 Redis 서버 정상 작동 상태

 

  • Redis 접속
redis-cli

 

명령어 설명
SET key value 키-값 저장
GET key 값 조회
DEL key 키 삭제
EXISTS key 키 존재 여부 (1: 있음, 0: 없음)
EXPIRE key seconds 만료 시간 설정
TTL key 남은 만료 시간 조회
KEYS * 모든 키 조회 (운영 환경에서는 절대 사용 금지)
FLUSHDB 현재 DB의 모든 키 삭제 (위험!)

 

 

 

 

03_Spring Boot에서 Redis 연동

📍 의존성 추가

build.gradle

dependencies{
	implementation 'org.springframework.boot:spring-boot-starter-data-redis'
}

 

application.yml

spring:
  data:
    redis:
      host: localhost
      port: 6379
      # 비밀번호가 있는 경우
      # password: your-password

보안 운영 환경에서는 비밀번호 설정할 것!

 

 

 

📍 RedisTemplate

Spring Data Redis는 RedisTemplate, StringRedisTemplate 템플릿 제공

  • RedisTemplate: 키/값 타입 객체(직렬화 필요), 객체 통째로 저장할 때
  • StringTemplate: String 전용. 키와 값 모두 문자열일 때
@Configuration
public class RedisConfig {
		// 키와 값 모두 String인 경우 사용
    @Bean
    public StringRedisTemplate stringRedisTemplate(RedisConnectionFactory connectionFactory) {
        return new StringRedisTemplate(connectionFactory);
    }

		// 키는 String, 값은 JSON으로 직렬화
    @Bean
    public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(connectionFactory);

        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());

        return template;
    }
}

 

🔍 코드 설명

  • StringRedisTemplate: 키와 값이 모두 문자열인 경우 사용
  • RedisTemplate<String, Object>: 객체를 JSON으로 직렬화해 저장하고 싶을 때 사용

 

  • 서비스에서 사용 시
@Service
@RequiredArgsConstructor
public class RedisService {

    private final StringRedisTemplate redisTemplate;

    // 저장
    public void save(String key, String value) {
        redisTemplate.opsForValue().set(key, value);
    }

    // 만료 시간과 함께 저장
    public void saveWithTtl(String key, String value, Duration ttl) {
        redisTemplate.opsForValue().set(key, value, ttl);
    }

    // 조회
    public String get(String key) {
        return redisTemplate.opsForValue().get(key);
    }

    // 삭제
    public boolean delete(String key) {
        return Boolean.TRUE.equals(redisTemplate.delete(key));
    }

    // 존재 여부 확인
    public boolean exists(String key) {
        return Boolean.TRUE.equals(redisTemplate.hasKey(key));
    }
}

 

🔍 코드 설명

  • opsForValue(): String 타입을 다루는 연산. 다른 자료구조는 opsForList(), opsForSet(), opsForZSet(), opsForHash() 사용
  • set(key, value, ttl): 저장과 동시에 TTL 설정. 별도로 expire를 호출하지 않아도 된다.
  • hasKey, delete는 Boolean을 반환하므로 null 안전 처리(Boolean.TRUE.equals(...))를 해주는 것이 좋다.

 

 

TTL(Time To Live)

키가 살아있는 시간

TTL을 설정해두고 설정 시간이 되면 Redis가 자동으로 키 삭제

TTL이 만료되어 자동 삭제된 키를 조회하면 null 반환 → null 체크해야 함

// 5분 동안 유효한 인증번호 저장
redisTemplate.opsForValue().set(
    "email-code:hong@example.com",
    "123456",
    CODE_TTL,
    TimeUnit.MINUTES
);

 

 

 

📍 GETDEL

get으로 값을 조회한 후 delete로 삭제하는 로직의 경우, 이 두 단계 사이에 다른 요청이 끼어들 수 있음

TOCTOU(Time Of Check to Time Of Use) 문제 발생

→ 원자적(atomic) 연산 사용

→ GETDEL은 값을 조회하면서 동시에 삭제하는 단일 명령어

public boolean verify(String email, String inputCode) {
    String key = KEY_PREFIX + email;

    // 조회와 삭제를 원자적으로 수행
    String storedCode = redisTemplate.opsForValue().getAndDelete(key);

    return storedCode != null && storedCode.equals(inputCode);
}

 

 

 

📍 KEYS, SCAN

KEYS * 같이 모든 키를 조회하는 명령어는 운영 환경에서 절대 사용하면 안 됨

Redis는 단일 스레드로 동작하기 때문에 키가 수백만 개일 경우 그동안 다른 요청 전부 멈춤

→ SCAN 명령어 사용(키를 나눠서 조회하므로 서버 멈춤x)