Spring Redis Sentinel

레디스 개발자 교육 신청 레디스 정기점검/기술지원
Redis Technical Support
레디스 엔터프라이즈 서버
Redis Enterprise Server

Spring Redis Sentinel

이 문서는 Spring Data Redis를 이용해서 레디스 센티널(Redis Sentinel) 용 Java Spring 애플리케이션을 개발하는 방법을 설명합니다.
레디스 센티널에 연결(접속)하는 방법, 마스터가 다운되는 장애 발생 시 레터스 작동 방식, Pub/Sub과의 관계 등을 설명하고 관련 자바 소스 코드(source code)를 제공합니다.


기본 사용법

센티널 구성에서는 레디스 서버의 주소를 사용하지 않고 센티널 서버 주소로 연결(접속)합니다.
Spring은 센티널에 접속해서 레디스 서버의 주소를 얻어와서 레디스 서버에 연결(접속)합니다. 그러므로 레디스 마스터가 다운되어 마스터 주소가 바뀌었어도 센티널이 최신 정보를 가지고 있기 때문에 새 마스터에 연결할 수 있습니다.
센티널이 3대이면 3대 주소 모두 등록합니다.

자동 연결(Auto Config)를 사용할 경우 LettuceConnectionFactory를 생성할 필요가 없습니다. 아래 예에서는 ①,②,③이 필요없고, redisProps, redisConn 클래스가 필요없습니다. 하지만 이 경우 readFrom을 설정할 수 없으므로 조회 명령을 복제 서버에서 실행하는 부하분산을 할 수 없습니다.

① new RedisSentinelConfiguration(redisProps.getSentinel().getMaster(),
new HashSet<>(redisProps.getSentinel().getNodes()))
RedisSentinelConfiguration을 master, nodes(센티널 서버 주소 목록)로 생성합니다.
② LettuceClientConfiguration.builder()...
readFrom, commandTimeout, clientName 등을 설정합니다.
③ new LettuceConnectionFactory(redisConfig, clientConfig)
LettuceConnectionFactory를 생성합니다.
④ valueOperations.set(key, value)
레디스 명령을 실행합니다.

Source code

Application 시작

애플리케이션이 시작할 때는 센티널/레디스 서버에 접속(연결)하지 않습니다. 첫 명령을 받으면 센티널에 접속해서 마스터 정보를 조회하고, 마스터에 접속해서 명령을 실행합니다.

본 테스트는 애플리케이션은 debug 모드로 실행했고, 레디스 서버는 명령을 로그에 기록하도록 수정해서 작성했습니다. 조회 명령도 마스터에서 실행되도록 설정했습니다.

  • Application 실행, 명령 실행, 센티널/레디스 연결
    http://localhost:8080/sentinel/set/key16 -> 저장
    http://localhost:8080/sentinel/get/key16 -> 조회
  • Application Log
    01:52:32.590 : Received [GET /sentinel/set/key16 HTTP/1.1 -> SET 명령 받음
    01:52:33.167 : Connecting to Redis using Sentinels [redis://192.168.56.102:7000, redis://192.168.56.102:7001, redis://192.168.56.102:7002], MasterId mymaster -> 센티널 목록
    01:52:33.185 : Connecting to Redis Sentinel, address: redis://192.168.56.102:7000 -> Sentinel_1 접속 시도
    01:52:33.577 : [192.168.56.1:8849 -> 192.168.56.102:7000] Completing command [type=HELLO, output={server=redis, version=7.2.3, mode=sentinel}]
    01:52:33.583 : Connecting to Redis at 192.168.56.102:7000: Success -> Sentinel_1 접속 성공
    01:52:33.623 : [192.168.56.1:8849 -> 192.168.56.102:7000] Completing command [type=SENTINEL, output=192.168.56.102:6000] -> 마스터 정보를 조회
        > SENTINEL get-master-addr-by-name mymaster -> 192.168.56.102:6000
    01:52:33.639 : Connecting to Redis at 192.168.56.102:6000 -> Redis_1 접속 시도
    01:52:33.648 : [192.168.56.1:8850 -> 192.168.56.102:6000] Completing command [type=HELLO, output={server=redis, version=7.2.3, mode=standalone, role=master}]
    01:52:33.649 : Connecting to Redis at 192.168.56.102:6000: Success -> Redis_1 접속 성공
    01:53:33.655 : [192.168.56.1:8850 -> 192.168.56.102:6000] Completing command [type=SET, output=OK]
    01:53:33.681 : Completed 200 OK -> SET 명령 성공

    01:53:42.209 : Received [GET /sentinel/get/key16 HTTP/1.1 -> GET 명령 받음
    01:53:42.216 : [192.168.56.1:8850 -> 192.168.56.102:6000] Completing command [type=GET, output=[B@12785b45]]
    01:53:42.220 : Completed 200 OK -> GET 명령 성공
    HELLO 명령 실행 예

장애복구(failover)와 Application 연결

Redis_1 (6000), Sentinel_1 (7000) 다운 -> 01:54:51.901 (서버 시간: 클라이언트와 약간의 시간 차이가 있음)
Sentinel_2에 접속해서 새 마스터 정보를 조회해서 레디스 서버에 연결합니다.

  • Application Log
    01:54:51.665 : [192.168.56.1:8850 -> 192.168.56.102:6000] channelInactive() -> Redis_1 다운 감지
    01:54:51.666 : [192.168.56.1:8850 -> 192.168.56.102:6000] Reconnect attempt 1, delay 1ms -> 1ms, 2, 4, ..., 1sec, 2, 4, ..., 30, 30sec 반복 연결 시도, 해당 로그 생략

    01:54:51.700 : Connecting to Redis Sentinel, address: redis://192.168.56.102:7000 -> Sentinel_1 접속 시도
    01:54:53.720 : Cannot connect Redis Sentinel at redis://192.168.56.102:7000: -> Sentinel_1 접속 실패

    01:54:53.721 : Connecting to Redis Sentinel, address: redis://192.168.56.102:7001 -> ① Sentinel_2 접속 시도
    01:54:53.737 : [192.168.56.1:8866 -> 192.168.56.102:7001] Completing command [type=HELLO, output={server=redis, version=7.2.3, mode=sentinel}]
    01:54:53.738 : Connecting to Redis at 192.168.56.102:7001: Success -> Sentinel_2 접속 성공
    01:54:53.742 : [192.168.56.1:8866 -> 192.168.56.102:7001] Completing command [type=SENTINEL, output=192.168.56.102:6000] -> 마스터 정보 조회, 아직 Failover 전이므로 6000번을 리턴
        > SENTINEL get-master-addr-by-name mymaster -> 192.168.56.102:6000
    01:54:53.746 : Reconnecting to Redis at 192.168.56.102:6000 -> Redis_1 접속 시도
    01:54:55.769 : Cannot reconnect to [192.168.56.102:6000]: Connection refused: -> Redis_1 접속 실패

    01:54:55.794 : Connecting to Redis Sentinel, address: redis://192.168.56.102:7000 -> Sentinel_1 접속 시도
    01:54:57.805 : Cannot connect Redis Sentinel at redis://192.168.56.102:7000: -> Sentinel_1 접속 실패

    01:54:57.806 : Connecting to Redis Sentinel, address: redis://192.168.56.102:7001 -> ② Sentinel_2 접속 시도
    01:54:57.817 : [192.168.56.1:8869 -> 192.168.56.102:7001] Completing command [type=HELLO, output={server=redis, version=7.2.3, mode=sentinel}]
    01:54:57.823 : [192.168.56.1:8869 -> 192.168.56.102:7001] Completing command [type=SENTINEL, output=192.168.56.102:6001] -> 새 마스터(6001) 정보를 얻어옴.
    01:54:57.826 : Reconnecting to Redis at 192.168.56.102:6001 -> 새 마스터 (6001)번에 접속 시도
    01:54:57.840 : [192.168.56.1:8870 -> 192.168.56.102:6001] Completing command [type=HELLO, output={server=redis, version=7.2.3, mode=standalone, role=master}] -> 6001 서버 접속 성공
  • Sentinel_2 (7001) Log
    1460:X 01:54:51.899 - Client closed connection addr=192.168.56.102:49244 name=sentinel-9005923a-cmd -> Sentinel_1 다운, 연결 끊김

    1460:X 01:54:53.963 - 192.168.56.1:8866: HELLO 3 -> ① Application 접속
    1460:X 01:54:53.971 - 192.168.56.1:8866: SENTINEL get-master-addr-by-name mymaster -> 마스터 정보 조회

    1460:X 01:54:54.973 - 192.168.56.102:49262: SENTINEL is-master-down-by-addr 192.168.56.102 6000 4 * -> Failover 절차 시작
    1460:X 01:54:54.978 # +sdown master mymaster 192.168.56.102 6000
    1460:X 01:54:54.978 # +sdown sentinel 9005923a2c9762730662e0a39bb17aba500cb620 192.168.56.102 7000 @ mymaster 192.168.56.102 6000
    1460:X 01:54:55.041 # +odown master mymaster 192.168.56.102 6000 #quorum 2/2
    1460:X 01:54:55.042 # +new-epoch 5
    1460:X 01:54:55.042 # +try-failover master mymaster 192.168.56.102 6000
    1460:X 01:54:55.055 * Sentinel new configuration saved on disk
    1460:X 01:54:55.055 # +vote-for-leader be758c4042d46fb6dbf3317b4585a3c149a58370 5
    1460:X 01:54:55.068 * 98b0a8c86f06d00545989f7122c45acdf31995b3 voted for be758c4042d46fb6dbf3317b4585a3c149a58370 5
    1460:X 01:54:55.122 # +elected-leader master mymaster 192.168.56.102 6000
    1460:X 01:54:55.122 # +failover-state-select-slave master mymaster 192.168.56.102 6000
    1460:X 01:54:55.213 # +selected-slave slave 192.168.56.102:6001 192.168.56.102 6001 @ mymaster 192.168.56.102 6000
    1460:X 01:54:55.213 * +failover-state-send-slaveof-noone slave 192.168.56.102:6001 192.168.56.102 6001 @ mymaster 192.168.56.102 6000
    1460:X 01:54:55.266 * +failover-state-wait-promotion slave 192.168.56.102:6001 192.168.56.102 6001 @ mymaster 192.168.56.102 6000
    1460:X 01:54:55.453 - -role-change slave 192.168.56.102:6001 192.168.56.102 6001 @ mymaster 192.168.56.102 6000 new reported role is master
    1460:X 01:54:55.467 * Sentinel new configuration saved on disk
    1460:X 01:54:55.467 # +promoted-slave slave 192.168.56.102:6001 192.168.56.102 6001 @ mymaster 192.168.56.102 6000
    1460:X 01:54:55.467 # +failover-state-reconf-slaves master mymaster 192.168.56.102 6000
    1460:X 01:54:55.535 * +slave-reconf-sent slave 192.168.56.102:6002 192.168.56.102 6002 @ mymaster 192.168.56.102 6000
    1460:X 01:54:56.157 # -odown master mymaster 192.168.56.102 6000
    1460:X 01:54:56.521 * +slave-reconf-inprog slave 192.168.56.102:6002 192.168.56.102 6002 @ mymaster 192.168.56.102 6000
    1460:X 01:54:56.521 * +slave-reconf-done slave 192.168.56.102:6002 192.168.56.102 6002 @ mymaster 192.168.56.102 6000
    1460:X 01:54:56.621 # +failover-end master mymaster 192.168.56.102 6000
    1460:X 01:54:56.621 # +switch-master mymaster 192.168.56.102 6000 192.168.56.102 6001 -> Failover 완료
    1460:X 01:54:56.621 * +slave slave 192.168.56.102:6002 192.168.56.102 6002 @ mymaster 192.168.56.102 6001
    1460:X 01:54:56.621 * +slave slave 192.168.56.102:6000 192.168.56.102 6000 @ mymaster 192.168.56.102 6001

    1460:X 01:54:58.046 - 192.168.56.1:8869: HELLO 3 -> ② Application 접속
    1460:X 01:54:58.052 - 192.168.56.1:8869: SENTINEL get-master-addr-by-name mymaster -> Application이 마스터 정보 조회해서 마스터에 접속한다.
  • Redis_2 (6001) Log: 복제 -> 마스터
    장애복구(failover) 시 센티널은 레디스 서버(마스터/복제)에 접속되어있는 애플리케이션 연결을 모두 끊습니다. 그러므로 장애복구 시간 동안 정상적인 서비스를 할 수 없습니다.
    1436:S 01:54:51.896 - Client closed connection addr=192.168.56.102:6000 -> Redis_1 다운, 연결 끊김

    1436:S 01:54:53.134 * Connecting to MASTER 192.168.56.102:6000 -> Redis_1에 연결 시도
    1436:S 01:54:53.135 * MASTER <-> REPLICA sync started
    1436:S 01:54:53.135 # Error condition on socket for SYNC: Connection refused -> Redis_1 연결 실패

    1436:S 01:54:55.266 - 192.168.56.102:35100: MULTI -> 센티널로부터 새 마스터로 변경 명령 받음
    1436:S 01:54:55.266 - 192.168.56.102:35100: SLAVEOF NO ONE -> 마스터로 변경
    1436:S 01:54:55.267 - 192.168.56.102:35100: CONFIG REWRITE -> redis.conf 다시 쓰기
    1436:S 01:54:55.267 - 192.168.56.102:35100: CLIENT KILL TYPE normal -> 일반 연결 모두 끊음
    1436:S 01:54:55.267 - 192.168.56.102:35100: CLIENT KILL TYPE pubsub -> Pub/Sub 연결 모두 끊음
    1436:S 01:54:55.267 - 192.168.56.102:35100: EXEC -> 실행: 복제에서 마스터로 변경

    1436:M 01:54:55.550 * Replica 192.168.56.102:6002 asks for synchronization -> 6002번 복제로 부터 데이터 동기화 요청 받음
  • 마스터가 다운되면 센티널은 복제서버 중 하나를 마스터로 승격시키는 명령을 실행하고, 기존 클라이언트(애플리케이션) 연결(normal | pubsub)을 끊습니다.
    • Sentinel 이 CLIENT KILL TYPE normal | pubsub 명령을 실행해서 client 연결을 끊는다.
      Source code: sentinel.c sentinelSendSlaveOf()
    • Redis-4.0 : CLIENT KILL TYPE normal : pubsub은 하지 않는다.
    • Redis-5.0 : CLIENT KILL TYPE normal : pubsub은 하지 않는다.
    • Redis-6.0 : CLIENT KILL TYPE normal | pubsub도 한다.
    • Master-Replica 연결은 끊지 않습니다.
  • 다른 복제서버에도 마스터가 변경되었음 알리고 기존 클라이언트(애플리케이션) 연결(normal | pubsub)을 끊습니다. 애플리케이션은 재연결을 시도해서 다시 연결합니다.
    • 자동 재연결은 ClientOptions에서 설정하며, 기본이 true입니다.
    • 재연결은 1,2,4,8,16,30초, 이후 30초마다 반복해서 재연결 시도합니다.
    • clientOptions.autoReconnect(true);
  • 장애복구 후 명령 실행
    http://localhost:8080/sentinel/set/key17 -> 저장
    http://localhost:8080/sentinel/get/key15 -> 조회
    http://localhost:8080/sentinel/get/key16 -> 조회
  • Application Log
    01:55:52.291 : Received [GET /sentinel/set/key17 HTTP/1.1
    01:55:52.301 : Completed 200 OK
    01:56:17.417 : Received [GET /sentinel/get/key15 HTTP/1.1
    01:56:17.425 : Completed 200 OK
    01:56:22.248 : Received [GET /sentinel/get/key16 HTTP/1.1
    01:56:22.256 : Completed 200 OK
  • Redis_2 (6001)
    1436:M 01:55:52.529 - 192.168.56.1:8870: SET key17 value-key17
    1436:M 01:56:17.655 - 192.168.56.1:8870: GET key15
    1436:M 01:56:22.486 - 192.168.56.1:8870: GET key16

Redis_1 다시 시작

Redis_1 다시 시작하면 센티널이 복제로 변경합니다.
센티널은 다운된 레디스 서버 주소로 1초에 한번씩 PING을 보내서 다시 시작했는지 확인합니다.
다운되었던 레디스 서버가 다시 시작하면 처음에는 마스터로 시작합니다.
센티널은 이를 감지해서 복제서버로 만들고, 기존 클라이언트 연결을 끊습니다.

  • Redis_1 (6000), Sentinel_1 (7000) 다시 시작 -> 01:57:13.402
    Redis_1 (6000) Log
    1922:M 01:57:13.525 - 192.168.56.102:54746: AUTH redisgate -> ① Sentinel_2 (7001)에서 6000에 접속
    1922:M 01:57:13.525 - 192.168.56.102:54746: CLIENT SETNAME sentinel-98b0a8c8-cmd
    1922:M 01:57:13.525 - 192.168.56.102:54746: PING
    1922:M 01:57:13.525 - 192.168.56.102:54746: INFO
  • Sentinel_2 (7001) Log
    1460:X 01:57:13.797 - -role-change slave 192.168.56.102:6000 @ mymaster new reported role is master -> ① 6000번이 마스터로 시작
    1460:X 01:57:13.897 # -sdown slave 192.168.56.102:6000 @ mymaster -> 6000번 레디스 다운 해제
    1460:X 01:57:13.974 # -sdown sentinel 192.168.56.102 7000 @ mymaster -> 7000번 센티널 다운 해제
    1460:X 01:57:23.834 - +role-change slave 192.168.56.102:6000 @ mymaster new reported role is slave -> ② 센티널이 10초후 복제로 변경
  • Redis_1 (6000) Log
    1922:M 01:57:23.588 - 192.168.56.102:54746: MULTI -> ② Sentinel_2 (7001) 명령 시작
    1922:M 01:57:23.588 - 192.168.56.102:54746: SLAVEOF 192.168.56.102 6001 -> 복제로 변경
    1922:M 01:57:23.588 - 192.168.56.102:54746: CONFIG REWRITE -> redis.conf 다시 쓰기
    1922:M 01:57:23.588 - 192.168.56.102:54746: CLIENT KILL TYPE normal -> 일반 연결 모두 끊음
    1922:M 01:57:23.588 - 192.168.56.102:54746: CLIENT KILL TYPE pubsub -> Pub/Sub 연결 모두 끊음
    1922:M 01:57:23.588 - 192.168.56.102:54746: EXEC -> 명령 실행

    1922:S 01:57:23.588 * Connecting to MASTER 192.168.56.102:6001 -> ③ 새 마스터에 접속해서 데이터를 받아옴
    1922:S 01:57:23.588 * MASTER <-> REPLICA sync started
    1922:S 01:57:23.588 * REPLICAOF 192.168.56.102:6001 enabled
  • Redis_1 (6000) 시작 후 명령 실행
    http://localhost:8080/sentinel/set/key18 -> 저장
    http://localhost:8080/sentinel/get/key18 -> 조회
  • Application Log
    01:57:40.650 : Received [GET /sentinel/set/key18 HTTP/1.1
    01:57:40.657 : [192.168.56.1:8870 -> 192.168.56.102:6001] Completing command [type=SET, output=OK]
    01:57:40.659 : Completed 200 OK
    01:57:50.090 : Received [GET /sentinel/get/key18 HTTP/1.1
    01:57:50.097 : [192.168.56.1:8870 -> 192.168.56.102:6001] Completing command [type=GET, output=[B@1bee8232]]
    01:57:50.099 : Completed 200 OK
  • Redis_2 (6001)
    1436:M 01:57:23.611 * Replica 192.168.56.102:6000 asks for synchronization -> ③ 6000번 다시 시작, 데이터 동기화 요청 받음
    1436:M 01:57:40.892 - 192.168.56.1:8870: SET key18 value-key18
    1436:M 01:57:50.332 - 192.168.56.1:8870: GET key18

센티널 구성과 Pub/Sub과의 관계

센티널에서 Pub/Sub 사용은 아무 문제없이 잘됩니다. 장애복구(failover) 시 Spring에서 이전 마스터에 실행(등록)했던 subscribe 명령을 기억했다가 새 마스터에 자동으로 subscribe 명령을 실행합니다.
예) 이전 마스터에 ch01 ~ ch05까지 5번 SUBSCRIBE를 했다면 새 마스터에 이렇게 실행합니다.
    SUBSCRIBE ch05 ch03 ch04 ch01 ch02
그러므로 새 마스터에 다시 subscribe 명령을 실행할 필요 없습니다.
Pub/Sub에 대한 소개와 사용법은 여기를 보세요.

센티널 서버 password 설정

아래와 같이 RedisSentinelConfiguration에 센티널 password를 사용할 수 있습니다.


Lettuce Spring Project

Spring Project 생성

Java Spring Project 생성 URL: start.spring.io

  • Project: Gradle-Groovy
  • Language: Java
  • Spring Boot: 3.1.8
  • Project Metadata
    • Group: com.redisgate
    • Artifact: SentinelSpring -> Project 명칭, 이 이름으로 압축파일이 생긴다.
    • Name: Main -> Main Class Name
    • Description: Redis Sentinel Spring Application -> 설명
    • Package name: com.redisgate.redis -> Package Name
    • Packaging: Jar
    • Java: 17 -> Java 버전 선택
  • Dependencies에서 [ADD … CTRL + B] 버튼을 클릭해서 아래 3개를 추가합니다.
    • Spring Web
    • Lombok
    • Spring Data Redis(Access+Driver)
  • 마지막으로 [GENERATE CTRL + ⏎ ] 버튼 클릭해서 압축 파일(zip)을 다운받는다.
  • 스프링부트3.x 는 자바17 이상, 스프링부트 2.x는 자바11을 사용합니다.

이 프로젝트에 사용된 Spring, Lettuce, Redis 버전
Spring Data Redis는 Lettuce를 기반으로 합니다.

  • Spring-Boot: 3.1.8, Spring: 6.0.16
  • Lettuce-6.2.7
  • Redis-7.2.3

Class/File 구성

  • Main Class: 메인 클래스
  • application.properties: 센티널/레디스 서버 연결 값들
  • RedisProps class: application.properties 파일을 읽어오는 클래스
  • RedisConn class: 센티널/레디스 서버에 연결하는 클래스
  • RedisController class: 센티널/레디스 명령 테스트 클래스

Main Class

application.properties

RedisProps class

application.properties 파일을 읽어오는 클래스.

  • getReadFrom()은 readFrom 문자열을 ReadFrom 값으로 변경합니다.
    readFrom은 자동을 읽어오지 않으므로 추가로 설정해야 합니다. readFrom을 설정하지 않는다면 이 클래스는 필요하지 않습니다.

RedisConn class

LettuceConnectionFactory를 생성해서 레디스 서버에 연결합니다. readFrom을 설정하기 위해서 LettuceConnectionFactory를 수동(manual)으로 생성했습니다. readFrom을 설정하지 않는다면 이 클래스는 필요하지 않습니다.

RedisController class

센티널, 레디스 명령 테스트 클래스

  1. SET 키-값 저장
    http://localhost:8080/set/key01
  2. GET 조회, 복제에서 읽는지 readFrom 설정 확인
    http://localhost:8080/get/key01
    GET 명령이 복제서버에 실행되는 것을 확인하기 위해서 아래와 같이 redis-cli를 실행 후 위 get 명령을 실행합니다.
    $ bin/redis-cli -p 6001 -a redisgate --stat
  3. PING: 마스터에서 실행
    http://localhost:8080/ping


<< Master/Replica Sentinel Cluster >>

Email 답글이 올라오면 이메일로 알려드리겠습니다.

혹시 처음이세요?
레디스게이트에는 레디스에 대한 많은 정보가 있습니다.
레디스 소개, 명령어, SQL, 클라이언트, 서버, 센티널, 클러스터 등이 있습니다.
혹시 필요한 정보를 찾기 어려우시면 redisgate@gmail.com로 메일 주세요.
제가 찾아서 알려드리겠습니다.
 
close
IP를 기반으로 보여집니다.