레디스 센티넬이란
레디스 센티넬과 레디스 클러스터는 모두 레디스의 안정성과 확장성을 높이기 위한 방법입니다. 하지만 두 방식은 역할과 기능이 다릅니다. 여기서 다룰 주제인 레디스 센티넬은 레디스 서버의 고가용성을 보장하는 시스템입니다. 즉, 서버가 죽더라도 서비스가 계속되도록 도와주는 시스템입니다.
특징
모니터링
센티넬은 마스터와 슬레이브 레디스 인스턴스를 지속적으로 감시하면서 서버가 정상적으로 동작하고 있는지 확인합니다. 이를 통해 서버가 다운되거나 문제가 생겼을 때 신속하게 대응할 수 있습니다.
- 마스터 서버: 데이터를 읽고 쓰는 역할을 하는 주 서버
- 슬레이브 서버: 마스터 서버의 데이터를 복제하는 서버로, 마스터 서버에 문제가 생겼을 때 대신 동작
센티넬은 주기적으로 PING 명령어를 보내 서버의 상태를 점검하고, 응답이 없거나 지연될 경우 서버가 다운된 것으로 간주합니다.
자동 장애 복구(failover)
마스터 서버가 다운되면, 센티넬은 이를 감지하고, 자동으로 슬레이브 서버 중 하나를 새로운 마스터로 승격시킵니다. 이 과정에서 클라이언트들이 계속해서 데이터를 읽고 쓸 수 있도록 슬레이브 서버를 마스터로 전환하는데 매우 빠르게 이루어집니다.
자동 장애 복구 시퀀스
- 새로운 마스터가 된 슬레이브에게 쓰기 권한을 부여
- 나머지 슬레이브 서버들은 새로운 마스터로부터 데이터를 복제하도록 재구성
- 클라이언트들이 새로운 마스터 서버로 연결할 수 있도록 알림
구성 관리
센티넬은 자동 장애 복구 이후, 새로운 마스터와 슬레이브 서버의 구성을 자동으로 관리합니다.
동작 원리
레디스 센티넬이 정상적으로 동작하려면 최소 3개의 센티넬 인스턴스가 필요합니다. 이를 통해 하나의 센티넬이 장애를 감지해도, 다수의 센티넬이 확인한 후에만 장애로 간주하고 자동 복구를 실행할 수 있습니다. 이 과정을 자세히 살펴보면 다음과 같습니다.
1) 모니터링 및 감지
센티넬은 주기적으로 마스터와 슬레이브 서버에 PING 명령어를 보내서 서버의 상태를 확인합니다. 만약 마스터가 응답하지 않으면, 센티넬은 이를 감지하고 다른 센티넬에게 이 사실을 알립니다.
2) 합의
한 개의 센티넬이 마스터의 장애를 감지했다고 바로 복구가되는 것은 아닙니다. 여러 개의 센티넬이 투표를 통해 마스터가 정말 장애 상태인지 확인합니다. 최소한 과반수의 센티넬이 마스터 장애에 동의해야 장애 복구 절차가 시작됩니다.
3) 자동 장애 복구
투표를 통해 장애가 확정되면, 센티넬은 슬레이브 서버 중에서 가장 적합한 서버를 선택하여 새로운 마스터로 승격시킵니다. 이때, 선택된 슬레이브는 마스터로서 쓰기 권한을 얻고, 나머지 슬레이브 서버들은 새 마스터에게서 데이터를 복제하도록 재구성됩니다.
4) 클라이언트 재연결
새로운 마스터가 선정되면, 센티넬은 클라이언트에게 새로운 마스터 서버 정보를 알려줍니다. 이를 통해 클라이언트는 자동으로 새로운 마스터 서버에 연결됩니다.
센티넬의 한계
레디스 센티넬은 높은 고가용성을 제공하지만, 클러스터에 비해 한계점이 존재합니다.
- 복잡한 분산시스템: 여러 개의 센티넬이 협력하여 장애 복구를 수행하기 때문에, 분산 시스템의 특성상 네트워크 문제나 지연이 발생할 수 있습니다.
- 데이터 분산 기능 부족: 센티넬은 고가용성을 제공하는 대신 데이터 분산(샤딩)은 제공하지 않습니다.
- 복구 시간: 마스터 장애 감지부터 새로운 마스터로의 전환까지 어느 정도 시간이 걸리며, 이 기간 동안 쓰기 작업이 불가능할 수 있습니다.
그렇다면 언제 센티넬을 사용하고 언제 레디스 클러스터를 사용해야할까요?
특징 | 센티넬 | 클러스터 |
주요 목적 | 고가용성 (서버 장애 시 자동 복구) | 확장성(데이터 분산 저장 및 대규모 데이터 처리) |
데이터 저장 방식 | 마스터-슬레이브 복제 기반 | 데이터 샤딩 (서버에 나누어 저장) |
서버 장애 처리 | 마스터 장애 시 슬레이브를 새로운 마스터로 자동 전환 | 마스터 장애 시 슬레이브가 새로운 마스터로 전환 |
데이터 확장성 | 단일 마스터 기반이므로 수직 확장에 가깝다. | 여러 서버로 데이터를 분산해 수평 확장이 가능 |
복잡성 | 상대적으로 간단한 설정과 운영 | 설정 및 운영이 더 복잡 |
스프링에서 적용해보기
docker-compose.yml
먼저 센티넬을 도커 컴포즈로 구현하겠습니다.
version: "3.8"
services:
redis-master:
image: redis:latest
container_name: redis-master
hostname: redis-master
ports:
- "6379:6379"
volumes:
- ./data/master:/data
command:
[
"redis-server",
"--appendonly",
"yes",
"--repl-diskless-load",
"on-empty-db",
"--replica-announce-ip",
"${HOST_IP}",
"--replica-announce-port",
"6379",
"--protected-mode",
"no"
]
networks:
redis-net:
ipv4_address: 172.21.0.3
redis-slave-1:
image: redis:latest
container_name: redis-slave-1
hostname: redis-slave-1
depends_on:
- redis-master
ports:
- "6380:6379"
volumes:
- ./data/slave1:/data
command:
[
"redis-server",
"--appendonly",
"yes",
"--replicaof",
"redis-master",
"6379",
"--repl-diskless-load",
"on-empty-db",
"--replica-announce-ip",
"${HOST_IP}",
"--replica-announce-port",
"6380",
"--protected-mode",
"no"
]
networks:
redis-net:
ipv4_address: 172.21.0.4
redis-slave-2:
image: redis:latest
container_name: redis-slave-2
hostname: redis-slave-2
depends_on:
- redis-master
ports:
- "6381:6379"
volumes:
- ./data/slave2:/data
command:
[
"redis-server",
"--appendonly",
"yes",
"--replicaof",
"redis-master",
"6379",
"--repl-diskless-load",
"on-empty-db",
"--replica-announce-ip",
"${HOST_IP}",
"--replica-announce-port",
"6381",
"--protected-mode",
"no"
]
networks:
redis-net:
ipv4_address: 172.21.0.5
sentinel-1:
image: redis:latest
container_name: sentinel-1
hostname: sentinel-1
depends_on:
- redis-master
ports:
- "26379:26379"
command: >
sh -c 'echo "bind 0.0.0.0" > /etc/sentinel.conf &&
echo "sentinel monitor mymaster ${HOST_IP} 6379 2" >> /etc/sentinel.conf &&
echo "sentinel resolve-hostnames yes" >> /etc/sentinel.conf &&
echo "sentinel down-after-milliseconds mymaster 10000" >> /etc/sentinel.conf &&
echo "sentinel failover-timeout mymaster 10000" >> /etc/sentinel.conf &&
echo "sentinel parallel-syncs mymaster 1" >> /etc/sentinel.conf &&
redis-sentinel /etc/sentinel.conf'
networks:
redis-net:
ipv4_address: 172.21.0.6
sentinel-2:
image: redis:latest
container_name: sentinel-2
hostname: sentinel-2
depends_on:
- redis-master
ports:
- "26380:26379"
command: >
sh -c 'echo "bind 0.0.0.0" > /etc/sentinel.conf &&
echo "sentinel monitor mymaster ${HOST_IP} 6379 2" >> /etc/sentinel.conf &&
echo "sentinel resolve-hostnames yes" >> /etc/sentinel.conf &&
echo "sentinel down-after-milliseconds mymaster 10000" >> /etc/sentinel.conf &&
echo "sentinel failover-timeout mymaster 10000" >> /etc/sentinel.conf &&
echo "sentinel parallel-syncs mymaster 1" >> /etc/sentinel.conf &&
redis-sentinel /etc/sentinel.conf'
networks:
redis-net:
ipv4_address: 172.21.0.7
sentinel-3:
image: redis:latest
container_name: sentinel-3
hostname: sentinel-3
depends_on:
- redis-master
ports:
- "26381:26379"
command: >
sh -c 'echo "bind 0.0.0.0" > /etc/sentinel.conf &&
echo "sentinel monitor mymaster ${HOST_IP} 6379 2" >> /etc/sentinel.conf &&
echo "sentinel resolve-hostnames yes" >> /etc/sentinel.conf &&
echo "sentinel down-after-milliseconds mymaster 10000" >> /etc/sentinel.conf &&
echo "sentinel failover-timeout mymaster 10000" >> /etc/sentinel.conf &&
echo "sentinel parallel-syncs mymaster 1" >> /etc/sentinel.conf &&
redis-sentinel /etc/sentinel.conf'
networks:
redis-net:
ipv4_address: 172.21.0.8
redisinsight:
image: redis/redisinsight:latest
container_name: redisinsight
ports:
- "5540:5540"
networks:
redis-net:
ipv4_address: 172.21.0.9
networks:
redis-net:
driver: bridge
ipam:
config:
- subnet: 172.21.0.0/16
커맨드를 이용해서 conf 파일을 구성해줍니다.
컴포즈파일과 동일한 폴더내에 .env 파일을 만든 후
REDIS_HOST_NAME={ipconfig로 해서 나온 IPV4 주소}
를 입력하고 저장해줍니다.
그후 docker-compose --env-file ./.env up 으로 실행을 해줍니다.
도커 데스크톱으로 컨테이너들을 보면 잘 실행된것을 볼 수 있습니다.
아무 슬레이브 노드에 들어가서 set을 해보면 READONLY여서 안되는 것을 볼 수 있습니다.
여기서 마스터노드를 죽여보겠습니다.
로그들이 뜨면서 마스터가 변경됩니다. 새로운 마스터가 된 6380 포트 redis-slave1 에 들어가보겠습니다.
정상적으로 쓰기 작업이 되는 것을 알 수 있습니다. 여기서 redis-slave-1 컨테이너를 종료하면 어떻게 될까요? 네, 맞습니다. redis-slave-2가 새로운 마스터가 되고 쓰기 작업이 가능해질 것 입니다.
종료를 해보면
6381포트가 마스터가 된 것을 볼 수 있습니다.
쓰기도 잘되는 것을 볼 수 있습니다.
이제 아까 종료시켰던 두 레디스 서버들을 다시 살리면 어떻게 될까요? 바로, 현재 마스터인 redis-slave-2의 복제본으로 즉, 슬레이브로 만들어집니다.
보이시나요? redis-master가 READONLY로 바뀌고 redis-master가 죽어있던 중 설정된 master: im-master가 정상적으로 조회가 됩니다.
스프링
이제 스프링에서 사용해봅시다. 싱글 레디스와 사용할 때처럼 구성파일을 만들어 줘야합니다.
@Configuration
public class RedisConfig {
@Value("${REDIS_HOST_NAME}")
private String localhost;
@Bean
public RedisConnectionFactory redisConnectionFactory(){
RedisSentinelConfiguration redisSentinelConfiguration = new RedisSentinelConfiguration()
.master("mymaster")
.sentinel(localhost, 26379)
.sentinel(localhost, 26380)
.sentinel(localhost, 26381);
return new LettuceConnectionFactory(redisSentinelConfiguration);
}
@Bean
public RedisTemplate<String, Object> redisTemplate(){
RedisTemplate<String, Object> redisTemplate = new RedisTemplate<>();
redisTemplate.setConnectionFactory(redisConnectionFactory());
redisTemplate.setKeySerializer(new StringRedisSerializer());
redisTemplate.setValueSerializer(new GenericJackson2JsonRedisSerializer());
return redisTemplate;
}
}
REDIS_HOST_NAME에는 센티넬에 주입한 환경변수값 그대로 사용하면 됩니다. 이와 같이 Config를 구성해주면 정상적으로 동작이 됩니다.
'DB' 카테고리의 다른 글
DAO와 DTO (0) | 2024.07.13 |
---|---|
Transaction (0) | 2024.07.12 |