Redis의 FailOver상황을 대비해 replication을 구성하는 경우가 많습니다. 이때 Sentinel 또는 Cluster를 구축하지 않으면 일시적인 FailOver만 극복할 수 있기 때문에 장애가 지속될 경우 데이터가 유실됩니다.
해당 글에서 Redis의 replication들을 고가용성으로 사용하기 위한 Redis-Sentinel에 대해 알아보도록 하겠습니다.
Replication
데이터의 유실을 막거나 트래픽 분산을 위해서 replication을 구성하는 경우가 많은데 replication을 구성하기 위해서는 master(read/write)와 slave(read)가 있어야 합니다.
slave는 master의 데이터를 미러링 하고 있는 read전용 replication입니다
Redis replication들은 아래 특징을 갖습니다.
- master와 slave가 연결된 상태라면 master에서 이루어진 write작업이 slave에 복제된다.
- slave의 데이터 복제는 비동기로 이루어진다. 해당 작업은 non-blocking이기 때문에 복제가 master의 성능에 영향을 미치지 않는다.
- master는 여러 개의 slave을 가질 수 있습니다.
- slave끼리의 연결을 통해 cascading 같은 구조를 만들 수 있습니다.
- master와 slave의 연결이 끊어졌던 경우 데이터의 일관성을 위해 동기화를 시도한다.
실제로는 master와 slave를 서로 다른 서버에서 돌리는 게 일반적이지만 해당 글에서는 하나의 노드 위에서 돌리도록 하겠습니다.
최근에는
master/slave
보다main/replica
또는primary/replica
라는 용어가 쓰이기도 합니다.
HA-High Availability(고가용성)
Redis의 master가 장애에 빠져도 slave들이 존재하기 때문에 메모리 내에 저장되어 있던 데이터들은 유실되지 않습니다.하지만 master가 금방 되살아나지 않는다면 master가 죽어있던 기간 동안의 데이터는 유실될 수 있습니다.
이러한 장애를 피하기 위해서 존재하는 것이 Sentinel입니다.
Sentinel을 사용한다면 master의 FailOver를 파악해 새로운 master를 선출해서 Redis서버의 가용성을 높일 수 있습니다.
Sentinel
Redis-Sentinel은 Redis서버의 상태를 감시하고 있습니다. 만약 Master에 장애가 발생할 경우 Slave 중 1개를 Master로 승격시켜 FailOver에 대처합니다.
Sentinel을 구축할 때 아래 사항을 지켜야 합니다.
- Sentinel의 개수는 꼭 1개 이상의 홀수개로 한다
- quorum값은 Sentinel 개수를 넘지 않는다
quorum은 몇개의 Sentinel이 master를 SDown이라고 판단할 때 ODown 처리할 것인지에 대한 설정값입니다.
이해하기 위해서 sentinel의 원리를 알아보도록 합시다.
우선 각각의 Sentinel은 독립적으로 redis 서버를 감시하고 있습니다.
각각의 sentinel이 내리는 판단을 SDown(subjective down, 주관적인 판단)
이라고 합니다.
SDown의 총합이 quorum
의 수보다 커지게 되면 ODown(objective down, 객관적인 판단)
을 선언합니다.
ODown선언을 하게 되면 slave 중에 하나를 master로 승격시켜 FailOver를 극복하게 됩니다.
이때 SDown의 원인이 Sentinel자체에 있거나 일시적인 네트워크 문제일 수도 있기 때문에 Sentinel의 개수는 한 개 이상이어야 합니다.
만약 quorum값이 1이고 1개의 Sentinel만 사용하고 있을 때 Redis가 아닌 Sentinel의 장애로 또는 네트워크 장애로 인해서 SDown을 선언하게 되면 Redis는 정상이지만 ODown선언이 이루어집니다.
하지만 quorum값이 2이고 3개의 Sentinel을 사용 중이라면 한 개의 Sentinel이 제기능을 하지 못해 SDown을 선언했어도 남은 2개의 Sentinel이 SDown을 선언하지 않기 때문에 ODown이 일어나지 않습니다.
즉 새로운 master가 선출되는 과정은 다수결의 원칙
을 따르고 있습니다. Sentinel의 개수가 짝수라면 다수결의 원칙이 이루어지지 않기 때문에 1개 이상의 홀수로 생성해야 합니다.
다수결의 원칙을 사용 중인데 quorum의 숫자가 Sentinel보다 크다면 영원히 ODown이 일어날 수 없게 되므로 주의해야 합니다.
HAProxy
Auto FailOver 시스템인 Sentinel에 대해서 알아보았습니다.하지만 client입장에서 생각해보면 아직 사용성 측면에서 부족한 점이 존재합니다.
client는 기존에 write을 위해 7000번 포트를 사용 중이었으나 sentinel에 의해서 7001번 포트가 master가 되었습니다. 이때 client는 해당 변경사항을 알 수가 없습니다.
이를 해결하기 위해 모든 connection마다 redis info을 체크하는 것은 너무 비효율적인 일입니다. client는 어떤 포트가 master인지 매번 확인하기보다는 하나의 포트를 통해서 지속적으로 장애 없이 write 하고 싶습니다.
haproxy를 통해 해당 문제를 해결하도록 하겠습니다.
haproxy는 redis와는 무관한 서비스이며 L4/L7의 기능을 제공하는 로드밸런서입니다.
haproxy 로드밸런싱 기능을 통해서 5000번 포트로 들어오는 요청은 항상 master로 가게 하고 5001번으로 들어오는 요청은 항상 slave로 가도록 합니다.
이를 구현하기 위해서 redis info에서 replication section에 존재하는 role 정보를 확인하고 조건에 맞게 tcp요청을 분산합니다.
구현
소스코드는 여기를 통해 확인하실 수 있습니다.
Redis, Sentinel, HAProxy 모두 docker image로 구현했습니다.
Redis-Sentinel
bitnami/redis
의 더 자세한 옵션들은 여기를 참고하세요.
# docker-compose.yml
version: "3"
services:
master:
image: bitnami/redis:5.0-debian-9
environment:
REDIS_REPLICATION_MODE: master
ALLOW_EMPTY_PASSWORD: "yes"
networks:
- redis_network
restart: always
slave-1:
image: bitnami/redis:5.0-debian-9
environment:
REDIS_REPLICATION_MODE: slave
REDIS_MASTER_HOST: master
ALLOW_EMPTY_PASSWORD: "yes"
networks:
- redis_network
slave-2:
image: bitnami/redis:5.0-debian-9
environment:
REDIS_REPLICATION_MODE: slave
REDIS_MASTER_HOST: master
ALLOW_EMPTY_PASSWORD: "yes"
networks:
- redis_network
slave-3:
image: bitnami/redis:5.0-debian-9
environment:
REDIS_REPLICATION_MODE: slave
REDIS_MASTER_HOST: master
ALLOW_EMPTY_PASSWORD: "yes"
networks:
- redis_network
sentinel-1:
image: bitnami/redis-sentinel:5.0-debian-9
environment:
REDIS_MASTER_HOST: master
REDIS_SENTINEL_DOWN_AFTER_MILLISECONDS: 5000
REDIS_SENTINEL_FAILOVER_TIMEOUT: 5000
networks:
- redis_network
sentinel-2:
image: bitnami/redis-sentinel:5.0-debian-9
environment:
REDIS_MASTER_HOST: master
REDIS_SENTINEL_DOWN_AFTER_MILLISECONDS: 5000
REDIS_SENTINEL_FAILOVER_TIMEOUT: 5000
networks:
- redis_network
sentinel-3:
image: bitnami/redis-sentinel:5.0-debian-9
environment:
REDIS_MASTER_HOST: master
REDIS_SENTINEL_DOWN_AFTER_MILLISECONDS: 5000
REDIS_SENTINEL_FAILOVER_TIMEOUT: 5000
networks:
- redis_network
haprxoy:
image: rafpe/docker-haproxy-rsyslog
ports:
- 80:80
- 5000-5001:5000-5001
volumes:
- ${PWD}/haproxy.cfg:/usr/local/etc/haproxy/haproxy.cfg
networks:
- redis_network
networks:
redis_network:
driver: bridge
HAProxy
haproxy의 log을 확인하기 위해서 rafpe/docker-haproxy-rsyslog image를 사용했습니다
# haproxy.cfg
global
log 127.0.0.1 local2
pidfile /var/run/haproxy.pid
defaults
mode tcp
log global
option tcplog
balance source
timeout connect 5s
timeout server 1m
timeout client 1m
timeout tunnel 365d
listen stats
mode http
bind :80
stats enable
stats hide-version
stats realm Haproxy\ Statistics
stats uri /haproxy_stats
frontend ft_redis_master
mode tcp
bind :5000
default_backend bk_redis_master
backend bk_redis_master
mode tcp
option tcp-check
tcp-check send PING\r\n
tcp-check expect string +PONG
tcp-check send info\ replication\r\n
tcp-check expect string role:master
tcp-check send QUIT\r\n
tcp-check expect string +OK
server redis-master master:6379 check inter 1s
server redis-slave-1 slave-1:6379 check inter 1s
server redis-slave-2 slave-2:6379 check inter 1s
server redis-slave-3 slave-3:6379 check inter 1s
frontend ft_redis_slave
mode tcp
bind :5001
default_backend bk_redis_slave
backend bk_redis_slave
mode tcp
option tcp-check
tcp-check send PING\r\n
tcp-check expect string +PONG
tcp-check send info\ replication\r\n
tcp-check expect string role:slave
tcp-check send QUIT\r\n
tcp-check expect string +OK
server redis-master master:6379 check inter 1s
server redis-slave-1 slave-1:6379 check inter 1s
server redis-slave-2 slave-2:6379 check inter 1s
server redis-slave-3 slave-3:6379 check inter 1s
확인
처음에는 redis-master가 master
로써의 역할을 하고 있는것을 볼 수 있습니다.
이때 redis-master를 중지시켜보도록 하겠습니다.
redis-master가 중지되어있기 때문에 redis-slave-1이 새롭게 master
역할을 맡게 되었습니다.
redis-master를 정상화 시키겠습니다.
redis-master가 정상으로 돌아왔지만 master
역할은 기존의 redis-slave-1이 하게 됩니다.
돌아온 redis-master는 slave
역할을 맡게 되었습니다.
redis-master와 redis-slave는 꼭master
나slave
의 역할을 맡는 것을 뜻하는 것이 아닙니다.
redis-master는 이름이 master인 container이며 redis-slave-n는 이름이 slave-n인 container들을 뜻합니다.
주의할 점
- HAProxy서버가 다운될 경우 Redis가 살아 있어도 사용할 수 없는 대참사가 일어나기 때문에 HAProxy를 여러 개 두고 L4 Switch로 사용해주는 것이 좋습니다.
- 완벽한 고가용성을 만들기 위해서는 sentinel master slave들을 모두 다른 node에서 생성해야 하며 장애가 일어났을 때 수동이 아닌 자동으로 재시작을 할 수 있도록 하는 것이 좋습니다.
- HAProxy를 사용할 때 redis에서 blocking 로직을 사용하게 된다면 timeout tunnel 설정을 365d(최댓값)으로 해야 blocking 되는 동안 timeout error가 일어나지 않습니다.
Reference
Docker를 사용해서 Redis Replication 구성하기 Feat.Sentinel