프로젝트 STG 환경 Redis 구성을 Cluster 모드로 업그레이드를 진행하였습니다.

ECS 컨테이너에서는 접속에 문제가 없지만 Bastion Host를 통한 Local 접속 시 아래와 같이 오류가 발생하였습니다.

 

[전제조건] Redis는 Private Subnet 네트워크 구성

2024-03-25 15:00:22.591  WARN [NewEverTimeServer,,] 3782 --- [ioEventLoop-6-3] i.l.core.cluster.RedisClusterClient      : connection timed out: test-redis-0001-001.test-cluster-redis.******.apn2.cache.amazonaws.com/10.***.***.129:6379
2024-03-25 15:00:32.633  WARN [NewEverTimeServer,,] 3782 --- [ioEventLoop-6-4] i.l.core.cluster.RedisClusterClient      : connection timed out: test-redis-0001-002.test-cluster-redis.******.apn2.cache.amazonaws.com/10.***.***.47:6379

org.springframework.data.redis.RedisConnectionFailureException: Redis connection failed; nested exception is io.lettuce.core.RedisConnectionException: Unable to connect to [rediss://127.0.0.1:56379?verifyPeer=NONE, rediss://127.0.0.1:56380?verifyPeer=NONE]

 

원인을 찾아보니 Cluster모드의 Redis에서 응답하는 nodes 정보가 Private Subnet IP로 확인하였습니다.

 Lettuce 라이브러리에 Cluster Nodes 정보를 Mapping 해주지 않으면 Redis에서 응답받은 nodes 정보가 반영되어

접속 오류가 발생하는 것입니다.

Lettuce 라이브러리에서도 특별한 가이드라인은 찾지 못하였습니다.

hinweis@MacBook-Pro ~ % redis-cli -h 127.0.0.1 -p 56379
127.0.0.1:56379> ping
PONG
127.0.0.1:56379> cluster slots
1) 1) (integer) 0
   2) (integer) 16383
   3) 1) "test-redis-0001-001.test-cluster-redis.******.apn2.cache.amazonaws.com"
      2) (integer) 6379
      3) ""
      4) 1) "ip"
         2) "10.***.***.129"
   4) 1) "test-redis-0001-002.test-cluster-redis.******..apn2.cache.amazonaws.com"
      2) (integer) 6379
      3) "9221821ec8a571b31cfe80a60de87efda4526f4c"
      4) 1) "ip"
         2) "10.***.***.47"

 


문제 해결을 위해서 아래와 같이 작업을 진행하였습니다.

 

AWS Redis 서비스를 사용하기 위해서는 Lettuce 2.6.6 이상 권장합니다. (공식문서 참고)

 

1. Application.yml 설정

- cluster nodes 정보 추가

  redis:
#   host: 127.0.0.1
#   port: 56379
    cluster:
      nodes: 127.0.0.1:56379, 127.0.0.1:56380
      max-redirects: 2

 

 

2. RedisConfig 구성 변경

- Redis 모드 (Standalone, Cluster)에 따라 설정 분기 로직 추가

- 아래는 이해하기 편하게 테스트 코드 기준으로 명시 (추후 구조화 적용)

@Configuration
@RequiredArgsConstructor
public class RedisConfig {

    @Value("${spring.redis.host:localhost}")
    private String redisHost;

    @Value("${spring.redis.port:6379}")
    private int redisPort;
    
    @Value("${spring.redis.cluster.nodes:127.0.0.1:6379}")
    private List<String> clusterNodes;

    @Value("${spring.redis.cluster.max-redirects:0}")
    private int maxRedirects;

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {
        if (maxRedirects > 0) {
            List<String> nodes;

            if (clusterNodes.size() == INTEGER_ONE) {
                nodes = Collections.singletonList(HostAndPort.of(redisHost, redisPort).toString());
            }
            else {
                nodes = clusterNodes;
            }

            // cluster nodes를 원하는 값으로 적용
            RedisClusterConfiguration clusterConfiguration = new RedisClusterConfiguration(nodes);
            clusterConfiguration.setMaxRedirects(maxRedirects);
			
            // topology 자동 업데이트 옵션 추가
            ClusterTopologyRefreshOptions topologyRefreshOptions = ClusterTopologyRefreshOptions.builder()
                    .enableAllAdaptiveRefreshTriggers()
                    .enablePeriodicRefresh()
                    .dynamicRefreshSources(false)
                    .build();
                    
            // topology 옵션 및 timeout 세팅   
            ClusterClientOptions clientOptions = ClusterClientOptions.builder()
                    .autoReconnect(true)
                    .topologyRefreshOptions(topologyRefreshOptions)
                    .validateClusterNodeMembership(false)
                    .build();

            LettuceClientConfiguration lettuceClientConfiguration = useSsl ?
                    LettuceClientConfiguration.builder()
                            .clientOptions(clientOptions)
                            .readFrom(ReadFrom.REPLICA_PREFERRED)
                            .useSsl()
                            .disablePeerVerification()
                            .build()
                    :LettuceClientConfiguration.builder()
                            .clientOptions(clientOptions)
                            .readFrom(ReadFrom.REPLICA_PREFERRED)
                            .build();
            return new LettuceConnectionFactory(clusterConfiguration, lettuceClientConfiguration);
        } else {
            RedisStandaloneConfiguration redisConfiguration = new RedisStandaloneConfiguration(redisHost, redisPort);
            LettuceClientConfiguration lettuceClientConfiguration = useSsl ?
                    LettuceClientConfiguration.builder()
                            .useSsl()
                            .disablePeerVerification()
                            .build()
                    : LettuceClientConfiguration.builder().build();
            return new LettuceConnectionFactory(redisConfiguration, lettuceClientConfiguration);
        }
    }
}

 

 

읽어주셔서 감사합니다.

 

레퍼런스
반응형
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기