[Redis] Redis Sentinel?

greenlemonT·2025년 2월 4일

Redis

목록 보기
1/3

Redis Sentinel

Redis Sentinel이란?

  • 마스터 장애 발생 시 슬레이브를 새로운 마스터로 자동 승격하는 역할함
  • Sentinel을 통해 애플리케이션은 직접 마스터 IP를 관리할 필요 없이, Sentinel을 통해 자동으로 새로운 마스터를 감지하여 연결할 수 있다.

Redis Replication 적용 및 Sentinel로 자동 장애 조치

docker-compose.yml - Redis Sentinel 구성

1. Redis Master 컨테이너를 생성


  redis_master:
    hostname: redis-master
    container_name: redis-master
    image: "bitnami/redis"
    environment:
      - REDIS_REPLICATION_MODE=master
      - ALLOW_EMPTY_PASSWORD=yes
    ports:
      - "6379:6379"

2. Master를 복제하는 Slave

  redis_replica1:
    hostname: redis-replicas-1
    container_name: redis-replicas-1
    image: "bitnami/redis"
    environment:
      - REDIS_REPLICATION_MODE=slave
      - REDIS_MASTER_HOST=redis-master
      - ALLOW_EMPTY_PASSWORD=yes
    ports:
      - "5000:6379"
    depends_on:
      - redis_master

3. Sentinel 노드 구성

  redis-sentinel-1:
    container_name: sentinel1
    image: 'bitnami/redis-sentinel:latest'
    environment:
      - REDIS_SENTINEL_DOWN_AFTER_MILLISECONDS=3000
      - REDIS_MASTER_HOST=redis-master
      - REDIS_MASTER_PORT_NUMBER=6379
      - REDIS_MASTER_SET=mymaster
      - REDIS_SENTINEL_QUORUM=2
    ports:
      - "26379:26379"
    depends_on:
      - redis_master
      - redis_replica1
      - redis_replica2
     networks:
      - t4y
      
   redis-sentinel-2:
    container_name: sentinel2
    image: 'bitnami/redis-sentinel:latest'
    environment:
      - REDIS_SENTINEL_DOWN_AFTER_MILLISECONDS=3000
      - REDIS_MASTER_HOST=redis-master
      - REDIS_MASTER_PORT_NUMBER=6379
      - REDIS_MASTER_SET=mymaster
      - REDIS_SENTINEL_QUORUM=2
    ports:
      - "26380:26379"
    depends_on:
      - redis_master
      - redis_replica1
      - redis_replica2
    networks:
      - t4y
  redis-sentinel-3:
    container_name: sentinel3
    image: 'bitnami/redis-sentinel:latest'
    environment:
      - REDIS_SENTINEL_DOWN_AFTER_MILLISECONDS=3000
      - REDIS_MASTER_HOST=redis-master
      - REDIS_MASTER_PORT_NUMBER=6379
      - REDIS_MASTER_SET=mymaster
      - REDIS_SENTINEL_QUORUM=2
    ports:
      - "26381:26379"
    depends_on:
      - redis_master
      - redis_replica1
      - redis_replica2
    networks:
      - t4y
  • Sentinel 노드를 구성하여 장애 감지를 수행
  • REDIS_SENTINEL_QUORUM=2: 장애 감지를 위해 최소 2개의 Sentinel이 필요

Spring Boot에서 Redis Sentinel 구성

gateway

redisConfig

@Configuration
public class RedisConfig {

    @Autowired
    private RedisSentinelProperties redisSentinelProperties;

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {

        // Redis Sentinel 설정
        LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder().readFrom(ReadFrom.REPLICA_PREFERRED).build();

        final RedisSentinelProperties.RedisMasterProperties masterConfig = redisSentinelProperties.getMaster();
        RedisStaticMasterReplicaConfiguration staticMasterReplicaConfiguration = new RedisStaticMasterReplicaConfiguration(masterConfig.getHost(), masterConfig.getPort());
        redisSentinelProperties.getNodes().forEach(node -> {
            String[] nodeInfo = node.split(":");
            staticMasterReplicaConfiguration.addNode(nodeInfo[0], Integer.parseInt(nodeInfo[1]));
        });

        return new LettuceConnectionFactory(staticMasterReplicaConfiguration, clientConfig);
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory());

        ObjectMapper objectMapper = new ObjectMapper();
        objectMapper.findAndRegisterModules(); // LocalDateTime 같은 타입을 위한 모듈 자동 등록
        objectMapper.deactivateDefaultTyping(); // @class 제거

        // Jackson2JsonRedisSerializer 설정
        Jackson2JsonRedisSerializer<Object> serializer = new Jackson2JsonRedisSerializer<>(objectMapper, Object.class);

        // 키는 String, 값은 JSON 형식으로 처리
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(serializer);
        template.setHashKeySerializer(new StringRedisSerializer());
        template.setHashValueSerializer(serializer);

        return template;
    }
}
@Data
@Component
@ConfigurationProperties(prefix = "spring.data.redis.sentinel")
public class RedisSentinelProperties {
    private RedisMasterProperties master;
    private List<String> nodes;

    @Data
    public static class RedisMasterProperties {
        private String host;
        private Integer port;
    }

}

Master-Slave + Sentinel 구조 적용

  • Master-Slave 복제 (ReadFrom.REPLICA_PREFERRED) → Redis가 다운되면 슬레이브가 읽기 처리.
  • Sentinel 사용 → 장애 발생 시 자동으로 Master를 변경.
  • RedisSentinelProperties 클래스는 @ConfigurationProperties를 사용하여 Spring Boot가 자동으로 바인딩할 수 있도록 구성
  • Sentinel 노드 목록을 Set<RedisNode>로 변환 →
    변환된 Sentinel 노드 목록을 Sentinel 설정(sentinelConfig)에 추가

다른 모듈들 (ex.deliveryService)

RedisConfig

@Configuration
@EnableCaching
public class RedisConfig {

    public static final String CACHE_PREFIX = "deliveryCache";

    @Autowired
    private RedisSentinelProperties redisSentinelProperties;

    @Bean
    public RedisConnectionFactory redisConnectionFactory() {

        // Redis Sentinel 설정
        LettuceClientConfiguration clientConfig = LettuceClientConfiguration.builder().readFrom(ReadFrom.REPLICA_PREFERRED).build();

        final RedisSentinelProperties.RedisMasterProperties masterConfig = redisSentinelProperties.getMaster();
        RedisStaticMasterReplicaConfiguration staticMasterReplicaConfiguration = new RedisStaticMasterReplicaConfiguration(masterConfig.getHost(), masterConfig.getPort());
        redisSentinelProperties.getNodes().forEach(node -> {
            String[] nodeInfo = node.split(":");
            staticMasterReplicaConfiguration.addNode(nodeInfo[0], Integer.parseInt(nodeInfo[1]));
        });

        return new LettuceConnectionFactory(staticMasterReplicaConfiguration, clientConfig);
    }

    @Bean
    public RedisTemplate<String, Object> redisTemplate() {
        RedisTemplate<String, Object> template = new RedisTemplate<>();
        template.setConnectionFactory(redisConnectionFactory());
        template.setKeySerializer(new StringRedisSerializer());
        template.setValueSerializer(new JdkSerializationRedisSerializer());
        return template;
    }

    @Bean
    public RedisCacheManager cacheManager(
            RedisConnectionFactory redisConnectionFactory) {
        RedisCacheConfiguration configuration = RedisCacheConfiguration
                .defaultCacheConfig()
                .disableCachingNullValues()
                .entryTtl(Duration.ofHours(2))
                .computePrefixWith(CacheKeyPrefix.simple())
                .serializeKeysWith(SerializationPair.fromSerializer(new StringRedisSerializer()))
                .serializeValuesWith(SerializationPair.fromSerializer(new JdkSerializationRedisSerializer()));

        return RedisCacheManager
                .builder(redisConnectionFactory)
                .cacheDefaults(configuration)
                .build();
    }
}

RedisCacheManager의 역할

  • Spring Cache 기능을 지원하여 @Cacheable, @CachePut, @CacheEvict 같은 캐시 관련 어노테이션을 사용할 수 있음.
  • TTL(Time-to-Live) 설정 (entryTtl(Duration.ofHours(2))) → 2시간 동안 캐싱된 데이터 유지.
  • 캐시 Key Prefix 설정CacheKeyPrefix.simple()을 사용하여 캐시 키를 쉽게 관리.

3. RedisCacheManager vs RedisTemplate 차이

구분RedisCacheManagerRedisTemplate
주요 목적Spring Cache 기능 제공 (@Cacheable, @CachePut)Key-Value 직접 조작
설정 방식@EnableCaching & @Cacheable 어노테이션 사용redisTemplate.opsForValue().set(key, value)
데이터 저장Spring Cache 시스템이 자동으로 관리개발자가 직접 데이터 저장 및 조회
TTL 설정entryTtl(Duration.ofHours(2)) 설정 가능redisTemplate.expire(key, 2, TimeUnit.HOURS) 사용
직렬화 방식JdkSerializationRedisSerializer 사용 (객체 직렬화)Jackson2JsonRedisSerializer 사용 (JSON 저장)

4. RedisCacheManager vs RedisTemplate 예시

1) RedisCacheManager사용

@Service
@RequiredArgsConstructor
public class DeliveryApplicationService {

    private final DeliveryRepository deliveryRepository;
    private final RedisTemplate<String, Object> redisTemplate;

    @CachePut(value = "deliveryCache", key = "#deliveryId")
    public DeliveryInfoResponseDto endDelivery(UUID deliveryId) {
        log.info("배송 완료 요청을 처리합니다. 배송 ID: {}", deliveryId);
        Delivery delivery = deliveryRepository.findById(deliveryId).orElseThrow(
                () -> new CustomException(DeliveryErrorCode.DELIVERY_NOT_FOUND)
        );

        if (delivery.getDeliveryStatus() == DeliveryStatus.PENDING) {
            throw new CustomException(DeliveryErrorCode.DELIVERY_NOT_STARTED);
        }
        if (delivery.getDeliveryStatus() == DeliveryStatus.DELIVERED) {
            throw new CustomException(DeliveryErrorCode.DELIVERY_ALREADY_DELIVERED);
        }

        delivery.endDelivery();
        Delivery savedDelivery = deliveryRepository.save(delivery);

        return DeliveryMapper.convertEntityToInfoResponseDto(savedDelivery);
    }
}

@CachePut을 사용하여 Redis 캐시에 배송 정보 저장

  • @CachePut(value = "deliveryCache", key = "#deliveryId")
    • 배송 완료 후 배송 ID를 Key로 Redis에 저장.
    • 이후 같은 ID의 배송 데이터를 조회하면 Redis에서 바로 반환(DB 접근 불필요).

2) Redistemplate을 사용하여 직접 데이터 저장

@Transactional(readOnly = true)
public DeliveryInfoResponseDto getDeliveryInfo(UUID deliveryId) {
    log.info("배송 정보 조회 요청을 처리합니다. 배송 ID: {}", deliveryId);

    DeliveryInfoResponseDto cachedDeliveryInfo = (DeliveryInfoResponseDto) redisTemplate.opsForValue().get(
            "deliveryCache::" + deliveryId
    );

    if (cachedDeliveryInfo != null) {
        return cachedDeliveryInfo;
    }

    Delivery delivery = deliveryRepository.findById(deliveryId)
            .orElseThrow(() -> new CustomException(DeliveryErrorCode.DELIVERY_NOT_FOUND));

    return DeliveryMapper.convertEntityToInfoResponseDto(delivery);
}

application.yml

spring:
  data:
    redis:
      sentinel:
        master:
          host: localhost
          port: 6379
        nodes:
          - localhost:26379
          - localhost:26380
          - localhost:26381
      timeout: 10000

nodes?

  • 애플리케이션이 Sentinel들과 통신하여 Redis 마스터 정보를 동적으로 가져올 수 있도록 하는 설정
  • Redis Sentinel에 연결할 때 사용할 Sentinel 서버들의 주소를 정의

💡 즉, nodes를 설정하면 마스터가 변경되더라도 Sentinel을 통해 자동으로 새로운 마스터를 감지할 수 있다.

0개의 댓글