스프링부트

Spring Data Redis - Auto Configuration을 이용한 Redis 연결 설정

알쓸개잡 2024. 3. 24.

고성능 인메모리 데이터베이스 스토어인 Redis는 탁월한 데이터 캐싱 및 검색 속도를 제공하며 많은 프로덕션 환경에서 활용되고 있다.

이에 발맞추어 Spring Data 프로젝트는 Redis 상호 작용에 대한 높은 수준의 추상화를 제공하여 개발자에게 최소한의 노력으로 Redis의 성능 이점을 활용하면서 기능이 풍부한 애플리케이션을 구축하는데 집중할 수 있도록 한다.

이번 포스팅에서는 Spring에서 제공하는 Redis 관련 Auto Configuration에 대해서 다뤄보고자 한다. (Lettuce 위주로 살펴봄)

 

 

Dependency

우선 Spring Data Redis를 사용하기 위해서는 다음과 같은 dependency가 필요하다.

build.gradle

plugins {
    id 'java'
    id 'org.springframework.boot' version '3.2.3'
    id 'io.spring.dependency-management' version '1.1.4'
}
...
dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-redis'
    implementation 'org.apache.commons:commons-pool2:2.12.0'
    developmentOnly 'org.springframework.boot:spring-boot-docker-compose'
    testAndDevelopmentOnly 'org.springframework.boot:spring-boot-docker-compose'
    ...
}
  • spring boot 에서 지원하는 docker compose 기능을 사용하기 위해서 추가로 spring-boot-docker-compose 아티팩트를 dependency로 추가하였다.
  • 테스트 코드에서도 spring boot docker compose를 사용하고자 한다면
    • testAndDevelopmentOnly 'org.springframework.boot:spring-boot-docker-compose' 디펜던시를 추가한다.
  • 단일 서버 환경의 Redis인 경우 connection pool을 사용하기 위해서는 commons-pool2 디펜던시를 추가해야 한다.
    • 단일 서버 환경의 경우 애플리케이션이 commons-pool2 라이브러리를 사용할 수 있다면 pool은 자동 활성화 된다.

 

Spring Redis Auto Configuration

Spring Boot에서 Redis를 위한 auto configuration의 시작은 org.springframework.boot:spring-boot-autoconfigure 아티팩트의 org.springframework.boot.autoconfigure.data.redis 패키지에 있는 RedisAutoConfiguration 클래스다.

RedisAutoConfiguration 클래스에 지정된 어노테이션을 살펴보면 다음과 같다.

@AutoConfiguration
@ConditionalOnClass({RedisOperations.class})
@EnableConfigurationProperties({RedisProperties.class})
@Import({LettuceConnectionConfiguration.class, JedisConnectionConfiguration.class})
  • application.yml(. properties)의 Redis 관련 설정은 RedisProperties 클래스에 맵핑된다.
  • LettuceConnectoinConfiguration, JedisConnectionConfiguration 이 우선 동작한다.

 

Spring Data Redis Properties

RedisProperties 클래스는 spring data redis 설정 항목에 대한 맵핑 클래스다.

RedisProperties 클래스의 어노테이션은 다음과 같이 지정되어 있다.

@ConfigurationProperties(
    prefix = "spring.data.redis"
)

속성 이름에 spring.data.redis prefix가 정의됨을 알 수 있다.

서버 연결을 위한 기본 설정 항목은 다음과 같다.

프로퍼티 설명 디폴트
spring.data.redis.client-name 클라이언트 설정 이름으로 연결할 때 설정할 클라이언트 이름  
spring.data.redis.client-type 연결에 사용할 클라이언트 유형. (lettuce or jedis) 기본적으로 클래스 경로에 따라 자동 감지됨.  
spring.data.redis.connect-timeout Connection 타임아웃.  
spring.data.redis.timeout Read 타임아웃.  
spring.data.redis.database connection factory 에서 사용하는 데이터베이스 인덱스 설정. Redis는 인덱스로 식별되는 여러 데이터베이스를 제공하며, 이 속성을 사용하여 사용할 데이터베이스를 선택할 수 있음. 0
spring.data.redis.host Redis 서버 호스트 localhost
spring.data.redis.port Redis 서버 포트 6379
spring.data.redis.username Redis 서버 로그인 username  
spring.data.redis.password Redis 서버 로그인 password  
spring.data.redis.url Connection URL 설정. 이 설정이 지정되면 host, port, username, password 설정이 재정의 됨.
ex) redis://username:password@localhost:6379
 
spring.data.redis.ssl.enabled SSL 지원 활성화 여부 설정. 별도로 지정하지 않는 경우 bundle을 제공하면 자동으로 활성화 됨.  
spring.data.redis.ssl.bundle SSL bundle 이름  
spring.data.redis.cluster.max-redirects 클러스터 전체에서 명령을 실행할 때 따라야 할 최대 리다이렉션 수.  
spring.data.redis.cluster.nodes 연결할 “host:port” 쌍의 쉼표(,)로 구분된 목록.
클러스터 노드의 초기 목록을 나타내며 적어도 하나의 항목이 있어야 함.
 
spring.data.redis.sentinel.master Redis 서버의 이름.  
spring.data.redis.sentinel.nodes 연결할 “host:port” 쌍의 쉼표(,)로 구분된 목록.  
spring.data.redis.sentinel.password redis sentinel 인증을 위한 비밀번호  
spring.data.redis.sentinel.username redis sentinel 인증을 위한 계정  
spring.data.redis.repositories.enabled Redis Repository를 활성화 할지 여부. true

 

Connection Pool 관련 설정은 다음과 같다.

프로퍼티 설명 디폴트
spring.data.redis.{jedis|lettuce}.pool.enabled connection pool 활성화 여부 설정. commons-pool2 를 사용할 수 있는 경우 자동으로 활성화 됨.
Jedis를 사용하는 경우 센티널 모드에서 풀링이 암시적으로 활성화되며 이 설정은 단일 노드에서만 적용됨.
connection pool을 활성화 하기 위해서는 commons-pool2 디펜던시를 추가해야 한다.
(디펜던시가 없으면 ConnectionFactory 생성시 실패함)
 
spring.data.redis.{jedis|lettuce}.pool.max-active 연결 풀의 최대 connection 수 설정. 제한이 없는 경우 음수 값을 사용 8
spring.data.redis.{jedis|lettuce}.pool.max-idle 연결 풀의 최대 idle connection 수 설정. 제한이 없는 경우 음수 값을 사용 8
spring.data.redis.{jedis|lettuce}.pool.min-idle 연결 풀이 항상 유지해야 하는 최소 idle connection 수 설정.
이 설정은 time-between-eviction-runs 설정 시간이 양수인 경우에 적용됨.
0
spring.data.redis.{jedis|lettuce}.pool.max-wait 연결 풀이 모두 사용 중인 경우 연결이 가능해질 때까지 기다리는 시간 설정.
지정된 시간 동안에도 연결 가능한 풀이 없는 경우 예외 발생.
음수 값을 설정한 경우 연결 가능한 풀이 생기기까지 계속 대기함.
-1ms
spring.data.redis.{jedis|lettuce}.pool.time-between-eviction-runs 제거 스레드의 실행 시간 주기 설정. 음수 값이 지정된 경우 제거 스레드를 실행되지 않음.
제거 스레드를 통해서 min-idle, max-idle 에 설정된 수치를 넘어선 경우 불필요한 idle connection을 제거하여 리소스를 효율적으로 관리함.
 

* {jedis | lettuce} 표시된 부분은 jedis 혹은 lettuce 가 지정됨을 의미한다.

spring data properties에 대한 전반적인 내용은 

https://docs.spring.io/spring-boot/docs/current/reference/html/application-properties.html#appendix.application-properties.data 링크를 참고하면 도움이 될 것이다.

 

기본 설정 예시

spring:
  data:
    redis:
      # redis 접속 호스트 (default: localhost)
      # redis 접속 호스트 (default: localhost)
      # - redis를 spring boot docker compose로 생성한 경우 spring에서 생성한 docker 컨테이너로부터 정보를 얻어 접속 정보를 자동 셋팅한다.
      # - redis를 spring boot docker compose로 생성하지 않은 경우 redis 접속 호스트 정보를 셋팅한다.
      host: localhost
      # redis 접속 포트 (default: 6379)
      # - redis를 spring boot docker compose로 생성한 경우 spring에서 생성한 docker 컨테이너로부터 정보를 얻어 접속 정보를 자동 셋팅한다.
      # - redis를 spring boot docker compose로 생성하지 않은 경우 redis 접속 포트 정보를 셋팅한다.
      port: 6379
      # connection timeout
      connect-timeout: 30s
      # read timeout
      timeout: 10s

 

 

ConnectionDetails 추상화

Spring Boot 3.1부터 도입된 ConnectionDetails 추상화는 여러 원격 서비스에 대한 연결 설정을 자동 구성을 통해 모델링할 수 있게 되었다.

Redis 서비스의 경우 ConnectionDetails 인터페이스를 확장한 RedisConnectionDetails 인터페이스를 사용하며 이에 대한 구현체는 다음과 같다.

  • RedisDockerComposeConnectionDetails
    • Spring Boot Docker Compose에 의해서 구성된 redis 서비스에 대해서 사용된다.
    • Spring Boot Docker Compose는 개발 및 테스트용이라서 그런지 standalone만 지원한다.
    • Spring Boot Docker Compose로 자동 생성된 redis 서버 docker container에 대한 호스트 포트를 자동으로 감지한다.
  • PropertiesRedisConnectionDetails
    • application.yml (. properties)의 spring.data.redis 설정에 의해서 연결 정보에 대한 ConnectionDetails 추상화가 구현된다.
    • RedisDockerComposeConnectionDetails와 달리 standalone, sentinel, cluster 구성 설정을 모두 처리한다.

ConnectionDetails를 통해서 standalone, sentinel, cluster 연결 설정에 대한 클래스가 구성이 되는데 각각 다음과 같다.

  • standalone - RedisStandaloneConfiguration - 단일 서버 환경에 대한 접속 정보 설정
  • sentinel - RedisSentinelConfiguration - Sentinel 구성 환경에 대한 접속 정보 설정
  • cluster - RedisClusterConfiguration - Cluster 구성 환경에 대한 접속 정보 설정

위 클래스 모두 ConnectionDetails 구현체 클래스를 통해서 알맞게 설정된다.

Spring Boot Docker Compose를 통해서 Redis 서비스를 구성한 경우 Auto Configuration 과정에서 다음에 설명할 LettuceConnectionConfiguration 클래스에 주입되는 ConnectionDetails 클래스에 대한 정보를 디버거를 통해서 보면 다음과 같다.

디버깅을 통해서 살펴본 ConnectionDetails 인스턴스
RedisDockerComposeConnectionDetails 인스턴스가 주입됨

참고로 앞서 언급했듯이 RedisDockerComposeConnectionDetails 에는 도커 컨테이너의 Redis 서버 포트와 연결된 연결된 호스트 포트를 자동으로 감지함을 알 수 있다.

ConnectionDetails에 할당된 포트 정보
Docker Container와 연결된 호스트 포트

 

 

LettuceConnectionConfiguration, JedisConnectionConfiguration 클래스

RedisAutoConfiguration 클래스에서 Import 하고 있는 두 RedisConnectionConfiguration 클래스다.

각각의 클래스에 지정된 어노테이션을 살펴보자.

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnClass({RedisClient.class})
@ConditionalOnProperty(
    name = {"spring.data.redis.client-type"},
    havingValue = "lettuce",
    matchIfMissing = true
)
class LettuceConnectionConfiguration extends RedisConnectionConfiguration {...}

LettuceConnectionConfiguration 클래스는 Lettuce 라이브러리를 통해서 연결을 위한 자동 구성 설정을 한다.

spring.data.redis.client-type 설정이 'lettuce'로 설정되거나 설정되지 않은 경우(matchIfMissing = true) 자동 구성이 동작하며 해당 클래스에서 LettuceConnectionFactory 빈을 생성(현재 RedisConnectionFactory 빈이 생성되지 않은 경우) 한다.

 

JedisConnectionConfiguration 클래스에 지정된 어노테이션은 다음과 같다.

@Configuration(
    proxyBeanMethods = false
)
@ConditionalOnClass({GenericObjectPool.class, JedisConnection.class, Jedis.class})
@ConditionalOnMissingBean({RedisConnectionFactory.class})
@ConditionalOnProperty(
    name = {"spring.data.redis.client-type"},
    havingValue = "jedis",
    matchIfMissing = true
)
class JedisConnectionConfiguration extends RedisConnectionConfiguration {...}

LettuceConnectionConfiguration에 지정된 어노테이션과 유사하지만

@ConditionalOnClass({GenericObjectPool.class, JedisConnection.class, Jedis.class})

@ConditionalOnMissingBean({RedisConnectionFactory.class})

차이가 있다.

Jedis를 사용하려면 추가적으로 Jedis 디펜던시가 추가되어야 한다.

implementation 'redis.clients:jedis:<version>'

또한 LettuceConnectionConfiguration에서 LettuceConnectionFactory(RedisConnectionFactory)를 생성하였다면 JedisConnectionConfiguration 클래스의 자동 구성은 수행되지 않는다.

즉 명시적으로 spring.data.redis.client-type=jedis로 설정된 경우에만 JedisConnectionConfiguration 자동 구성이 수행된다.

 

ClientResources Custom (Lettuce 만 해당)

ClientResource는 Lettuce에서 Redis 클라이언트 연결에 필요한 이벤트 루프 그룹, 스레드 풀 및 기타 인프라 구성 요소와 같은 공유 리소스를 관리하는 Lettuce의 인터페이스다.

이러한 리소스는 생성하는데 비용이 많이 들기 때문에 RedisConnectionFactory 빈을 생성할 때 자동으로 DefaultClientResources 인스턴스를 생성하여 LettuceConnectionFactory에 등록하여 사용한다.

LettuceConnectionConfiguration 클래스는 다음과 같이 ClientResources 인스턴스를 생성한다.

@Bean(
    destroyMethod = "shutdown"
)
@ConditionalOnMissingBean({ClientResources.class})
DefaultClientResources lettuceClientResources(ObjectProvider<ClientResourcesBuilderCustomizer> customizers) {
    DefaultClientResources.Builder builder = DefaultClientResources.builder();
    customizers.orderedStream().forEach((customizer) -> {
        customizer.customize(builder);
    });
    return builder.build();
}
  • ClientResources 인스턴스를 직접 생성하지 않았다면 Auto Configuration에 의해서 자동으로 인스턴스가 생성되는데 이때 ClientResourcesBuilderCustomizer를 빈으로 등록하여 변경하고자 하는 Resource 항목만을 변경할 수 있다.
  • ClientResources의 일부 항목을 변경하도록 ClientResourcesBuilderCustomizer 빈을 생성하는 샘플 코드는 다음과 같다.
@Configuration
public class RedisConfiguration {

    @Bean
    public ClientResourcesBuilderCustomizer clientResourceCustomize() {
        return builder -> {
            builder.reconnectDelay( Delay.constant( Duration.ofSeconds(50) ) );
            builder.ioThreadPoolSize( 4 );
            builder.commandLatencyRecorder( CommandLatencyRecorder.disabled() );
            builder.commandLatencyPublisherOptions( DefaultEventPublisherOptions.disabled() );
        };
    }
}

위 clientResourceCustomize 코드는 다음과 같은 설정을 변경한다.

  • ClientResources에서 reconnect delay 정책을 ConstantDelay 인스턴스로 설정한다. (디폴트는 ExponentialDelay 다)
    • 재접속을 위해서 대기하는 시간을 고정 시간으로 변경한다.
    • ExponentialDelay는 재접속 시도 횟수가 증가할수록 Delay 시간이 그에 비례하여 증가하는 방식이다.
  • io thread pool size를 4개로 고정한다. (디폴트는 processors 수다)
  • 지연 시간에 대한 메트릭 수집을 disable 한다.
  • 지연 시간에 대해서 수집된 메트릭 이벤트 발행을 disable 한다.

ClinetResources 클래스에 정의된 각 멤버 변수들에 대한 설명은 다음과 같다.

항목 설명 디폴트
ioThreadPoolSize I/O 스레드 풀의 스레드 개수를 설정한다. 기본값은 런타임시 사용가능한 processor 수를 기본값으로 한다.
모든 스레드는 모든 I/O 작업이 실행되는 내부 이벤트 루프를 나타낸다. 클라이언트는 네트워크(NIO) 및 유닉스 도메인 소켓(EPoll) 연결을 위해 서로 다른 스레드 풀을 필요로 하기 때문에 이 숫자는 실제 I/O 스레드 수를 반영하지 않는다. 최소 I/O 스레드는 2개다.
processors 수
computationThreadPoolSize compute 스레드 풀의 스레드 개수를 설정한다. 기본값은 런타임시 사용가는한 processor 수를 기본값으로 한다. 모든 스레드는 모든 계산 작업이 실행되는 내부 이벤트 루프를 나타낸다. 최소 compute 스레드는 2개다. processors 수
eventLoopGroupProvider 기존 네티 인프라를 재사용하거나 스레드 풀에 대한 전체 제어를 원하는 경우 EventLoopGroupProvider API가 이를 위한 방법을 제공한다. EventLoopGroup은 EventLoopGroupProvider에 의해서 얻어지고 관리된다. 제공된 EventLoopGroupProvider는 클라이언트에 의해 관리되지 않으며 리소스가 더 이상 필요하지 않으면 종료해야 한다. DefaultEventLoopGroupProvider
(ioThreadPoolSize 와 관련이 있다.)
eventExecutorGroup 기존 네티 인프라를 재사용하거나 스레드 풀에 대한 전체 제어를 원하는 경우 Client Resource에 기존 EventExecutorGroup을 제공할 수 있다. 제공된 EventExecutorGroup은 클라이언트에서 관리하지 않으며 리소스가 더 이상 필요하지 않으면 종료해야 한다. DefaultEventExecutorGroup
(computationThreadPoolSize 와 관련이 있다.)
eventBus 이벤트 버스 시스템은 클라이언트에서 subscriber에게 이벤트를 전송하는데 사용된다. 이벤트는 연결 상태 변경, 메트릭 등에 관한 것이다. 이벤트는 Rxjava 주체를 사용하여 게시되며 기본 구현은 백프레셔에서 이벤트를 삭제한다. DefaultEventBus
commandLatencyCollectorOptions 클라이언트는 명령을 dispatch 하는 동안 지연 시간 메트릭을 수집할 수 있다. 이 옵션을 사용하면 백분위수, 메트릭 수준(연결 또는 서버별), 메트릭을 누적할지 또는 수집 후 초기화 할지 여부를 구성할 수 있다. commandLatencyCollectorOptions 설정은 기본적으로 enabled 되어 있으며 commandLatencyCollectorOptions(…)를 DefaultCommandLatencyCollectorOptions.disabled()로 설정하여 비활성화 할 수 있다. - 6.0 버전 부터 메서드는 deprecated 되었다.
spring 지연 시간 수집기를 사용하려면 클래스 경로에 LatencyUtils가 있어야 한다.
DefaultCommandLatencyCollectorOptions

commandLatencyCollector 클라이언트는 명령을 dispatch 하는 동안 지연 시간 메트릭을 수집할 수 있다. 명령 대기 시간 메트릭은 연결 또는 서버 수준에서 수집된다. 명령 지연 시간 수집은 기본적으로 활성화 되어 있으며 commandLatencyCollectorOptions(…)를 DefaultCommandLatencyCollectorOptions.disabled()로 설정하여 비활성화 할 수 있다. - 6.0 버전 부터 메서드는 deprecated 되었다. DefaultCommandLatencyCollector
spring 6.0 부터 deprecated 됨.
commandLatencyRecorder commandLatencyCollector를 대체하여 나온 필드로 보인다. CommandLatencyRecorder는 인터페이스며 구현체는 CommandLatencyCollector 이다.
6.0버전 이후 부터 사용되는 지연 시간 메트릭을 수집하기 위한 인스턴스로 보인다. 내부적으로 CommandLatencyCollector 타입의 인스턴스를 가지며 CommandLatencyCollectorOptions가 null 인 경우에는 DefaultCommandLatencyCollectorOptions를 사용한다.
기본적으로 활성화 되어 있으며 commandLatencyRecorder(CommandLatencyRecorder.disabled()) 로 비활성화 하는듯 하다.
DefaultCommandLatencyCollector
commandLatencyPublisherOptions eventBus를 사용하여 명령 대기 시간 메트릭을 게시할 수 있다. 지연 시간 이벤트는 기본적으로 10분마다 발행된다. 이벤트 게시를 비활성화 하려면 commandLatencyPublisherOptions(…) 를 DefaultEventPublisherOptions.disabled() 로 설정하여 비활성화 할 수 있다. DefaultEventPublisherOptions
dnsResolver 3.5, 4.2 버전
호스트 이름을 java.net.InetAddress로 확인하도록 DNS resolver를 구성한다. 기본값은 호스트 이름 확인 및 조회 결과 캐싱을 사용하는 JVM DNS resolver를 사용한다. DNS 기반 Redis-HA 설정(ex. AWS ElasticCache)를 사용하는 사용자는 다른 DNS resolver를 구성할 수 있다. Lettuce에는 java의 DnsContextFactory를 사용하여 호스트 이름을 확인하는 DirContextDnsResolver가 함께 제공된다. DirContextDnsResolver를 사용하면 결과를 캐싱하지 않고 시스템 DNS 또는 사용자 정의 DNS 서버를 사용할 수 있으므로 각 호스트 이름 조회에서 DNS 조회 결과를 얻을 수 있다.
4.4 버전
기본값은 DnsResolvers.UNRESOLVED로 설정되어 Bootstrap.connect()에서 DNS 이름을 확인하는 netty의 AddressResolver를 사용한다.
DnsResolvers.UNRESOLVED
reconnectDelay 재연결 시도를 지연시키는데 사용되는 재연결 지연을 구성한다. 기본값은 상한이 30초인 exponential delay다. Delay.exponential()

 

 

Configuration 클래스에서 ConnectionFactory의 Client Configuration 변경하기

ClientResources의 항목을 Configuration 클래스에서 ClientResourcesBuilderCustomizer를 이용하여 변경할 수 있는 것처럼 LettuceClientConfigurationBuilderCustomizer를 이용하여 ConnectionFactory의 Client 설정 정보를 변경할 수 있다.

Lettuce를 기반으로 설정을 변경하는 샘플 코드는 다음과 같다.

@Bean
public LettuceClientConfigurationBuilderCustomizer lettuceClientConfigCustomize() {
    return builder -> {
        if ( builder instanceof
                LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder poolingBuilder ) {
            // Pool configuration
            GenericObjectPoolConfig<?> poolConfig = new GenericObjectPoolConfig<>();
            poolConfig.setMaxIdle(10);
            poolConfig.setMinIdle(5);
            poolConfig.setMaxTotal(20);
            poolConfig.setMaxWait(Duration.ofSeconds( 30 ));
            poolingBuilder.poolConfig( poolConfig );
        }

        builder.shutdownTimeout( Duration.ofMillis( 1000L ) );
    };
}

위 코드는

  • pool 설정이 있는 경우에는 LettucePoolingClientConfigurationBuilder 인스턴스가 생성되는데 max-idle, min-idle, max-total, max-wait 설정을 각각 10개, 5개, 20개, 30초로 다시 재설정을 한다.
  • lettuce shutdown timeout 설정을 1000ms로 설정한다. (default: 100ms)
    • spring.data.redis.lettuce.shutdown-timeout 설정이 지정되어 있다면 override 한다.

lettuceClientConfigCustomize 빈이 어떻게 호출되는지 LettuceConnectionConfiguration 클래스를 통해 살펴보자.

LettuceConnectionFactory 빈을 생성하는 코드는 다음과 같다.

@Bean
@ConditionalOnMissingBean(RedisConnectionFactory.class)
@ConditionalOnThreading(Threading.PLATFORM)
LettuceConnectionFactory redisConnectionFactory(
		ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
		ClientResources clientResources) {
	return createConnectionFactory(builderCustomizers, clientResources);
}

위 코드에서 ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers 파라미터에 직접 작성한 lettuceClientConfigCustomize 빈이 주입되게 된다.

createConnectionFactory(builderCustomizers, clientResources) 메서드 구현은 다음과 같다.

private LettuceConnectionFactory createConnectionFactory(
		ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
		ClientResources clientResources) {
	LettuceClientConfiguration clientConfig = getLettuceClientConfiguration(builderCustomizers, clientResources,
			getProperties().getLettuce().getPool());
	return createLettuceConnectionFactory(clientConfig);
}

getLettuceClientConfiguration(builderCustomizers, clientResources, getProperties().getLettuce().getPool()); 메서드가 Lettuce Client 설정을 생성하는 부분이다. 따라가 보자.

private LettuceClientConfiguration getLettuceClientConfiguration(
		ObjectProvider<LettuceClientConfigurationBuilderCustomizer> builderCustomizers,
		ClientResources clientResources, Pool pool) {
	LettuceClientConfigurationBuilder builder = createBuilder(pool);
	applyProperties(builder);
	if (StringUtils.hasText(getProperties().getUrl())) {
		customizeConfigurationFromUrl(builder);
	}
	builder.clientOptions(createClientOptions());
	builder.clientResources(clientResources);
	builderCustomizers.orderedStream().forEach((customizer) -> customizer.customize(builder));
	return builder.build();
}

직접 작성한 lettuceClientConfigCustomize 빈이 여기까지 흘러 들어왔다.

각 메서드 호출의 동작을 살펴보면

  • createBuilder(pool) : pool 설정이 enabled인 경우 LettucePoolingClientConfigurationBuilder를 아닌 경우 LettuceClientConfigurationBuilder를 리턴한다.
  • applyProperties(builder) : builder에 application 프로퍼티 설정내용을 적용한다. (spring.data.redis.~~~)
    • ssl enabled 여부 설정 적용 (spring.data.redis.ssl.enabled)
    • timeout 설정 적용 (spring.data.redis.timeout)
    • lettuce의 shutdown timeout 설정 적용 (spring.data.redis.lettuce.shutdown-timeout)
    • client name 설정 적용 (spring.data.redis.client-name)
  • customizeConfigurationFromUrl(builder) : spring.data.redis.url 설정이 적용된 경우 url 내용을 파싱 하여 scheme가 rediss인 경우 use ssl을 true로 설정한다.
  • builder.clientOptions(createClientOptions()) : ClientOptions 인스턴스를 생성하여 적용한다. ClientOptions에 적용되는 설정은 다음과 같다.
    • application.yml (.properties) 설정에 cluster refresh 관련 설정을 적용한다. (spring.data.redis.lettuce.cluster.refresh)
    • connection timeout 설정을 적용한다. (spring.data.redis.connect-timeout)
    • SSL bundle이 지정된 경우 bundle에 지정된 SSL 설정을 적용한다. (spring.data.redis.ssl.bundle)
  • builder.clientResources : ClientResources 인스턴스를 적용한다.
  • builderCustomizers.orderedStream().forEach( ( customizer ) → customizer.customize( builder ) )
    • 직접 작성한 lettuceClientConfigCustomize 인스턴스가 적용된다.
    • customizer.customize(builder) 호출은 결국 아래 코드와 같게 된다.
void customize(LettuceClientConfigurationBuilder builder) {
	if ( builder instanceof
        LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder poolingBuilder ) {
    // Pool configuration
	    GenericObjectPoolConfig<?> poolConfig = new GenericObjectPoolConfig<>();
	    poolConfig.setMaxIdle(10);
	    poolConfig.setMinIdle(5);
	    poolConfig.setMaxTotal(20);
	    poolConfig.setMaxWait(Duration.ofSeconds( 30 ));
	    poolingBuilder.poolConfig( poolConfig );
	}
	
	builder.shutdownTimeout( Duration.ofMillis( 1000L ) );
}

 

ClientOptions (Lettuce만 해당)

LettuceClientConfiguration를 생성시에 ClientOptions 인스턴스를 생성하여 LettuceClientConfiguration 인스턴스에 적용한다.

ClientOptions 클래스는 Spring 애플리케이션 내에서 사용되는 Redis 클라이언트의 동작을 구성하는 핵심 부분이다.

클라이언트가 Redis 서버와 상호 작용하는 방식에서 중요한 역할을 하며 연결 처리, 명령 실행, 보안 및 성능 최적화에 영향을 미친다.

다음은 ClientOptions 클래스의 각 필드에 대한 설명이다.

항목 설명 디폴트
autoReconnect 클라이언트가 연결이 끊긴 후 자동으로 다시 연결할지 여부를 설정한다. 활성화 된 경우 네트워크 문제로 인한 연결 중단 후 자동 재연결 시도가 이루어진다. true
cancelCommandsOnReconnectFailure 재연결 시도가 실패했을 때 보류 중인 모든 명령을 취소할지 여부를 설정한다. 이는 재연결 과정에서 명령이 무한히 대기하는 상황을 방지한다. false
decodeBufferPolicy 응답 버퍼를 어떻게 디코딩할지 결정하는 정책이다. 대량의 데이터를 처리할 때 메모리 사용량과 성능에 영향을 줄 수 있다. ratio(3)
disconnectBehavior 연결이 끊어졌을 때 클라이언트 동작을 정의한다. 명령이 실패하거나 일정 시간 동안 재시도하게 하는 등의 행동을 지정할 수 있다. DEFAULT
pingBeforeActivateConnection 새 연결이 활성화되기 전에 PING 명령을 보내 연결 상태를 확인할지 여부를 결정한다. 이를 통해 서버가 응답하지 않는 연결을 미리 확인할 수 있다. true
protocolVersion 사용할 Redis 프로토콜 버전을 지정한다.  
publishOnScheduler 명령을 비동기적으로 처리할 때 사용할 스케줄러를 정의한다. 이는 명령 처리의 성능과 효율성에 영향을 줄 수 있다. 이 옵션을 활성화하면 단일 혹은 소수의 Redis 연결로 상당한 양의 처리가 필요한 리액티브 시퀀스에서 단일 스레드와 같은 동작으로 인해 성능이 저하되는 경우에 유용할 수 있다. 이 옵션을 활성화하면 데이터/완료 신호에 대해 ClientResources를 통해 구성된 EventExecutorGroup을 사용한다.  false
readOnlyCommands 읽기 전용 명령을 허용할지 여부를 설정한다. 이는 주로 클러스터 환경에서 슬레이브 노드에 읽기 명령을 보낼 수 있게 하기 위해서 사용된다.  
requestQueueSize 클라이언트가 보내지 않은 명령을 대기시킬 수 있는 큐의 크기를 설정한다. 이 크기를 조절하여 네트워크 지연이나 서버 처리 능력을 고려한 유연한 처리가 가능하다. Integer.MAX_VALUE
scriptCharset 스크립트 명령을 인코딩/디코딩할 때 사용할 문자 인코딩을 지정한다. UTF-8
socketOptions 소켓 연결에 적용할 옵션을 설정한다. 타임아웃, 버퍼 크기, KeepAlive등의 옵션을 포함할 수 있다. AutoConfiguration에서 설정해준다.
sslOptions SSL/TLS를 통한 보안 연결 설정을 정의한다. 키 저장소, 트러스트 저장소, 사용할 암호화 알고리즘 등을 포함한다. AutoConfiguration에서 설정해준다.
suspendReconnectOnProtocolFailure 프로토콜 오류로 인해 연결이 실패했을 때 재연결을 일시 중단할지 여부를 설정한다. false
timeoutOptions 명령 실행 및 전체 연결 관리를 위해 timeout을 처리하는 방법을 설정한다.   
decodeBufferPolicy 
1. ratio 정책
   - 집계 버퍼의 용량과 사용량을 고려한 비율 기반의 정책이다. 사용 비율을 고려하여 CPU 사용량과 메모리 사용량을 최적화 한다. 값이 높을 수록 메모리 사용량이 늘어난다. parameter로 전달되는 bufferUsageRatio는 버퍼 사용 비율이다.
값이 1이면 50%, 값이 2이면 66%, 값이 3이면 75% 와 같이 계산된다. 0에서 2^31 - 1 사이의 값이어야 하며 일반적으로 50%에서 90%를 나타내는 1에서 10사이의 값을 설정한다.

2. always 정책
    - 각 디코딩 단계 후에 읽은 바이트를 삭제하는 정책이다. 메모리 효율성이 가장 높은 전략이지만 CPU 부담도 함께 커진다.

3. alwaysSome 정책
    - 각 디코딩 단계 후에 일부 읽기 바이트를 삭제하는 정책이다. 내부 구현에 따라 읽기 바이트의 일부 또는 전부를 삭제하거나 전혀 삭제하지 않고 잠재적으로 추가 메모리 소비를 감수하면서 전체 메모리 대역폭 소비를 줄일 수 있다.
disconnectBehavior
1. DEFAULT
    - autoReconnect 옵션이 true인 경우 명령을 수락하고, autoReconnect 옵션이 false인 경우 명령을 거부한다.

2. ACCEPT_COMMANDS
     - 연결이 끊긴 상태에서도 명령을 수락한다.

3. REJECT_COMMANDS
     - 연결이 끊기 상태에서는 명령을 거부한다. 

 

RedisConnectionFactory 생성 (LettuceConnectionFactory)

지금까지 위에서 살펴본 ClientResources, LettuceClientConfiguration, ConnectionDetails 인스턴스를 통해서 RedisConnectionFactory가 생성된다.

최종적으로 LettuceConnectionFactory에는 LettuceClientConfiguration과 RedisConfiguration 인스턴스가 적용이 된다.

RedisConfiguration 인스턴스는 application.yml (.properties) 설정의 다음 항목에 따라서 인스턴스가 결정된다.

프로퍼티 RedisConfiguration 인스턴스 설명
spring.data.redis.sentinel RedisSentinelConfiguration Sentinel 구성 환경에 대한 접속 설정 정보
spring.data.redis.cluster RedisClusterConfiguration Cluster 구성 환경에 대한 접속 설정 정보
spring.data.redis.host
spring.data.redis.port
RedisStandaloneConfiguration 단일 구성에 대한 접속 설정 정보
spring.data.redis.sentinel, spring.data.redis.cluster가 모두 설정된 경우에는 spring.data.redis.sentinel 설정이 적용된다.

정리하자면 RedisConnectionFactory(LettuceConnectionFactory) 인스턴스가 생성되는 과정은 다음과 같다.

RedisConnectionFactory 생성 과정

 

 

RedisTemplate Serializers

Spring Data Redis에서 사용자 정의 타입과 raw 데이터 간의 변환은 org.springframework.data.redis.serializer 패키지에서 처리한다. 이 패키지에는 직렬화 프로세스를 처리하는 두 가지 유형의 직렬화기가 포함되어 있다.

  • RedisSerializer를 기반으로 한 양방향 직렬화기
  • Element를 읽고 쓰기 위해서 사용하는 RedisElementReader 및 RedisElementWriter

두 직렬화기의 차이점은 RedisSerializer는 byte[]로 직렬화하는 반면, Reader와 Writer는 ByteBuffer를 사용한다.

위 두 가지 유형 이외에도 여러 직렬화기를 사용할 수 있다.

  • RedisCache와 RedisTemplate에 기본적으로 사용되는 JdkSerializationRedisSerializer
  • StringRedisSerializer
  • Spring OXM 지원을 통해 객체/XML 매핑을 위한 OxmSerializer
  • JSON 형식으로 데이터를 저장하기 위해 Jacson2JsonRedisSerializer 또는 GenericJackson2JsonRedisSerializer
저장 형식은 값으로만 제한되지 않고 키, 값 또는 해시에 제한 없이 사용할 수 있다.
기본적으로 RedisCache 및 RedisTemplate은 Java 네이티브 직렬화를 사용하도록 구성된다. Java 네이티브 직렬화는 확인되지 않은 바이트코드를 삽입하는 취약한 라이브러리 및 클래스를 악용하는 페이로드로 인해 원격 코드가 실행될 수 있는 것으로 알려져 있다. 조작된 입력으로 인해 역직렬화 단계에서 애플리케이션에 원하지 않는 코드가 실행될 수 있다. 따라서 신뢰할 수 없는 환경에서는 직렬화를 사용하지 않는 것이 좋다. 일반적으로 다른 메시지 형식(JSON과 같은)을 대신 사용할 것을 강력히 권장한다고 Spring 문서에서는 말한다.

 

다음 샘플 코드는 Redis의 키와 값에 대한 직렬화기를 설정하는 방법을 보여준다.

// lettuceConnectionFactory 인스턴스는 spring data redis auto configuration에 의해서 자동 생성됨
@Bean
public RedisTemplate<String, Object> redisTemplate( LettuceConnectionFactory lettuceConnectionFactory ) {
    RedisTemplate<String, Object> template = new RedisTemplate<>();
    template.setConnectionFactory( lettuceConnectionFactory );

    // set key serializer
    // template.setKeySerializer( new StringRedisSerializer(StandardCharsets.UTF_8) ); 와 동일함
    template.setKeySerializer( RedisSerializer.string() );
    template.setHashKeySerializer( RedisSerializer.string() );

    // set value serializer
    // template.setDefaultSerializer( new GenericJackson2JsonRedisSerializer() ); 와 동일함
    template.setDefaultSerializer( RedisSerializer.json() );
    template.setValueSerializer( RedisSerializer.json() );
    template.setHashValueSerializer( RedisSerializer.json() );

    return template;
}

Serializer를 변경하려면 Auto Configuration으로 생성되는 RedisTemplate 대신에 직접 빈을 정의해야 한다.

위 코드는

  • 키와 해시키에 대한 Serializer로 StringRedisSerializer를 사용하도록 한다.
  • 값과 해시값에 대한 Serializer로 GenericJackson2JsonRedisSerializer를 사용하도록 한다.

 

RedisTemplate 사용 테스트 코드

지금까지 Spring Redis Auto Configuration 및 RedisTemplate에 Serializer를 적용한 설정을 토대로 간단한 테스트 코드를 작성해 보았다.

UUID 형식의 키에 Person 객체를 저장하는 코드다.

package com.example.spring.redis.model;

import lombok.Builder;
import org.springframework.data.annotation.Id;

import java.util.UUID;

@Builder
public record Person(@Id UUID id,
                     String personName,
                     Integer personAge,
                     String personNation,
                     String comment) {
    public Person( UUID id, String personName, Integer personAge, String personNation, String comment ) {
        this.id = (id == null) ? UUID.randomUUID() : id;
        this.personName = personName;
        this.personAge = personAge;
        this.personNation = personNation;
        this.comment = comment;
    }
}

 

Redis 연결을 위한 Configuration

@Configuration
@RequiredArgsConstructor
public class RedisConfiguration {

    @Bean
    public ClientResourcesBuilderCustomizer clientResourceCustomize() {
        return builder -> {
            builder.reconnectDelay( Delay.constant( Duration.ofSeconds(50) ) );
            builder.ioThreadPoolSize( 4 );
            builder.commandLatencyRecorder( CommandLatencyRecorder.disabled() );
            builder.commandLatencyPublisherOptions( DefaultEventPublisherOptions.disabled() );
        };
    }

    @Bean
    public LettuceClientConfigurationBuilderCustomizer lettucyClientConfigCustomize() {
        return builder -> {
            if ( builder instanceof
                    LettucePoolingClientConfiguration.LettucePoolingClientConfigurationBuilder poolingBuilder ) {
                // Pool configuration
                GenericObjectPoolConfig<?> poolConfig = new GenericObjectPoolConfig<>();
                poolConfig.setMaxIdle(10);
                poolConfig.setMinIdle(5);
                poolConfig.setMaxTotal(20);
                poolConfig.setMaxWait(Duration.ofSeconds( 30 ));
                poolingBuilder.poolConfig( poolConfig );
            }

            builder.shutdownTimeout( Duration.ofMillis( 1000L ) );

        };
    }

    // lettuceConnectionFactory 인스턴스는 spring data redis auto configuration에 의해서 자동 생성됨
    @Bean
    public RedisTemplate<String, Object> redisTemplate( LettuceConnectionFactory lettuceConnectionFactory ) {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory( lettuceConnectionFactory );

        // set key serializer
        // template.setKeySerializer( new StringRedisSerializer(StandardCharsets.UTF_8) ); 와 동일함
        template.setKeySerializer( RedisSerializer.string() );
        template.setHashKeySerializer( RedisSerializer.string() );

        // set value serializer
        // template.setDefaultSerializer( new GenericJackson2JsonRedisSerializer() ); 와 동일함
        template.setDefaultSerializer( RedisSerializer.json() );
        template.setValueSerializer( RedisSerializer.json() );
        template.setHashValueSerializer( RedisSerializer.json() );

        return template;
    }
}

 

테스트 코드

@ExtendWith( SpringExtension.class )
@ContextConfiguration( classes = { RedisConfiguration.class } )
@DataRedisTest
@EnableAutoConfiguration
public class RedisTest {

    @Autowired
    RedisTemplate<String, Object> redisTemplate;

    final UUID id = UUID.randomUUID();

    @Test
    @DisplayName( "redis insert test" )
    void insert_person_redis_test() {
        Person person = Person.builder()
                .id( id )
                .personName( "test-name" )
                .personAge( 100 )
                .personNation( "korea" )
                .comment( "first commit data" )
                .build();

        redisTemplate.opsForValue().set( id.toString(), person );

        // find data
        Object object = redisTemplate.opsForValue().get( id.toString() );
        Assertions.assertThat( object ).isInstanceOf( Person.class );
        Person find = ( Person ) object;
        Assertions.assertThat( find.id().toString() ).isEqualTo( id.toString() );
    }
}

테스트 코드를 통해서 Redis에 저장된 데이터를 살펴보면 JSON 형식으로 저장된 것을 확인해 볼 수 있다.

데이터 조회
id를 키로 Person 데이터 조회
데이터 조회 결과
데이터 조회 결과

 

Spring Data Redis를 사용하기 위한 연결 설정을 위해서 수행되는 Redis Auto Configuration에 대해서 살펴보았다.

application.yml (.properties)의 spring.data.redis 설정과 변경하고자 하는 ClientResources, ClientConfiguration에 대한 customize 빈과 RedisTemplate에 적용할 Serializer를 설정하는 것만으로 손쉽게 Redis를 활용할 수 있다.

 

끝.

 


샘플 코드는 GITLAB 링크에서 확인할 수 있다.

 

댓글

💲 추천 글