Redis replication-Sentinel, Cluster차이와 Cluster구축 연동 방법

taehee kim·2023년 1월 14일
1

redis

목록 보기
2/4

Replication

1. 필요성

  • 백업만으로는 장애 대비에 부족함(소요 시간 및 백업 실패)
  • Redis도 관계형 데이터베이스 처럼 복제를 통해 가용성과 조회 트래픽 분산을 통해 성능을 향상 시킬 수 있음.
  • 관계형 데이터베이스처럼 Master Node와 Replica Node로 구성 되어 Master Node 장애 발생 시 Replica Node를 master로 전환

2. 적용 방법

  • replicaof
    • 다음과 같은 내용을 replica로 적용할 redis에 대해서 실행한다.
  • replica-read-only
    • replica는 read-only로 설정한다.
  • 주의사항
    • Master 노드에는 백업기능을 무조건 활성화 해두어야한다.(master 재시작 시 백업이 되어있지 않아서 빈 상태로 있게 되면 replica에도 빈상태가 복제 되기 때문이다.)

3. 한계점

  • Master장애 시 수동으로 Replica를 Master로 전환해주어야함.

Redis Sentinel

  • Redis에서 HA를 제공하기 위한 장치
  • master-replica 구조에서 master가 다운 시 replica를 master로 승격시키는 auto-failover수행.
  • Sentinel node는 적어도 3개 이상 유지(Quorum 때문)되며 Client는 Sentinel을 통해 redis에 접근한다.

Quorum

  • Sentinel들의 투표를 통해 Failover여부를 결정한다.
  • 이때, 각각의 Sentinel의 주관적 redis down여부를 SDOWN이라고 한다.
  • Quorum은 SDOWN의 개수이며 이것이 특정 값(일반적으로 과반 수)을 넘어야 Failover를 실제로 발생시킨다.
  • ODOWN은 개별적 Sentinel의 판단이 아닌 객관적 Quorum수에 의한 판단을 의미하며 ODOWN 판단을 해야 Failover를 하게 된다.

Redis Cluster

확장성

  • 확장성이란 규모의 관점에서 요구사항이 증가할 때 대처할 수 있는 능력을 말한다.
  • scale-up 방식의 경우에는 하나의 컴포넌트의 성능을 증가시키는 방법이다.
    • 일정량 이상으로 scale-up을 하게 되면 성능 증가를 위해 쏟은 input대비 실제 대비 실제 성능 증가가 비효율적으로 이루어질 수 있다.
  • scale-out 방식은 같은 컴포넌트 개수를 늘리는 방식이다.
    • scale-up의 문제점인 성능 향상 input 대비 실제 성능 증가가 비효율적이라는 문제를 해결할 수 있다.
    • 분산시스템으로 구성해야하기 때문에
      • 네트워크 실패나 데이터 동기화 등 개발 및 관리의 복잡성이 증가하는 어려움이 있다.
    • 하지만, 어려움에도 불구하고 아키텍처를 설계할 때 scale-out을 어떻게 할지에 초점을 맞추어 개발하는 경우가 많다.

Redis Cluster란?

  • 여러 노드에 자동적인 데이터 분산
  • 일부 노드의 실패나 통신 단절에도 계속 작동하는 가용성
  • 고성능을 보장하면서 선형 확장성을 제공

특징

  • Full-mesh 구조로 통신 (모든 노드가 연결되어 있음).
  • cluster bus라는 추가 채널(port 16379) 사용
  • gossip protocol
    • 동기화를 위해 모든 노드들이 서로에게 데이터를 송신하면 같은 데이터를 여러번 주고 받기 때문에 매우 비효율적임.
    • gossip protocol은 소문이 퍼지는 것처럼 주변에 있는 일부 node에만 데이터를 송신하게 되면 node간의 데이터 정합성이 시간이 지나면 확률적으로 맞추어지는 protocol방식https://www.educative.io/answers/what-is-gossip-protocol
  • start 시에 hash slot 사용

Sentinel과의 차이점

  • 가장 큰 차이점은 Sharding을 제공한다는 점
    • 이 때문에 multikey operation이 제한됨.
    • master node가 여럿 존재할 수 있음.
    • sharding을 위해 hash slot이 정의 되어야함.
  • node관리를 위한 Sentinel노드가 별개로 필요하지않음.

Hash Slot이란?

  • 다음과 같이 특정 shard에게 할당된 key공간을 나누어 관리
  • shard 추가, 변경시에 hash algorithm을 변경할 필요가 없음.

Redis Cluster의 데이터 일관성

  • Redis Cluster는 비동기 복제를 하기 때문에 strong consistency를 제공하지 않음.

  • 데이터 복제가 끝나지 않아도 Ack신호가 전송되므로 Master가 죽을 경우 데이터 유실이 가능함.

Redis Cluster의 가용성- auto failover

  • 일부 master노드가 통신할 수 없는 상태일 때 과반수 master가 남아있고 문제가 있는 master의 replica가 있다면 클러스터는 failover할 수 있음.
  • 이 경우 기존 master의 replica를 master로 승격시킴.
  • 사진의 경우 master1과 replica2가 불능 상태이지만, master1을 대체하여 replica1을 승격시켜 master1의 hash slot에 대응하도록 할 수 있음.

Redis Cluster 구축

1. Redis 다운로드

2. redis.conf파일 다운로드

3. redis.conf파일 수정

  • 저는 다음과 같은 필드들을 수정했는데 redis-cluster구축을 위해서는 필수적으로 port와, cluster-enable yes로 지정해야합니다.
  • 자세한 옵션의 설정 내용은 다음 링크를 참조 하시면 됩니다.
    redis 설정파일 옵션
port 5001 //몇번 포트에서 server
cluster-enable yes
cluster-config-file /home/ec2-user/redis/redis-cluster/5001/nodes-5001.conf
cluster-node-timeout 5000

cluster-require-full-coverage yes
cluster-migration-barrier 1
cluster-replica-validity-factor 10

dir /home/ec2-user/redis/redis-cluster/5001
appendonly yes
daemonize yes

4. redis-server 시작

  • redis cluster node는 최소 3개의 master node를 필요로 합니다. 그리고 각각의 master node가 replica node를 적어도 하나 필요로 하기 때문에 총 6개의 redis-server를 실행해야합니다.
  • 실행 커멘드는 다음과 같습니다.
    redis-server {conf파일}

5. redis cluster모드로 실행

  • 다음 커맨드 입력시 cluster 모드로 node들이 실행되게 됩니다.
  • --cluster-replicas 는 원하는 개수에 따라 조절하면 됩니다.
  redis-cli --cluster create {모든 redis node의 ip:port} --cluster-replicas 1

  • yes를 입력해줍니다. 어떤 node가 master, slave가 될 지는 random 하게 정해집니다.

Redis Cluster Spring project 연동.

@Slf4j
@Configuration
@RequiredArgsConstructor
public class LettuceConnectionConfig {

  @Value("${spring.redis.cluster.nodes}")
  private String clusterNodes;
  @Value("${spring.redis.cluster.max-redirects}")
  private int maxRedirects;
  @Value("${spring.redis.password}")
  private String password;
  private final RedisMessageSubscriber redisMessageSubscriber;

//    private final EntityManagerFactory entityManagerFactory;
//    private final DataSource dataSource;

  /*
   * Class <=> Json간 변환을 담당한다.
   *
   * json => object 변환시 readValue(File file, T.class) => json File을 읽어 T 클래스로 변환 readValue(Url url,
   * T.class) => url로 접속하여 데이터를 읽어와 T 클래스로 변환 readValue(String string, T.class) => string형식의
   * json데이터를 T 클래스로 변환
   *
   * object => json 변환시 writeValue(File file, T object) => object를 json file로 변환하여 저장
   * writeValueAsBytes(T object) => byte[] 형태로 object를 저장 writeValueAsString(T object) => string 형태로
   * object를 json형태로 저장
   *
   * json을 포매팅(개행 및 정렬) writerWithDefaultPrettyPrint().writeValueAs... 를 사용하면 json파일이 포맷팅하여 저장된다.
   * object mapper로 date값 변환시 timestamp형식이 아니라 JavaTimeModule() 로 변환하여 저장한다.
   */

  @Bean
  public ObjectMapper objectMapper() {
      ObjectMapper mapper = new ObjectMapper();
      mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
      mapper.registerModules(new JavaTimeModule(), new Jdk8Module());
      return mapper;
  }


  /**
   * RedisStaticMasterReplicaConfiguration를 사용할 경우 pub/sub사용 불가
   * @return
   */
  @Bean
  public RedisClusterConfiguration redisClusterConfiguration() {
      List<String> clusterNodeList = Arrays.stream(StringUtils.split(clusterNodes, ','))
          .map(String::trim)
          .collect(Collectors.toList());
      RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration(clusterNodeList);
      redisClusterConfiguration.setMaxRedirects(maxRedirects);
      redisClusterConfiguration.setPassword(password);
      return redisClusterConfiguration;
  }

  /*
   * Redis Connection Factory library별 특징
   * 1. Jedis - 멀티쓰레드환경에서 쓰레드 안전을 보장하지 않는다.
   *          - Connection pool을 사용하여 성능, 안정성 개선이 가능하지만 Lettuce보다 상대적으로 하드웨어적인 자원이 많이 필요하다.
   *          - 비동기 기능을 제공하지 않는다.
   *
   * 2. Lettuce - Netty 기반 redis client library
   *            - 비동기로 요청하기 때문에 Jedis에 비해 높은 성능을 가지고 있다.
   *            - TPS, 자원사용량 모두 Jedis에 비해 우수한 성능을 보인다는 테스트 사례가 있다.
   *
   * Jedis와 Lettuce의 성능 비교  https://jojoldu.tistory.com/418
   */
  @Bean
  public RedisConnectionFactory redisConnectionFactory(
      final RedisClusterConfiguration redisClusterConfiguration) {
      final SocketOptions socketOptions =
          SocketOptions.builder().connectTimeout(Duration.of(10, ChronoUnit.MINUTES)).build();

      final var clientOptions =
          ClientOptions.builder().socketOptions(socketOptions).autoReconnect(true).build();

      var clientConfig =
          LettuceClientConfiguration.builder()
              .clientOptions(clientOptions)
              .readFrom(REPLICA_PREFERRED);
//        if (useSSL) {
//            // aws elasticcache uses in-transit encryption therefore no need for providing certificates
//            clientConfig = clientConfig.useSsl().disablePeerVerification().and();
//        }

      return new LettuceConnectionFactory(
          redisClusterConfiguration, clientConfig.build());
  }


//    @Bean // 만약 PlatformTransactionManager 등록이 안되어 있다면 해야함, 되어있다면 할 필요 없음
//    public PlatformTransactionManager transactionManager() throws SQLException {
//        // 사용하고 있는 datasource 관련 내용, 아래는 JDBC
////        return new DataSourceTransactionManager(dataSource);
//
//        // JPA 사용하고 있다면 아래처럼 사용하고 있음
//        return new JpaTransactionManager(entityManagerFactory);
//    }

  @Bean
  public RedisTemplate<String, Object> redisTemplate(ObjectMapper objectMapper) {
      GenericJackson2JsonRedisSerializer serializer =
          new GenericJackson2JsonRedisSerializer(objectMapper);

      RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
      redisTemplate.setConnectionFactory(redisConnectionFactory(redisClusterConfiguration()));
      // json 형식으로 데이터를 받을 때
      // 값이 깨지지 않도록 직렬화한다.
      // 저장할 클래스가 여러개일 경우 범용 JacksonSerializer인 GenericJackson2JsonRedisSerializer를 이용한다
      // 참고 https://somoly.tistory.com/134
      // setKeySerializer, setValueSerializer 설정해주는 이유는 RedisTemplate를 사용할 때 Spring - Redis 간 데이터 직렬화, 역직렬화 시 사용하는 방식이 Jdk 직렬화 방식이기 때문입니다.
      // 동작에는 문제가 없지만 redis-cli을 통해 직접 데이터를 보려고 할 때 알아볼 수 없는 형태로 출력되기 때문에 적용한 설정입니다.
      // 참고 https://wildeveloperetrain.tistory.com/32
      redisTemplate.setKeySerializer(new StringRedisSerializer());
      redisTemplate.setValueSerializer(serializer);
      redisTemplate.setHashKeySerializer(new StringRedisSerializer());
      redisTemplate.setHashValueSerializer(serializer);
      redisTemplate.setEnableTransactionSupport(true); // transaction 허용

      return redisTemplate;
  }
  @Bean
  ChannelTopic topic() {
      return new ChannelTopic(SseEventName.ALARM_LIST.getValue());
  }

  @Bean
  MessageListenerAdapter messageListener() {
      return new MessageListenerAdapter(redisMessageSubscriber);
  }
  @Bean
  public RedisMessageListenerContainer redisMessageListenerContainer() {
      final RedisMessageListenerContainer container = new RedisMessageListenerContainer();
      container.setConnectionFactory(redisConnectionFactory(redisClusterConfiguration()));
      container.addMessageListener(messageListener(), topic());
      log.info("PubSubConfig init");
      return container;
  }
}
profile
Fail Fast

2개의 댓글

comment-user-thumbnail
2023년 3월 22일

노드가 여러개일때 host와 port는 어떻게 넣어주나요?

답글 달기
comment-user-thumbnail
2023년 3월 22일

활성화 클러스터 모드 랑 전송중암호화 했을시는 없나요

답글 달기