반응형
해당 글에서는 Spring Boot 환경에서 Redis를 다루는 방법에 대해 알아봅니다.
💡 [참고] Redis 관련해서 구성 내용에 대해 궁금하시면 아래의 글을 참고하시면 도움이 됩니다.
분류 | 링크 |
Redis(Remote Dictionary Server) 이해하기 -1 : 구조 및 특징, 아키텍처 | https://adjh54.tistory.com/447 |
Redis(Remote Dictionary Server) 이해하기 -2 : MacOS 로컬 환경 구성 및 명령어 | https://adjh54.tistory.com/448 |
RedisTemplate API Document | https://adjh54.tistory.com/462 |
ValueOperations API Document | https://adjh54.tistory.com/460 |
HashOperations API Documnet | https://adjh54.tistory.com/461 |
1) Redis(Remote Dictionary Server) 및 구조
💡 Redis(Remote Dictionary Server)
- NoSQL 데이터베이스 중 하나이며 오픈소스 소프트웨어입니다. 주요한 특징은 ‘키-값(Key-Value)’ 형태로 데이터를 저장하고 이 데이터를 ‘인-메모리 데이터 저장소’에 저장합니다.
- ‘키-값(Key-Value)’ 형태로 데이터를 저장을 하며 ‘다양한 종류의 값’을 지정 가능합니다. 값으로는 문자열, 리스트, 셋, 정렬된 셋, 해시 등을 지원합니다.
- ‘인 메모리 데이터 저장소’를 사용하기에 서버의 메인 메모리에 모든 데이터를 저장하므로, 디스크 I/O를 거치는 다른 데이터베이스 시스템보다 훨씬 빠른 성능을 보여줍니다.
- 메모리에 저장되는 데이터 베이스는 디스크에 저장하여 휘발성으로 사용되는 데이터를 영구적으로 저장하는 기능을 제공하여 서버가 다운되더라도 데이터를 복구할 수 있습니다.
- 주로 데이터베이스, 캐시, 메시지 브로커 등 다양한 용도로 사용될 수 있습니다.
[ 더 알아보기 ]
💡 NoSQL
- ‘Not Only SQL’ 또는 ‘Non-Relational SQL’이라는 의미를 가지며 관계형 데이터베이스 관리 시스템(RDBMS)이 아닌 다른 형태의 데이터 저장소를 의미하며 스키마가 없거나 유연한 스키마를 가지고 있어 데이터 구조가 고정되어 있지 않습니다. 이로 인해 다양한 형태의 데이터를 저장하고 관리할 수 있습니다.
- NoSQL은 대용량 분산 데이터 처리를 위해 개발되었으며 데이터의 구조적 유연성과 확장성을 제공합니다.
💡 Redis 서버를 내리고 다시 서버를 올리면 데이터가 모두 사라질까?
- Redis는 기본적으로 인-메모리 데이터 구조 저장소이기 때문에, 서버가 종료되면 메모리에 저장된 데이터는 사라집니다.
- 그러나 Redis는 디스크에 데이터를 저장하는 영구 저장 기능을 제공합니다. 이 기능을 사용하면, 서버가 종료되어도 데이터를 복구할 수 있습니다.
- 이 영구 저장 방식은 RDB (Redis DataBase)와 AOF (Append Only File) 두 가지 방법이 있습니다.
1. spring-boot-starter-data-redis
💡 spring-boot-starter-data-redis
- Spring Data Redis를 쉽게 사용할 수 있도록 도와주는 라이브러리이며, 내장 Lettuce Client를 사용하여서 Redis의 데이터를 키-값 형태의 저장소로 이용이 가능합니다.
- 이를 통해 Redis 데이터베이스에 접근하고 데이터를 저장, 조회하는 등의 작업을 수행할 수 있습니다.
Maven Repository: org.springframework.boot » spring-boot-starter-data-redis
2. Lettuce Client
💡 Lettuce Client
- spring-boot-starter-data-redis 라이브러리 내에 내장되어 있으며 Java 환경에서 Redis를 이용하기 위한 Redis 클라이언트입니다.
- 이를 사용하면 여러 Redis 명령을 비동기적으로 실행할 수 있습니다. 또한, Netty 네트워크 프레임워크를 사용하여 구현되어서 고가용성 및 확장성을 제공하며 다중 스레드 환경을 지원합니다.
특징 | 설명 |
비동기 지원 | Lettuce는 비동기적으로 Redis 명령을 실행할 수 있습니다. |
고가용성 및 확장성 | Lettuce는 Netty 네트워크 프레임워크를 사용하여 구현되어 있어 고가용성과 확장성을 제공합니다. |
다중 스레드 환경 지원 | Lettuce는 다중 스레드 환경을 지원합니다. |
[ 더 알아보기 ]
💡 Netty
- 자바 기반의 네트워크 애플리케이션 프레임워크로 ‘비동기식 이벤트 기반 서버’를 만드는 데 사용이 됩니다.
- 높은 성능과 확장성을 갖추고 있으며, 다양한 프로토콜을 지원하고 있어 네트워크 애플리케이션 개발에 매우 유용합니다.
💡[참고] Netty와 관련된 개념은 Spring Boot Webflux와 관련이 되어 있습니다. 이와 관련되어 궁금하시면 아래의 글을 참고하시면 도움이 됩니다.
2) org.springframework.data.redis.core 주요 클래스
1. RedisTemplate
💡 RedisTemplate
- Spring Data Redis에서 제공하는 템플릿 클래스로 데이터 조작을 추상화하고 자동화해 주는 역할을 수행하여 코드의 중복을 줄여주는 클래스입니다.
- 기본적으로 데이터를 읽고 쓰는데 필요한 모든 작업을 처리해 줍니다. 이 클래스를 활용하여 String, List, Set, Sorted Set, Hash 등 다양한 Redis 데이터 구조를 쉽게 다룰 수 있습니다.
- 연결 관리, 직렬화/역직렬화, 예외 처리 등을 처리해 주며, 기본적인 Redis 데이터 액세스 코드를 간결하게 만들어줍니다
2. Redis Data Class
💡 Redis Data Class
- Redis 내에 저장되는 데이터 타입과 이를 다루는 클래스에 대해 알아봅니다.
클래스 | 데이터 타입 | 설명 |
ValueOperations | Strings | 단일 값에 대한 연산을 수행하는 클래스입니다. 주로 문자열 타입의 데이터를 다룹니다. |
BitmapOperations | Bitmaps | 비트맵 타입의 데이터를 다루는 클래스입니다. 비트맵에 비트를 설정하거나 검색하는 등의 연산을 수행합니다. |
BitFieldOperations | Bit field | 비트 필드 타입의 데이터를 다루는 클래스입니다. 비트 필드에 비트를 설정하거나 검색하는 등의 연산을 수행합니다. |
HashOperations | Hashes | 해시 타입의 데이터를 다루는 클래스입니다. 해시에 필드와 값을 추가하거나 제거하는 등의 연산을 수행합니다. |
ListOperations | Lists | 리스트 타입의 데이터를 다루는 클래스입니다. 리스트의 요소를 추가하거나 제거하는 등의 연산을 수행합니다. |
SetOperations | Sets | 세트 타입의 데이터를 다루는 클래스입니다. 세트에 요소를 추가하거나 제거하는 등의 연산을 수행합니다. |
ZSetOperations | Sorted Sets | 정렬된 세트 타입의 데이터를 다루는 클래스입니다. 세트의 요소를 추가하거나 제거하면서, 각 요소에 점수를 부여하여 순서를 정렬합니다. |
GeoOperations | Geospatial Indexes | 지리적 인덱스 타입의 데이터를 다루는 클래스입니다. 지리적 인덱스에 위치를 추가하거나 검색하는 등의 연산을 수행합니다. |
HyperLogLogOperations | Hyperloglogs | 하이퍼로그로그 타입의 데이터를 다루는 클래스입니다. 하이퍼로그로그에 요소를 추가하거나 카운트하는 등의 연산을 수행합니다. |
StreamOperations | Streams | 스트림 타입의 데이터를 다루는 클래스입니다. 스트림에 메시지를 추가하거나 읽는 등의 연산을 수행합니다. |
3) 개발환경 구성
💡 개발환경 구성
- 해당 개발환경에서는 API 호출을 통해 String 데이터를 기반으로 Redis 내에 저장하고 조회, 삭제하는 간단한 테스트 환경을 구성합니다.
1. 초기 Redis 로컬 서버 구축
💡 초기 Redis 로컬 서버 구축
- 아래의 글을 기반으로 초기 로컬 서버가 구성되었다는 가정하에 아래의 환경 구축을 수행합니다.
2. 의존성 주입
💡 의존성 주입
- spring boot stater redis를 이용하여 의존성을 주입합니다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
}
Maven Repository: org.springframework.boot » spring-boot-starter-data-redis
3. application.properties
💡 application.properties
- 구성한 redis의 기본 속성을 구성합니다.
spring:
redis:
host: localhost
port: 6379
💡 [참고] Redis의 application.properties 속성
속성 | 설명 |
spring.redis.host | Redis 서버의 호스트 이름 |
spring.redis.port | Redis 서버의 포트 번호 |
spring.redis.password | Redis 서버에 접근하기 위한 비밀번호 |
spring.redis.database | 사용할 Redis 데이터베이스의 인덱스 |
spring.redis.ssl | SSL 연결 사용 여부 |
spring.redis.timeout | Redis 서버와의 연결 시간 제한 |
spring.redis.lettuce.pool.max-active | 동시에 유지할 수 있는 최대 연결 수 |
spring.redis.lettuce.pool.max-idle | 유휴 상태에서 유지할 수 있는 최대 연결 수 |
spring.redis.lettuce.pool.min-idle | 유휴 상태에서 유지할 수 있는 최소 연결 수 |
Spring Data Redis's Property-Based Configuration | Baeldung
4. RedisConfig
💡 RedisConfig
- 주요 Redis를 구성하기 위한 환경설정을 위한 Configuration 클래스입니다.
- 해당 클래스에서는 application.properties로부터 받아온 host, port 정보를 기반으로 Redis 클라이언트를 구성합니다. 이를 통해서 추후 Redis와의 통신에 사용됩니다.
1. RedisConnectionFactory
- Redis와의 연결을 위한 'Connection'을 생성하고 관리하는 메서드입니다.
- 해당 여기서는 LettuceConnectionFactory를 사용하여 host와 port 정보를 기반으로 연결을 생성합니다.
2. RedisTemplate<String, Object>
- Redis 데이터 처리를 위한 템플릿을 구성하는 메서드입니다. 이 메서드에서는 Redis와의 데이터 통신을 처리하기 위한 직렬화를 수행합니다.
항목 | 설명 |
RedisConnectionFactory | - Spring Data Redis에서 제공하는 인터페이스. Redis 커넥션을 생성하고 관리함. |
LettuceConnectionFactory | - 해당 클래스는 RedisConnectionFactory 인터페이스를 구현한 클래스입니다. - 내장 되어 있는 Lettuce 라이브러리를 사용하여 Redis 커넥션을 생성하고 관리하는데 사용됩니다. 이를 통해 Redis 서버와 비동기식 통신을 수행할 수 있습니다. |
RedisTemplate | - Spring Data Redis에서 제공하는 클래스. Redis 데이터베이스에 접근하고 데이터를 처리하는 데 사용됩니다. 타입 안전성을 제공하고, 직렬화 및 역직렬화를 처리하며, Redis의 기본적인 데이터 구조를 지원합니다. |
package com.adjh.multiflexapi.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.connection.lettuce.LettuceConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.StringRedisSerializer;
/**
* Redis 환경 설정
*
* @author : lee
* @fileName : RedisConfig
* @since : 3/29/24
*/
@Configuration
public class RedisConfig {
@Value("${spring.redis.host}")
private String host;
@Value("${spring.redis.port}")
private int port;
/**
* Redis 와의 연결을 위한 'Connection'을 생성합니다.
*
* @return
*/
@Bean
public RedisConnectionFactory redisConnectionFactory() {
return new LettuceConnectionFactory(host, port);
}
/**
* Redis 데이터 처리를 위한 템플릿을 구성합니다.
* 해당 구성된 RedisTemplate을 통해서 데이터 통신으로 처리되는 대한 직렬화를 수행합니다.
*
* @return
*/
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
// Redis를 연결합니다.
redisTemplate.setConnectionFactory(redisConnectionFactory());
// Key-Value 형태로 직렬화를 수행합니다.
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new StringRedisSerializer());
// Hash Key-Value 형태로 직렬화를 수행합니다.
redisTemplate.setHashKeySerializer(new StringRedisSerializer());
redisTemplate.setHashValueSerializer(new StringRedisSerializer());
// 기본적으로 직렬화를 수행합니다.
redisTemplate.setDefaultSerializer(new StringRedisSerializer());
return redisTemplate;
}
}
5. RedisService : Interface
메서드 | 반환 값 | 설명 |
setValues(String key, String value) | int | - key, value 값을 기준으로 등록하거나 수정할때 사용하는 Service 입니다. |
setValues(String key, String value, Duration duration) | int | - key, value, duration 값을 기준으로 등록하거나 수정할때 사용하는 Service입니다.(*duration 값은 메모리에 데이터가 유지될 시간을 지정합니다) |
getValue(String key) | String | - key 값을 기반으로 Redis 데이터의 값을 조회합니다. |
deleteValue(String key) | int | - key 값을 기반으로 Redis 데이터를 삭제합니다. |
package com.adjh.multiflexapi.service;
import org.springframework.stereotype.Service;
import java.time.Duration;
/**
* Redis Service를 관리하는 인터페이스
*
* @author : lee
* @fileName : RedisService
* @since : 3/29/24
*/
@Service
public interface RedisService {
int setValues(String key, String value); // 값 등록 / 수정
int setValues(String key, String value, Duration duration); // 값 등록 / 수정
String getValue(String key); // 값 조회
int deleteValue(String key); // 값 삭제
}
6. RedisServiceImpl : interface 구현체
메서드 | 반환 값 | 설명 |
setValues(String key, String value) | int | - key, value 값을 기준으로 등록하거나 수정할때 사용하는 Service 입니다. |
setValues(String key, String value, Duration duration) | int | - key, value, duration 값을 기준으로 등록하거나 수정할때 사용하는 Service입니다.(*duration 값은 메모리에 데이터가 유지될 시간을 지정합니다) |
getValue(String key) | String | - key 값을 기반으로 Redis 데이터의 값을 조회합니다. |
deleteValue(String key) | int | - key 값을 기반으로 Redis 데이터를 삭제합니다. |
package com.adjh.multiflexapi.service.impl;
import com.adjh.multiflexapi.service.RedisService;
import lombok.RequiredArgsConstructor;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.ValueOperations;
import org.springframework.stereotype.Service;
import java.time.Duration;
/**
* RedisService의 구현체
*
* @author : lee
* @fileName : RedisServiceImpl
* @since : 3/29/24
*/
@Service
@RequiredArgsConstructor
public class RedisServiceImpl implements RedisService {
private final RedisTemplate<String, Object> redisTemplate;
/**
* Redis 값을 등록/수정합니다.
*
* @param {String} key : redis key
* @param {String} value : redis value
* @return {void}
*/
@Override
public void setValues(String key, String value) {
ValueOperations<String, Object> values = redisTemplate.opsForValue();
values.set(key, value);
}
/**
* Redis 값을 등록/수정합니다.
*
* @param {String} key : redis key
* @param {String} value: redis value
* @param {Duration} duration: redis 값 메모리 상의 유효시간.
* @return {void}
*/
@Override
public void setValues(String key, String value, Duration duration) {
ValueOperations<String, Object> values = redisTemplate.opsForValue();
values.set(key, value, duration);
}
/**
* Redis 키를 기반으로 값을 조회합니다.
*
* @param {String} key : redis key
* @return {String} redis value 값 반환 or 미 존재시 빈 값 반환
*/
@Override
public String getValue(String key) {
ValueOperations<String, Object> values = redisTemplate.opsForValue();
if (values.get(key) == null) return "";
return String.valueOf(values.get(key));
}
/**
* Redis 키값을 기반으로 row 삭제합니다.
*
* @param key
*/
@Override
public void deleteValue(String key) {
redisTemplate.delete(key);
}
}
[ 더 알아보기 ]
💡Redis에서 등록/수정/삭제를 수행하는 경우 @Transactional 어노테이션이 필요할까?
- 일반적으로 RDBMS에서 사용되는 @Transactional 어노테이션입니다. Redis의 경우 각 명령은 원자적으로 실행하기에 @Transaction 어노테이션이 필요로 하지 않습니다. 그러나 만약 여러 개의 Redis 연산을 한 트랜잭션으로 묶어야 하는 경우 @Transactional을 사용할 수 있습니다.
7. RedisController: controller
Endpoint | HTTP Method | 반환값 | 설명 |
/api/v1/redis/getValue | GET | value | - Redis 값을 조회합니다. |
/api/v1/redis/setValue | POST | 실패:0, 성공:1 | - Redis의 값을 추가/수정합니다.(duration 값이 포함되면 데이터 유효기간을 지정합니다.) |
/api/v1/redis/deleteValue | DELETE | 실패:0, 성공:1 | - Redis 값을 삭제합니다. |
package com.adjh.multiflexapi.controller;
import com.adjh.multiflexapi.common.codes.SuccessCode;
import com.adjh.multiflexapi.common.response.ApiResponse;
import com.adjh.multiflexapi.model.RedisDto;
import com.adjh.multiflexapi.service.RedisService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
/**
* Redis 동작 Controller
*
* @author : lee
* @fileName : RedisController
* @since : 3/29/24
*/
@RestController
@RequestMapping("/api/v1/redis")
public class RedisController {
private final RedisService redisService;
public RedisController(RedisService redisService) {
this.redisService = redisService;
}
/**
* Redis의 값을 조회합니다.
*
* @param redisDto
* @return
*/
@GetMapping("/getValue")
public ResponseEntity<ApiResponse<Object>> getValue(@RequestBody RedisDto redisDto) {
String result = redisService.getValue(redisDto.getKey());
ApiResponse<Object> ar = ApiResponse.builder()
.result(result)
.resultCode(SuccessCode.SELECT.getStatus())
.resultMsg(SuccessCode.SELECT.getMessage())
.build();
return new ResponseEntity<>(ar, HttpStatus.OK);
}
/**
* Redis의 값을 추가/수정합니다.
*
* @param redisDto
* @return
*/
@PostMapping("/setValue")
public ResponseEntity<ApiResponse<Object>> setValue(@RequestBody RedisDto redisDto) {
int result = 0;
if (redisDto.getDuration() == null) {
result = redisService.setValues(redisDto.getKey(), redisDto.getValue());
} else {
result = redisService.setValues(redisDto.getKey(), redisDto.getValue(), redisDto.getDuration());
}
ApiResponse<Object> ar = ApiResponse.builder()
.result(result)
.resultCode(SuccessCode.SELECT.getStatus())
.resultMsg(SuccessCode.SELECT.getMessage())
.build();
return new ResponseEntity<>(ar, HttpStatus.OK);
}
/**
* Redis의 key 값을 기반으로 row를 제거합니다.
*
* @param redisDto
* @return
*/
@PostMapping("/deleteValue")
public ResponseEntity<ApiResponse<Object>> deleteRow(@RequestBody RedisDto redisDto) {
int result = redisService.deleteValue(redisDto.getKey());
ApiResponse<Object> ar = ApiResponse.builder()
.result(result)
.resultCode(SuccessCode.SELECT.getStatus())
.resultMsg(SuccessCode.SELECT.getMessage())
.build();
return new ResponseEntity<>(ar, HttpStatus.OK);
}
}
4) 결과확인: Postman & redis-cli
Endpoint | HTTP Method | 반환 값 | 설명 |
/api/v1/redis/getValue | GET | value | - Redis 값을 조회합니다. |
/api/v1/redis/setValue | POST | 실패:0, 성공:1 | - Redis의 값을 추가/수정합니다.(duration 값이 포함되면 데이터 유효기간을 지정합니다.) |
/api/v1/redis/deleteValue | DELETE | 실패:0, 성공:1 | - Redis 값을 삭제합니다. |
1. Redis 값 등록 / 수정
💡 Redis 값 등록
- redis 내의 값을 저장하기 위해 해당 endpoint로 body를 담아서 호출하였습니다.
💡 redis-cli를 통해서 GET hello 명령어를 호출하여 값을 얻었습니다.
- 해당 값을 확인해 보았을 때 “\xec\x95\x88\xeb\x85\x95\xed\x95\x98\xec\x84\xb8\xec\x9a\x94”로 출력이 되고 있습니다.
- 이는 redis-cli가 UTF-8이 적용이 되지 않아서 발생하는 문제라고 합니다.
💡 [참고] redis-cli를 UTF-8로 적용하는 명령어
$ redis-cli --raw
💡 Redis 값 수정
- redis 내의 값을 수정하기 위해 해당 endpoint로 body를 담아서 호출하였습니다.
💡 수정을 수행한 이후에 redis-cli 내에서 다시 한번 조회를 하여서 바뀜을 확인하였습니다.
2. Redis 값 조회
💡 Redis 값 조회
- 이전에 생성한 값을 조회하기 위해 key 값을 전달하여 조회하여 결과를 얻었습니다.
3. Redis 값 삭제
💡 Redis 값 삭제
- Redis의 값을 삭제하기 위해 key 값을 전송하여서 결과 값으로 1로 반환을 받아서 값이 삭제됨을 확인하였습니다.
💡 redis-cli를 통해서 값을 조회하였고, key-value row가 삭제됨을 확인하였습니다.
오늘도 감사합니다. 😀
반응형