HA(High availability)와 Sentinel
Redis 서버를 한 대로만 서비스할 경우 단순히 캐시 용도로만 사용하거나 클라이언트에서 해싱해서 사용한다면 장비에 이상이 발생해 데이터 일부가 사라진다고 하더라도 문제가 없지만 유실되어서는 안 되는 데이터에 경우에는 큰 문제가 발생할 수 있습니다.
이러한 문제들을 방지하지 위해 Redis 서비스를 운영할 때는 기본적으로 마스터/슬레이브 형태로 서비스하는데,
Redis 2.6 버전부터 슬레이브의 slave-read-only가 yes로 설정되어 있어서 쓰기 요청은 모두 실패하므로 슬레이브의 전환 기능은 꼭 필필요합니다.
또한 장애가 발생하더라도 제대로 운영하려면 마스터의 장애를 정확히 판별하고 슬레이브를 마스터로 승격해야 시켜야 합니다.
그다음 해당 작업 내용을 클라이언트에게 통지해야 합니다.
위와 같은 작업을 하는 서비스를 직접 구현하기는 쉽지 않으며 귀찮은 일인데 Redis에서는 기본적으로 Sentinel이라는 데몬을 이용하여 해당 작업들을 처리할 수 있습니다.
다만, Sentinel에서는 이미 장애가 발생한 마스터에 접속된 클라이언트를 알 수 없으므로 해당 알림을 원하는 클라이언트는 Redis Pub/Sub으로 Sentinel에 등록해야 합니다.
Sentinel은 어떻게 장애를 판별할까?
기본적으로 PING 명령의 응답을 이용해서 판단합니다.
응답이 없다고 해서 해당 서버가 장애라고 판단하여 마스터를 변경하지는 않습니다.
Sentinel은 SDOWN과 ODOWN이라는 두 가지 상태로 장애를 인식합니다.
SDOWN
"Subjectively Down"의 약어로 주관적인 장애 상태를 의미합니다.
Sentinel 서버 하나가 장애라고 인식되면 SDOWN 상태가 됩니다.
SDOWN을 판별하기 위해서 sentinelCheckSubjectivelyDown 함수를 이용하는데 해당 서버의 last_avail_time과 현재 시간과의 차이가 설정된 down_after_period 값보다 크면 SDOWN 상태가 됩니다.
ODOWN
"Objectively Down"의 약어로 객관적인 장애 상태를 뜻합니다.
Sentinel 설정을 보면 장애 발생 시 Failover를 위한 정족수(의사를 진행하고 결정하는 데 필요한 최소한의 수) 설정이 있는데 이 값 이상의 서버가 장애라고 판단하면 해당 서버는 ODOWN 상태가 됩니다.
ODOWN을 판별하기 위해서 sentinelCheckObejctivelyDown 함수를 이용합니다.
서버가 SDOWN 상태일 때만 정족수를 체크하며, 정족수에 도달하면 ODOWN 상태로 설정됩니다.
정족수를 Sentinel 장비 수 보다 높은 값으로 설정하게 되면 슬레이브 마스터 승격이 되지 않기 때문에
보통 Sentinel 장비 수를 홀수로 두고 그 과반수를 정족수로 설정합니다.
Sentinel은 마스터로 승격할 슬레이브를 어떻게 선택할까?
새로운 마스터는 ODOWN 되었을 때 선출하는데, sentinelSelectSlave 함수를 통해 결정됩니다.
결정 우선수위는 다음과 같습니다.
- SDOWN, ODOWN, DISCONNECT 상태인 슬레이브는 제외
- last_avail_time이 info_validity_time보다 작으면 제외
- info_refresh 값이 info_validity_time보다 작으면 제외
- master_link_down_time이 max_master_down_time보다 크면 제외
- 남은 후보들 중에서 slave_priority가 높은 순으로 선택되고, slave_priority가 같으면 runid를 비교해서 가장 큰 값이 마스터로 선택
(slave_priority가 0이면 제외)
절대로 승격되지 않는 슬레이가 필요하면 redis.conf에서 slave_priority 값을 0으로 설정하면 됩니다.
단, 이 설정을 한 경우 슬레이브가 한 대만 존재할 경우 마스터로 승격되지 않으니 주의해야 합니다.
Docker Compose를 이용한 Redis Sentinel 구성하기
4. Redis 복제 글에서 작성한 docker compose에 이어서 작성하도록 하겠습니다.
마찬가지로 이미지는 bitnami/redis-sentinel를 사용하였습니다.
$ sudo vim docker-compose.yml
version: '2'
networks:
redis-network:
driver: bridge
external: true
services:
redis-master:
image: 'bitnami/redis:latest'
container_name: 'redis-master'
environment:
- REDIS_REPLICATION_MODE=master
- ALLOW_EMPTY_PASSWORD=yes
networks:
- redis-network
ports:
- 6379:6379
redis-slave-1:
image: 'bitnami/redis:latest'
container_name: 'redis-slave-1'
environment:
- REDIS_REPLICATION_MODE=slave
- REDIS_MASTER_HOST=redis-master
- ALLOW_EMPTY_PASSWORD=yes
ports:
- 6380:6379
networks:
- redis-network
depends_on:
- redis-master
redis-slave-2:
image: 'bitnami/redis:latest'
container_name: 'redis-slave-2'
environment:
- REDIS_REPLICATION_MODE=slave
- REDIS_MASTER_HOST=redis-master
- ALLOW_EMPTY_PASSWORD=yes
ports:
- 6381:6379
networks:
- redis-network
depends_on:
- redis-master
redis-sentinel-1:
image: 'bitnami/redis-sentinel:latest'
container_name: 'redis-sentinel-1'
environment:
- REDIS_SENTINEL_DOWN_AFTER_MILLISECONDS=3000
- REDIS_MASTER_HOST=redis-master
- REDIS_MASTER_PORT_NUMBER=6379
- REDIS_MASTER_SET=master
- REDIS_SENTINEL_QUORUM=2
networks:
- redis-network
ports:
- 26379:26379
depends_on:
- redis-master
- redis-slave-1
- redis-slave-2
redis-sentinel-2:
image: 'bitnami/redis-sentinel:latest'
container_name: 'redis-sentinel-2'
environment:
- REDIS_SENTINEL_DOWN_AFTER_MILLISECONDS=3000
- REDIS_MASTER_HOST=redis-master
- REDIS_MASTER_PORT_NUMBER=6379
- REDIS_MASTER_SET=master
- REDIS_SENTINEL_QUORUM=2
networks:
- redis-network
ports:
- 26380:26379
depends_on:
- redis-master
- redis-slave-1
- redis-slave-2
redis-sentinel-3:
image: 'bitnami/redis-sentinel:latest'
container_name: 'redis-sentinel-3'
environment:
- REDIS_SENTINEL_DOWN_AFTER_MILLISECONDS=3000
- REDIS_MASTER_HOST=redis-master
- REDIS_MASTER_PORT_NUMBER=6379
- REDIS_MASTER_SET=master
- REDIS_SENTINEL_QUORUM=2
networks:
- redis-network
ports:
- 26381:26379
depends_on:
- redis-master
- redis-slave-1
- redis-slave-2
총 3개의 Sentinel로 구성하였고 환경변수들의 의미는 아래와 같습니다.
- REDIS_SENTINEL_DOWN_AFTER_MILLISECONDS : Sentinel이 마스터의 장애여부를 판단하는 기준 시간
- REDIS_MASTER_HOST : 마스터 호스트명
- REDIS_MASTER_PORT_NUMBER : 마스터 포트번호
- REDIS_MASTER_SET : Sentinel에서 설정할 마스터 이름
- REDIS_SENTINEL_QUORUM : Failover를 판단하기 위해 마스터의 장애를 동의하는 Sentinel 최소 개수
더 자세한 환경변수는 bitnami/redis-sentinel dockerhub에서 확인이 가능합니다.
$ docker-compose up -d
$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
ba9d6ca97127 bitnami/redis:latest "/opt/bitnami/script…" 5 seconds ago Up 4 seconds 0.0.0.0:6379->6379/tcp redis-master
4f9c13544eb3 bitnami/redis:latest "/opt/bitnami/script…" 5 seconds ago Up 4 seconds 0.0.0.0:6380->6379/tcp redis-slave-1
79d7985986f4 bitnami/redis:latest "/opt/bitnami/script…" 5 seconds ago Up 4 seconds 0.0.0.0:6381->6379/tcp redis-slave-2
be229b87abc6 bitnami/redis-sentinel:latest "/opt/bitnami/script…" 5 seconds ago Up 4 seconds 0.0.0.0:26379->26379/tcp redis-sentinel-1
772a1a2c1d44 bitnami/redis-sentinel:latest "/opt/bitnami/script…" 5 seconds ago Up 4 seconds 0.0.0.0:26380->26379/tcp redis-sentinel-2
9add4e51ec3e bitnami/redis-sentinel:latest "/opt/bitnami/script…" 5 seconds ago Up 4 seconds 0.0.0.0:26381->26379/tcp redis-sentinel-3
docker compose를 통해 컨테이너를 생성하고 Sentinel 컨테이너에 bash로 접속해 redis-cli을 실행합니다.
psubscribe 명령을 통해 정보를 받을 수 있습니다.
$ docker exec -i -t redis-sentinel-1 bash
I have no name!@273574a5dcc2:/$ redis-cli -p 26379
127.0.0.1:26379> psubscribe *
1) "psubscribe"
2) "*"
3) (integer) 1
Reading messages ...
이제 새로운 터미널을 열어 redis-master 컨테이너를 종료시켜 보겠습니다.
$ docker stop redis-master
다시 Sentinel 컨테이너에 돌아와 보면 새로운 메시지들이 출력되어 있습니다.
127.0.0.1:26379> psubscribe *
1) "psubscribe"
2) "*"
3) (integer) 1
1) "pmessage"
2) "*"
3) "+sdown"
4) "master master 172.22.0.2 6379"
1) "pmessage"
2) "*"
3) "+new-epoch"
4) "1"
1) "pmessage"
2) "*"
3) "+vote-for-leader"
4) "9ce6ea1a0499fc99fa1bb14cf52d2f5d0e2d257f 1"
1) "pmessage"
2) "*"
3) "+odown"
4) "master master 172.22.0.2 6379 #quorum 3/2"
1) "pmessage"
2) "*"
3) "+config-update-from"
4) "sentinel 9ce6ea1a0499fc99fa1bb14cf52d2f5d0e2d257f 172.22.0.5 26379 @ master 172.22.0.2 6379"
1) "pmessage"
2) "*"
3) "+switch-master"
4) "master 172.22.0.2 6379 172.22.0.4 6379"
1) "pmessage"
2) "*"
3) "+slave"
4) "slave 172.22.0.3:6379 172.22.0.3 6379 @ master 172.22.0.4 6379"
1) "pmessage"
2) "*"
3) "+slave"
4) "slave :6379 6379 @ master 172.22.0.4 6379"
1) "pmessage"
2) "*"
3) "+sdown"
4) "slave :6379 6379 @ master 172.22.0.4 6379"
여기서 +switch-maste를 보면 "master 172.22.0.2 6379 172.22.0.4 6379"를 확인할 수 있는데
이는 "{클러스터명} {이전 마스터 IP} {이전 마스터 Port} {새 마스터 IP} {새 마스터 Port}"를 의미합니다.
redis-slave-2(172.22.0.4 6379) 컨테이너가 슬레이스에서 마스터로 failover 된 것을 확인할 수 있습니다.
이제 다시 redis-master 컨테이너를 살려보겠습니다.
$ docker start redis-master
Sentinel 컨테이너에 돌아와 보면 다시 redis-master 컨테이너(172.22.0.2 6379)가 마스터로 롤 체인지 된 것을 확인할 수 있습니다.
...
1) "pmessage"
2) "*"
3) "-role-change"
4) "slave :6379 172.22.0.2 6379 @ master 172.22.0.4 6379 new reported role is master"
1) "pmessage"
2) "*"
3) "-sdown"
4) "slave :6379 172.22.0.2 6379 @ master 172.22.0.4 6379"
1) "pmessage"
2) "*"
3) "+role-change"
4) "slave :6379 172.22.0.2 6379 @ master 172.22.0.4 6379 new reported role is slave"
1) "pmessage"
2) "*"
3) "+slave"
4) "slave 172.22.0.2:6379 172.22.0.2 6379 @ master 172.22.0.4 6379"
Sentinel은 다른 노드를 어떻게 발견할까?
Sentinel은 info 명령어를 이용해 마스터 노드나 슬레이브 노드를 찾습니다.
info 명령에 각 마스터와 슬레이브 정보가 있어, 슬레이브 노드라면 마스터 노드의 주소로 변경해서 처리하게 되고, 마스터 노드라면 슬리이브 노드 주소를 가져와서 해당 클러스터를 위한 슬레이브 목록을 만듭니다.
그럼 Sentinel 노드끼리는 어떻게 알 수 있을까?
Sentinel은 현재 마스터 노드에 "SENTINEL_HELLO_CHANNEL(__sentinel__:hello)"라는 Pubscribe 채널을 만드는데, 새로 접속한 Sentinel은 해당 채널에 hello 메시지를 전달하며 이를 통해서 서로의 존재를 알게 됩니다.
Hello Pub/Sub을 이용해서 받은 주소가 현재 자신이 가지고 있지 않은 값이면 새로운 Sentinel에 등록합니다.
'Redis' 카테고리의 다른 글
Redis를 사용해 API Response 캐시화하기(with. Spring Boot) - 1 (0) | 2024.07.15 |
---|---|
6. Spring Redis 분산락(Distribute Lock)을 활용한 동시성 처리 (1) | 2024.03.12 |
4. Redis 복제 (0) | 2024.02.15 |
3. Redis 운영과 관리 (0) | 2023.10.01 |
2. Docker로 Redis 실행하기 (0) | 2023.09.30 |