Spring Boot
Spring Data Redis 설정(1) - RedisTemplate 설정
sendkite
2025. 4. 27. 16:57
- Redis 설정 관련하여 아키텍처, 직렬화/역직렬화에 대한 기록
1. Redis 아키텍처
- 설정에 앞서 아키텍처에 따라 Connection 설정 방법에 차이가 있어 아키텍처를 살펴본다.
- Redis를 사용하는 아키텍처는 크게 4가지가 있다. (참고 - https://docs.spring.io/spring-data/redis/reference/redis/connection-modes.html)
1) StandAlone
- 1대의 Redis 서버
2) Redis Replication (master/slave 구조)
- master (읽기/쓰기), slave (master 데이터 복사 - 읽기전용)로 구성
- master → slave 복사시간이 1-2초 소요되는 단점이라 잘 쓰이지 않는 구조이다.
- redis cluster를 잘 운영하지 못할때 사용한다.
3) Redis Sentinel
- master/replica 구조에서 primary 장애 시 자동 장애 조치(auto failover)를 수행 구조
- sentinel 노드 과반 수 이상이 master 장애를 감지하면 slave 하나를 master로 승격
- 지속적으로 모티터링/감시만 하는 node(sentinel)를 따로 관리해줘야한다.
- client가 올바른 노드를 찾을 수 있도록 도움
- 최소 3개 이상의 노드를 권장
- 감시 역할은 Redis와 동일한 노드에 구성해도 되고, 별로로 구성해도 됨
- 지속적으로 모티터링/감시만 하는 node(sentinel)를 따로 관리해줘야한다.
- 일반적으로 단일 master로 구성함,
- master 하나가 모든 쓰기 작업을 처리해야하는 단점 있음
- 대규모 쓰기 작업시 병목 발생
- Slave에 복제되는 Latency 증가
4) Redis Cluster
- 최소 3대의 마스터 노드 필요, 각 노드 당 최소 Slave node 필요함
- 데이터는 16384개 슬롯으로 나뉘어 특정 Master에 할당
- 모든 데이터는 master 단위로 sharding되고, slave 단위로 복제됨 (즉 슬롯으로 sharding함)
- 설정과 관리가 매우 복잡하고 특정 작업(ex - 다중키 작업)은 일부 제한이 있을 수 있음
application.yml에서 어떤 Connection mode인지에 따라서 다른 환경 변수를 사용할 수 있다.
3. Redis 설정
- OMS는 본격적으로 Redis를 활용하는 구조가 아니기 때문에 StandAlone으로 설정을 진행한다.
Redis 접속 설정
- Lettue, Jedis 선택지가 있으나 Lettue가 성능상 더 좋다.
@Configuration
class RedisConfig {
@Bean
fun connectionFactory(): LettuceConnectionFactory {
val redisConfig = RedisStandaloneConfiguration("localhost", 6379)
val clientConfig =
LettuceClientConfiguration
.builder()
.commandTimeout(Duration.ofSeconds(30)) // 명령 실행 타임아웃
.clientOptions(
ClientOptions
.builder()
.autoReconnect(true) // 자동 재연결
.socketOptions(
SocketOptions
.builder()
.connectTimeout(Duration.ofSeconds(5)) // 네트워크 연결 타임아웃
.build(),
).disconnectedBehavior(ClientOptions.DisconnectedBehavior.REJECT_COMMANDS) // // 연결 끊겼을 때 처리 방식
.build(),
)
.build()
return LettuceConnectionFactory(redisConfig, clientConfig)
}
}
- disconnectedBehavior 옵션 다른 선택지
- DEFAULT: 요청을 큐에 쌓았다가 나중에 처리 (❗️주의: 메모리 소비 증가 가능)
- REJECT_COMMANDS: 연결이 없으면 명령을 즉시 거부 (→ 예외 발생)
- ACCEPT_COMMANDS: 연결이 끊겨도 명령을 계속 받아서 큐에 저장
RedisTemplate 설정
- Key-Value 구조가 <String, String> 이라면 StringRedisTemplate를 사용한다.
- 라이브러리 자체에서 최적화 설정이 되어 있기 때문이다.
@Bean("stringRedisTemplate")
fun stringRedisTemplate(
@Qualifier("connectionFactory") connectionFactory: LettuceConnectionFactory,
): StringRedisTemplate = StringRedisTemplate(connectionFactory)
1) RedisTemplate<String, Object> - 가장 기본적인 사용 방법
- 아래와 같이 String은 StringRedisSerializer로, Object는 GenericJackson2JsonRedisSerializer로 객체를 JSON 형태로 직렬화하여 저장한다.
@Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(redisConnectionFactory());
// 직렬화 설정
template.setKeySerializer(new StringRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return template;
}
- 이 방식의 단점
- Json 객체를 그대로 바이트 배열로 변환하여 저장하므로, 저장 공간이 많이 사용됨
- 설정에 따라 직렬화 시에 클래스 정보가 포함되어 여러 문제가 생길 수 있음
- ObjectMapper를 사용하여 직렬화를 시도함
- 참고 - Spring Redis 직렬화의 한계 / https://mangkyu.tistory.com/402
- Redis에 저장 데이터가 크다면 직접 직렬화를 구현하여 압축 저장하는 방법도 있다.
2) RedisTemplate<String, ByteArray>로 구현하고 ObjectMapper를 커스텀하는 방식
- Redis에 byteArray로 관리하고, ObjectMapper를 직접 구현하여 저장 포멧을 최적화한다. (smile, avro..)
@Bean("objectRedisTemplate")
fun objectRedisTemplate(
@Qualifier("connectionFactory") connectionFactory: LettuceConnectionFactory,
): RedisTemplate<String, ByteArray> {
val redisTemplate = RedisTemplate<String, ByteArray>().apply {
keySerializer = StringRedisSerializer()
hashKeySerializer = StringRedisSerializer()
setConnectionFactory(connectionFactory)
}
return redisTemplate
}
@Bean("redisObjectMapper")
fun redisObjectMapper(): ObjectMapper {
return SmileMapper().registerModules(JavaTimeModule())
.configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, true)
.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false)
.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false)
.setSerializationInclusion(JsonInclude.Include.ALWAYS)
.disable(MapperFeature.USE_ANNOTATIONS)
}
- smile로 저장함으로 JSON 문자열 대비 2/3 절약된 크기 저장
- 전반적인 고성능
- 단점으로는 ObjecMapper 커스텀 구현 및 redis 저장소에서 데이터 읽기가 불편할 수 있다.
반응형