Spring발행일 2025. 2. 13.원본 https://blog.naver.com/jword_/223758250946 ↗

스프링(Spring) Redis 연동 정리(설치, 설정)

스프링(Spring) Redis 연동 정리(설치, 설정) — #개발자의도구들 #Redis #레디스 #스프링Redis #springredis #redisconf #redis설치 #redis다운로드 #레...

#Spring#Naver Blog

#개발자의도구들 #Redis #레디스 #스프링Redis #springredis #redisconf #redis설치 #redis다운로드 #레디스설치 #레디스다운로드

🚨📝 📖 📒✏️💡🔍

​

AI스쿨 msa기반 java 백엔드 코스 중에 공부한 내용을 작성하였습니다

\* 현재 Spring x 프로젝트를 진행하고 있습니다. 전체 목차를 보시려면 여기를 눌러주세요.

\* 해당 프로젝트 이전에 공부한 내용을 보시려면 여기를 눌러주세요.

​

Redis와 Cache 혼동를 혼동하지 마시오

Redis는 In-memory DB라고 알려져있습니다. DB에 대한 깊은 지식이 없다보니, 단순히 데이터를 memory에 저장하는구나하고 넘어갔습니다. 하지만, 이 둘은 전혀 다른 개념이었습니다.

​

Redis는 기본적으로 서버의 역할을 수행합니다. DB처럼 하나의 저장소 서버인 셈이죠. 이때 Redis는 in-memory 방식으로 데이터를 관리할 뿐입니다.

javascript 코드 예제
                                    저는 기존 MySQL과 같은 DB는 무조건 Disk에 in-memory DB는 무조건 Memory에 저장되는 줄 알았습니다.
하지만 MySQL과 같은 DB도 memory에 올려서 사용합니다.

💡 중요한건 Redis는 서버일 뿐이며, 개발자가 직접적으로 Memory에 데이터를 올려다 쓰는 개념이
아니라는 점이 중요합니다.

기본적으로 서버이기 때문에 네트워크를 사용하며, 이는 적접 캐시에 데이터를 두고 사용하는 것보다 훨씬 느립니다.

​

Cache memory > Cache Server > DB(Disk)

💡 직접 메모리에 데이터를 두려면 Caffeine과 같은 라이브러리를 사용하세요.

Redis

Redis는 실제 in-memory DB를 구현해둔 캐시 서버입니다. 그렇기에 일반적인 RDBMS와는 다른 방식으로 데이터를 관리하는데 기본적으로 Key-Value 형태로 데이터를 관리합니다. (NoSQL)

javascript 코드 예제
                                    💡 String, BitMap, hashs, set, sorted set과 같은 다양한 자료구조를 가집니다.

Redis를 다운하기

이제 부터는 본격적으로 리눅스와 친해질 시간입니다. 대부분이 서버용 프로그램들은 리눅스 환경에서 사용됩니다. 이를 위해서 리눅스 관련 명령어를 꼭 정리해 두세요!

javascript 코드 예제
                                    sudo apt update
sudo apt install lsb-release curl -y
javascript 코드 예제
                                    // Redis GPG 키 등록 Redis 저장소 추가
curl -fsSL https://packages.redis.io/gpg | sudo gpg --dearmor -o /usr/share/keyrings/redis-archive-keyring.gpg
echo "deb [signed-by=/usr/share/keyrings/redis-archive-keyring.gpg] https://packages.redis.io/deb $(lsb_release -cs) main" | sudo tee /etc/apt/sources.list.d/redis.list
javascript 코드 예제
                                    // 저장소 목록 업데이트 후 Redis 설치
sudo apt update
sudo apt install redis -y
javascript 코드 예제
                                    // 설치 확인
redis-server --version

Redis를 설정하기

javascript 코드 예제
                                    // conf 파일 열기
sudo nano /etc/redis/redis.conf
javascript 코드 예제
                                    // 🔑 설정하기

// 외부 포트 열기
bind = 0.0.0.0

// pw 설정하기
protected-mode yes
requirepass [pw 설정]
javascript 코드 예제
                                    // 재시작 명령어
sudo sytemctl restart redis-server.service
javascript 코드 예제
                                    // redis 실행 테스트
redis-cli -h [hostserver ip] -P 6379
AUTH [pw]

ping -> pong 응답시 서버 정상 작동

Spring관 연동하기

Spring 서버와 Redis 연동하는 방법을 정리하였습니다. Local(Window)에서 VM의 Linux로 연결하는 방법을 정리하였습니다.

​

yaml파일

javascript 코드 예제
                                    spring:
  data:
    redis:
      host: [linux server ip]
      port: 6379
      password: [redis conf에 설정한 pw]

비즈니스 로직 정리

이미지

javascript 코드 예제
                                    👉 현재 로직
* User Profile에 해당하는 DTO를 클라이언트가 전달받는 상황

1. Clinet의 요청에 FrontServer는 DB에서 Entity를 찾고 이후 dto를 만들어 client에게 전달한다
- dto를 만드는 과정에서 api 호출이 들어감 (user 팔로잉, 팔로워 수 등)

이미지

javascript 코드 예제
                                    👉 Redis가 도입된 이후 로직

1. Client의 요청에 FrontServer는 우선적으로 Redis에서 Dto를 찾는다.
-- DTO가 존재한다면 바로 Client에게 전달한다.

2. DTO가 없다면 기존 로직 처럼 dto를 만들어서 보낸다.
-- 보낸 이후에 해당 user에 대한 dto를 redis Server로 전달하여 저장한다.

코드 정리

javascript 코드 예제
                                    package com.example.frontServer.service.user

import com.example.frontServer.dto.user.response.UserProfileGetResult
import com.fasterxml.jackson.databind.ObjectMapper
import io.github.oshai.kotlinlogging.KotlinLogging
import org.springframework.data.redis.core.RedisTemplate
import org.springframework.stereotype.Service

@Service
class RedisUserService(
    private val objectMapper: ObjectMapper,
    private val redisTemplate: RedisTemplate<String, String>
) {
    private val logger = KotlinLogging.logger {}

    fun save(redisDto: UserProfileGetResult) {
        val json = objectMapper.writeValueAsString(redisDto)
        redisTemplate.opsForValue().set("user:profile:${redisDto.id}", json)
    }

    fun findByUserId(userId: Long): UserProfileGetResult? {
        val key = "user:profile:${userId}"
        val json: String? = redisTemplate.opsForValue().get(key)

        return json?.let {
            objectMapper.readValue(it, UserProfileGetResult::class.java)
        }?: run {
            logger.error { "can't find user- id: $userId" }
            null
        }
    }
}
javascript 코드 예제

// in board Service
@Transactional
    fun findMainUserProfile(request: UserProfileGetRequest): UserProfileGetResult{
        // first find in redis
        val cachedResult = redisUserService.findByUserId(request.userId)
        cachedResult?.let {
            return it
        }

        // find user
        val user = userRepository.findById(request.userId).orElse(null)

        // find following, follower count
        if (user != null) {
            val newProfileResult = UserProfileGetResult.of(
                user,
                userFollowService.findFollowCount(request.userId)
            )

            // save in redis
            redisUserService.save(newProfileResult)

            return newProfileResult

        } else {
            throw NotFoundEntityException(
                entityType = EntityType.USER.code,
                id = request.userId
            )
        }
    }

Redis의 Key-Value

redis는 key-value 형태로 데이터가 저장되기 때문에 탐색 역시 이 key를 통해서 탐색됩니다. value의 경우 거의 대부분의 경우 json으로 저장하면 됩니다. 이런 이유로 저는 objectMapper를 사용하여 원하는 DTO를 Json으로 변환하여 저장하였습니다.

javascript 코드 예제
                                    👉 key의 경우 Redis Insight와 같은 도구들에서 : 을 기준으로 데이터를 분류해 줍니다.
실제 개발자들은 key를 : 로 구분해서 넣는 경우가 많다고 합니다.

공식적인 규칙은 아니지만, 실제 업무상에서 암묵적으로 만들어진 규칙이라고 보시면 됩니다.

Redis의 여러 정책들

Redis는 기본적으로 캐시이기 때문에, 일정 시간이 지나거나 혹은 저장소가 가득차면 데이터를 삭제 하거나 교채하는 방법을 사용합니다.

​

Redis에서 사용되는 여러가지 정책들을 정리하였습니다.

javascript 코드 예제
                                    👉 noeviction: 메모리 초과시 새로운 데이터 쓰기를 거부합니다.

👉 allkeys-lru: 모든 키를 대상으로 최근에 덜 사용한 데이터를 우선 삭제합니다.

👉🔑 volatile-lru : TTL이 설정된 키를 대상으로 최근에 가장 덜 삭제된 키를 삭제합니다.

👉 allkeys-random: key를 random으로 삭제합니다

👉 volatile-random: TTL이 설정된 키를 대상으로 random 삭제합니다.

👉 volatile = TTL이 가장 짧은 키부터 삭제합니다.

가장 많이 사용되는 방식은 Volatile-lru 방식입니다. Redis의 TTL은 key에 일정 시간을 걸어두고 시간이 만료되면 자동으로 삭제합니다. Redis는 Cache이기 때문에 이런 TTL 설정이 필수⭐라고 할 수 있습니다.

TTL을 함께 넣어서 저장하기

현재 로직에서는 TTL을 따로 넣어서 저장하고 있지 않습니다. 이런 경우 캐시가 가득찰 때까지 영구히 보관됩니다.

​

로직을 추가하여 TTL을 설정해 봅시다.

javascript 코드 예제
                                    fun save(redisDto: UserProfileGetResult) {
        val json = objectMapper.writeValueAsString(redisDto)
        redisTemplate.opsForValue().set("user:profile:${redisDto.id}", json, 60, TimeUnit.MINUTES)
    }

set()에 추가 parameter를 전달해 줍시다. 이제 모든 key에 대하여 60분의 TTL이 적용되었습니다.

Hash를 사용하지 마시오...

처음 구현한 로직에서는 Hash를 사용했습니다. 하지만, 여러 성능적인 이슈 때문에 Hash를 사용하지 않는 것이 낫겠다고 판단하였습니다.

​

javascript 코드 예제
                                    👉 Hash를 사용하면 다음과 같이 데이터가 저장된다고 보면 된다.

{
   // key
  "user:100": { //value
    "name": "Alice",
    "age": "30",
    "email": "alice@example.com"
  }
}

😯 근데 문제는 Transaction 지원이 안되기 때문에 각 필드의 값이 모두 개별적으로 전달된다.
-- 이는 서버에 과한 오버헤드를 야기한다.

실제 메모리 사용량?

😯 여담으로 Redis와 같은 Cache 서버는 실제 사용량 보다 2~3배 많은 메모리를 할당해야 한다고 합니다. 이는 서버의 성능이슈와 관련이 있습니다.

​