본문 바로가기
Spring/ETC

Spring Boot와 Redis로 입문자용 구현

by YoonJong 2025. 4. 2.
728x90

Redis(Remote Dictionary Server) : 인메모리 데이터 구조 저장소로, 키-값 쌍을 빠르게 저장하고 조회할 수 있는 오픈소스 NoSQL

 

Redis 주요 특징

- 메모리 기반

 ㄴ디스크 기반 DB(MySQL)보다 훨씬 빠름 (수십만 QPS 처리 가능).

 ㄴ단점: RAM 크기에 따라 저장량 제한, 전원 끄면 데이터 손실 (설정으로 보완 가능).

 

- 다양한 데이터 구조 지원

   ㄴ문자열(String): 기본 키-값 (예: SET key value).

   ㄴ리스트(List): 순서 있는 데이터 (예: LPUSH, RPOP).

   ㄴ해시(Hash): 필드-값 쌍 (예: HSET user id 1 name 홍길동).

   ㄴ셋(Set): 중복 없는 집합 (예: SADD).

   ㄴ정렬된 셋(Sorted Set): 점수 기반 정렬 (예: ZADD).

 

- 캐싱, 세션 관리, 실시간 분석

   ㄴ캐싱: 자주 사용하는 데이터(예: 환율)를 저장해 DB 부하 감소.

   ㄴ세션 관리: 사용자 로그인 세션 저장 (TTL로 만료 관리).

   ㄴ실시간 분석: 리더보드, 카운터 등 빠른 집계.

 

- TTL 지원: 데이터에 만료 시간 설정 가능 (예: EXPIRE key 3600).

- 단순성: 복잡한 쿼리 언어(SQL) 대신 간단한 명령어 사용.

 

Redis 동작원리

- 인메모리 : 데이터가 RAM에 상주해 디스크 I/O 없이 즉시 접근 가능

- 싱글 스레드 : Redis는 단일 스레드로 요청을 처리하지만, 비동기 이벤트 루프를 사용해 높은 처리량을 유지

    ㄴ 장점 : 락 충돌 없음 , 단순한 아키텍처

    ㄴ 단점 : CPU 코어 하나만 사용 -> 멀티코어 사용하려면 샤딩 필요

- 영속성 옵션 : 기본은 인메모리지만, 설정으로 디스크에 저장 가능 (RDB 스냅샷, AOF 로그)

- 클라이언트-서버 : Redis 서버 (redis-server)가 실행되고, 클라이언트(redis-cli 또는 Spring boot 등)이 TCP 로 연결해 조작

 

Redis 사용 이유 

- 속도 : 밀리초 단위 응답으로 대용량 트래픽 처리

- 유연성 : 다양한 데이터 구조로 복잡한 로직 구현 가능

- 확장성 : 클러스터링, 샤딩으로 대규모 환경 지원

- Spring boot 연계 : spring-boot-starter-data-redis 로 쉬운 통합 가능, 캐싱(@Cacheable) 지원

 

Redis 한계

- 메모리 의존 : RAM 용량 초과 시 데이터 손실 위험

- 영속성 제한 : 기본 설정으로는 재부팅 시 데이터 사라짐

- 복잡한 쿼리 불가 : RDBMS처럼 조인 등 복잡한 검색은 불가능

 

build.gradle 추가

// Spring Data Redis (Redis 연동 핵심 의존성)
    implementation 'org.springframework.boot:spring-boot-starter-data-redis'

 

application.yml 설정

spring:
  datasource:
    url: jdbc:h2:mem:testdb
    driver-class-name: org.h2.Driver
    username: sa
    password:
  jpa:
    hibernate:
      ddl-auto: update
  data:
    redis:
      host: localhost
      port: 6379
logging:
  level:
    org.springframework: INFO

 

Controller 

@RestController
@RequestMapping("/api")
public class ExchangeRateController {

    @Autowired
    private ExchangeRateService exchangeRateService;

    // 환율 저장 (POST)
    @PostMapping("/exchange-rate")
    public String saveRate(@RequestParam String pair,
                           @RequestParam String rate) {
        exchangeRateService.saveExchangeRate(pair, rate);
        return "저장 완료: " + pair + " = " + rate;
    }

    // 환율 조회 (GET)
    @GetMapping("/exchange-rate/{pair}")
    public String getRate(@PathVariable String pair) {
        return exchangeRateService.getExchangeRate(pair);
    }

    // 환율 삭제 (DELETE)
    @DeleteMapping("/exchange-rate/{pair}")
    public String deleteRate(@PathVariable String pair) {
        exchangeRateService.deleteExchangeRate(pair);
        return "삭제 완료: " + pair;
    }

    // TTL 확인 (GET)
    @GetMapping("/exchange-rate/{pair}/ttl")
    public String getRateTtl(@PathVariable String pair) {
        Long ttl = exchangeRateService.getTtl(pair);
        return "TTL: " + pair + " = " + ttl + "초";
    }
}

 

Service 

@Slf4j
@Service
public class ExchangeRateService {

    @Autowired
    private RedisTemplate<String, String> redisTemplate;

    // 환율 데이터 저장
    public void saveExchangeRate(String currencyPair,
                                 String rate) {
        String cacheKey = "exchangeRate:" + currencyPair;
        redisTemplate.opsForValue().set(cacheKey, rate); // 기본 저장
        redisTemplate.expire(cacheKey, 1, TimeUnit.HOURS); // 1시간 TTL 설정
        log.info("환율 저장: " + currencyPair + " = " + rate);
    }

    // 환율 데이터 조회
    public String getExchangeRate(String currencyPair) {
        String cacheKey = "exchangeRate:" + currencyPair;
        String rate = redisTemplate.opsForValue().get(cacheKey);
        if (rate != null) {
            log.info("Redis에서 환율 조회: " + currencyPair + " = " + rate);
            return rate;
        }
        log.info("환율 없음: " + currencyPair);
        return "Not Found";
    }

    // 환율 데이터 삭제
    public void deleteExchangeRate(String currencyPair) {
        String cacheKey = "exchangeRate:" + currencyPair;
        redisTemplate.delete(cacheKey);
        log.info("환율 삭제: " + currencyPair);
    }

    // TTL 확인
    public Long getTtl(String currencyPair) {
        String cacheKey = "exchangeRate:" + currencyPair;
        Long ttl = redisTemplate.getExpire(cacheKey, TimeUnit.SECONDS);
        if (ttl != null && ttl > 0) {
            log.info("남은 TTL: " + currencyPair + " = " + ttl + "초");
            return ttl;
        }
        log.info("TTL 없음 또는 만료: " + currencyPair);
        return -1L;
    }
}

 

스프링부트 실행 후 POSTMAN 또는 curl 테스트 진행

저장

curl -X POST "http://localhost:8080/api/exchange-rate?pair=USD_KRW&rate=1350"

조회

curl "http://localhost:8080/api/exchange-rate/USD_KRW"

TTL 확인 > TTL 시간이 만료되면 자동 삭제

curl "http://localhost:8080/api/exchange-rate/USD_KRW/ttl"

삭제

curl -X DELETE "http://localhost:8080/api/exchange-rate/USD_KRW"

 

 

-- 참고 --

mac 기준 redis 설치

brew --version

없으면 

/bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

 

버전 확인 후 redis 설치

brew install redis

 

redis 버전 확인

redis-server --version

 

redis 시작

redis-server

 

redis 접속

redis-cli

 

 

레디스 명령어 정리

 

728x90

댓글