https://kiru-dev-study.tistory.com/20
[DB] Redis Cluster
먼저 이글은 "실전 레디스" 라는 책을 읽고 나서 적은 글이다.24년 5월에 발행한 따끈따끈한 책이기도 했고 시중에 생각보다 redis에 대한 내용으로 책이 없었는데 마침 redis에 빠져서 공부를 하다
kiru-dev-study.tistory.com
먼저 Redis Cluster에 대한 이해도를 높이기 위해서 다음글을 먼저 보고 오는 것을 추천드린다.
그리고 Redis cluster를 Docker compose로 구성하기 위해서는 다음의 내용을 조금 아는 것이 좋다고 생각한다.
- 도커
- 도커 네트워크의 구성
- 도커로 실행할때 컨테이너 네임에 관한 DNS 방식
- 도커 컴포즈
- Redis Cluster의 구조
- Redis cluster의 설정 방법
일단 Redis의 단일노드부터 실행해 보자.
Redis의 노드의 구성요소들은 다음의 폴더에 conf 파일이 존재한다.
/etc/redis.conf
따라서 다음의 구성을 변경할 수 있도록 내가 설정하고 싶은 구성대로 파일을 구성해 놓아야 한다.
먼저 redis.conf 구성에서 우리가 건드리고 싶은 것들은 무엇이 있는가 확인을 해야 한다.
redis.conf 파일
port 7001
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 3000
appendonly yes
requirepass sopt
masterauth sopt
protected-mode no
bind 0.0.0.0
cluster-announce-ip 192.168.219.172
cluster-announce-port 7001
cluster-announce-bus-port 17001
먼저 port는 말 그대로 port다. 명시적인 구분을 위해서 port를 기본 포트인 6379 대신에 7001으로 구성했다.
나머지 포트들은 7002~7006으로 구성할 것이다. 참고로 나는 Linux 운영체제를 사용하기에 상관이 없지만 아마 맥을 쓰시는 분들이 7000 포트로 설정하게 되면 블루투스 관련 문제로 인해 7001부터 설정하는 것이 좋다.
다음으로 cluster-enabled는 클러스터로 노드를 사용할 건지의 여부고 당연히 yes로 둔다.
cluster-config-file은 nodes.conf로 그대로 사용하고 cluster-node-timeout의 시간을 정할 수 있다.
appendonly는 이제 redis에서 RDB와 AOF 방식으로 인메모리 기반이지만 비동기적으로 데이터를 저장하면서 영속성을 가질 수 있는데 이 부분은 레디스의 성능차이에 큰 영향을 줄 수 있으므로 후에 포스팅을 해보도록 하겠다.
requirepass 부분은 자신이 지정한 비밀번호로써 클라이언트에서 접근할 때 사용한다.
masterauth 부분은 slave노드에서 master에 접근할 때에도 비밀번호를 설정할 경우에 사용하는데 마찬가지로 똑같이 적었다.
protected-mode는 yes로 하면 redis cluster 자체적으로 외부통신을 사용하는 것이 아닌 내부통신을 사용한다.
이는 yes로 설정해도 상관없는 부분이다. 아마 yes로 하는 것이 내부통신을 함으로써 더 빠른 통신이 이루어지지만 혹시
redis cluster가 통신을 못한다면 이 부분에서 redis cluster끼리 통신이 안될 수도 있으므로 no로 설정하고 재시작시켜보자.
bind는 외부에서 오는 통신을 어디까지 허용할 건지의 문제다. bind는 127.0.0.1 등으로 로컬호스트나 여러 가지 ip주소를 적을 수 있는데 이 부분에서 redis 자체에서 내부오류가 많이 뜨기 때문에 대부분 0.0.0.0으로 모든 외부통신을 허용하게 한 후 사용하고 비밀번호를 사용한다.
cluster-announce-ip는 말 그대로 클러스터끼리 통 작을 할 때 나 자신의 ip주소를 알리는 동작이다. 이 부분은 docker container로 돌릴 시 도커 컨테이너 ip를 사용하거나, ifconfig나 ipconfig를 통해 나오는 자신의 ip주소를 적어주자.
cluster-announce-port는 클라이언트에서 접속할 때 사용하는 포트번호를 명시하는 부분이다. 이 포트번호로 클라이언트에서 접속을 다음과 같이 사용할 수 있다.
redis-cli -h {ip} -p 7001 -c
여기서 뒤에 -c옵션은 클러스터모드로 접속하는 것인데 -c옵션이 없다면 MOVED 리다이렉트 커멘드에서 오류가 날 수도 있으니 주의하자.
cluster-announce-bus-port는 클러스터 나부끼리 통신할때 어느 클러스터 버스 포트로 내 자신의 정보나, failover를 수행할 때 어느 곳으로 연결을 해야 하는지 지정한다. 간단히 말해 내부통신은 이 cluster-announce-bus-port를 사용한다.
이런 방식으로 redis의 클러스터노드 1개를 구성할 수 있으며 이제 이를 여러번 수행해야 한다.
그러나 Docker compose로 구현하게 되면 쉽게 구현을 할수 있기 때문에 다음으로 Docker Compose를 사용해서 구현해 보자.
redis-master-1:
container_name: redis-master-1
image: redis
command: redis-server /etc/redis.conf
volumes:
- ./redis_conf/redis-master-1.conf:/etc/redis.conf
restart: always
ports:
- 7001:7001
- 17001:17001
networks:
- redis-cluster
redis-slave-1:
container_name: redis-slave-1
image: redis
command: redis-server /etc/redis.conf
volumes:
- ./redis_conf/redis-slave-1.conf:/etc/redis.conf
restart: always
ports:
- 7004:7004
- 17004:17004
networks:
- redis-cluster
자 이제 Master와 Slave를 구성하는 Docker compose를 만들었다.
각각의 conatainer이름을 설정하고 이미지를 가져오고
command로 컨테이너 내부에서 redis-server를 실행시킨다.
그리고 위에서 작성한 redis_conf 파일을 컨테이너 내부에 redis.conf 파일과 동기화시켜주면 된다.
그리고 port는 명시적으로 구성을 함으로써 후에 관리측면에서 쉬울 수 있도록 구성해 주었다.
network도 ip를 구성해서 적은 경우 사용할 필요는 없지만 내부적으로 사용될 수 있는 부분이 있을 것 같아 적어주었다.
최종 Docker Compose 파일
services:
redis:
container_name: redis
image: redis
expose:
- 6379
ports:
- 6379:6379
redis-master-1:
container_name: redis-master-1
image: redis
command: redis-server /etc/redis.conf
volumes:
- ./redis_conf/redis-master-1.conf:/etc/redis.conf
restart: always
ports:
- 7001:7001
- 17001:17001
networks:
- redis-cluster
redis-master-2:
container_name: redis-master-2
image: redis
command: redis-server /etc/redis.conf
volumes:
- ./redis_conf/redis-master-2.conf:/etc/redis.conf
restart: always
ports:
- 7002:7002
- 17002:17002
networks:
- redis-cluster
redis-master-3:
container_name: redis-master-3
image: redis
command: redis-server /etc/redis.conf
volumes:
- ./redis_conf/redis-master-3.conf:/etc/redis.conf
restart: always
ports:
- 7003:7003
- 17003:17003
networks:
- redis-cluster
redis-slave-1:
container_name: redis-slave-1
image: redis
command: redis-server /etc/redis.conf
volumes:
- ./redis_conf/redis-slave-1.conf:/etc/redis.conf
restart: always
ports:
- 7004:7004
- 17004:17004
networks:
- redis-cluster
redis-slave-2:
container_name: redis-slave-2
image: redis
command: redis-server /etc/redis.conf
volumes:
- ./redis_conf/redis-slave-2.conf:/etc/redis.conf
restart: always
ports:
- 7005:7005
- 17005:17005
networks:
- redis-cluster
redis-slave-3:
container_name: redis-slave-3
image: redis
command: redis-server /etc/redis.conf
volumes:
- ./redis_conf/redis-slave-3.conf:/etc/redis.conf
restart: always
ports:
- 7006:7006
- 17006:17006
networks:
- redis-cluster
networks:
redis-cluster:
driver: bridge
최종적으로 완성된 docker compose 파일은 다음과 같이 구성하면 된다.
그렇다면 redis-cluster를 바로 구성할 수 있게 다음 shell 파일을 완성해 보자.
이때 필요한 건 현재의 ip주소, 아마 프로젝트의 서버에 올리는 경우 단일 서버를 사용할 경우 서버의 ip주소가 변경될 일은 자주 없을 것이라 생각된다. 아니라면 고정 ip주소를 사용하자.
위처럼 구성을 사용하게 되면 master, slave의 각 컨테이너 이름, port번호가 필요하다. 따라서
if command -v hostname >/dev/null 2>&1; then
if [[ "$OSTYPE" == "darwin"* ]]; then
REDIS_IP=$(ifconfig | grep 'inet ' | grep -v '127.0.0.1' | awk 'NR==1{print $2}') # macOS
else
REDIS_IP=$(hostname -I | awk '{print $1}') # Linux
fi
export REDISCLI_AUTH=$(echo $REDISCLI_AUTH)
else
echo "호스트 이름 명령어를 찾을 수 없습니다."
exit 1
fi
# 환경변수 설정
export REDIS_IP
export REDISCLI_AUTH
# Redis 설정 파일을 저장할 디렉토리 생성
mkdir -p redis_conf
다음과 같이 Redis IP를 가져올 수 있게 하자. 이때 REDISCLI_AUTH 같은 경우는 자신의 환경변수에 설정을 해줘야 하는데
vi ~/. bashrc 혹은 zsh를 사용하고 있다면 vi ~/. zshrc를 사용해서 파일을 열어준다음 맨 마지막에 내가 사용할 비밀번호를 환경변수로 등록해 준다.
그 후 source ~/. bashrc 혹은 zshrc를 사용해서 환경변수를 인식할 수 있도록 변경한다.
export REDIS_CLI="sopt"
그리고 다음으로 각 redis.conf파일을 만들어야 한다. 아래와 같이 자신에 맞게 구성하면 되는데 위의 코드를 그대로 썼다면 변경할 필요는 없다.
master_ports=(7001 7002 7003)
slave_ports=(7004 7005 7006)
master_types=("master" "master" "master")
slave_types=("slave" "slave" "slave")
# 각 Redis 노드에 대한 설정 파일 생성
for i in "${!master_ports[@]}"; do
cat <<EOL > redis_conf/redis-${master_types[$i]}-$((i + 1)).conf
port ${master_ports[$i]}
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 3000
appendonly yes
requirepass ${REDISCLI_AUTH}
masterauth ${REDISCLI_AUTH}
protected-mode no
bind 0.0.0.0
cluster-announce-ip ${REDIS_IP}
cluster-announce-port ${master_ports[$i]}
cluster-announce-bus-port $((17001 + ${master_ports[$i]} - 7001))
EOL
done
for i in "${!slave_ports[@]}"; do
cat <<EOL > redis_conf/redis-${slave_types[$i]}-$((i + 1)).conf
port ${slave_ports[$i]}
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 3000
appendonly yes
requirepass ${REDISCLI_AUTH}
masterauth ${REDISCLI_AUTH}
protected-mode no
bind 0.0.0.0
cluster-announce-ip ${REDIS_IP}
cluster-announce-port ${slave_ports[$i]}
cluster-announce-bus-port $((17001 + ${slave_ports[$i]} - 7001))
EOL
done
그리고 다음과 같이 각 컨테이너별로 redis.conf파일을 생성할 수 있게 구성해 준다.
그 후에 docker compose를 실행시켜서 이제 각 container가 동작할 수 있게 구성해야 한다.
docker compose -f docker-compose-local.yml up -d redis-master-1 redis-master-2 redis-master-3 redis-slave-1 redis-slave-2 redis-slave-3
echo "REDIS IP : ${REDIS_IP}"
# 모든 노드들이 완전히 실행될 때까지 대기
echo "Waiting for Redis nodes to be ready..."
# 클러스터 구성 명령어 실행
docker exec -it redis-master-1 redis-cli --cluster create \
redis-master-1:7001 \
redis-master-2:7002 \
redis-master-3:7003 \
redis-slave-1:7004 \
redis-slave-2:7005 \
redis-slave-3:7006 \
--cluster-replicas 1 -a ${REDISCLI_AUTH} --cluster-yes
# 성공 메시지 출력
echo "Redis cluster has been created successfully."
클러스터를 만들기 위해서는 각 클러스터로 들어가 clutser가 제공하는 명령어를 사용해야 하는데 다음 명령어는
node 1개로 접속해 cluster의 각정보를 입력하여 각 cluster끼리 연결되고, 각 노드마다 1개의 replica를 갖도록 하는 명령어이다.
최종적으로 파일은 다음과 같아진다. sh파일을 실행시키는 방법은 다양하다.
./start.sh 파일로 만들어도 되고 자기 맘대로 만들어도 되니 이를 실행시키자.
혹시 실행이 안된다면 chmod +x 파일이름. sh를 붙여서 실행 파일로 만들어주자.
혹은 IDE를 사용한다면 아마 자체적으로 sh파일을 실행시킬 수 있을 것이다.
Cluster 실행 파일
#!/bin/bash
# 현재 IP 주소 가져오기
if command -v hostname >/dev/null 2>&1; then
if [[ "$OSTYPE" == "darwin"* ]]; then
REDIS_IP=$(ifconfig | grep 'inet ' | grep -v '127.0.0.1' | awk 'NR==1{print $2}') # macOS
else
REDIS_IP=$(hostname -I | awk '{print $1}') # Linux
fi
export REDISCLI_AUTH=$(echo $REDISCLI_AUTH)
else
echo "호스트 이름 명령어를 찾을 수 없습니다."
exit 1
fi
# 환경변수 설정
export REDIS_IP
export REDISCLI_AUTH
# Redis 설정 파일을 저장할 디렉토리 생성
mkdir -p redis_conf
# 포트 번호 배열 설정
master_ports=(7001 7002 7003)
slave_ports=(7004 7005 7006)
master_types=("master" "master" "master")
slave_types=("slave" "slave" "slave")
# 각 Redis 노드에 대한 설정 파일 생성
for i in "${!master_ports[@]}"; do
cat <<EOL > redis_conf/redis-${master_types[$i]}-$((i + 1)).conf
port ${master_ports[$i]}
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 3000
appendonly yes
requirepass ${REDISCLI_AUTH}
masterauth ${REDISCLI_AUTH}
protected-mode no
bind 0.0.0.0
cluster-announce-ip ${REDIS_IP}
cluster-announce-port ${master_ports[$i]}
cluster-announce-bus-port $((17001 + ${master_ports[$i]} - 7001))
EOL
done
for i in "${!slave_ports[@]}"; do
cat <<EOL > redis_conf/redis-${slave_types[$i]}-$((i + 1)).conf
port ${slave_ports[$i]}
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 3000
appendonly yes
requirepass ${REDISCLI_AUTH}
masterauth ${REDISCLI_AUTH}
protected-mode no
bind 0.0.0.0
cluster-announce-ip ${REDIS_IP}
cluster-announce-port ${slave_ports[$i]}
cluster-announce-bus-port $((17001 + ${slave_ports[$i]} - 7001))
EOL
done
# Docker Compose로 Redis 노드들을 백그라운드에서 실행
docker compose up -d redis-master-1 redis-master-2 redis-master-3 redis-slave-1 redis-slave-2 redis-slave-3 redis
echo "REDIS IP : ${REDIS_IP}"
# 모든 노드들이 완전히 실행될 때까지 대기
echo "Waiting for Redis nodes to be ready..."
# 클러스터 구성 명령어 실행
docker exec -it redis-master-1 redis-cli --cluster create \
redis-master-1:7001 \
redis-master-2:7002 \
redis-master-3:7003 \
redis-slave-1:7004 \
redis-slave-2:7005 \
redis-slave-3:7006 \
--cluster-replicas 1 -a ${REDISCLI_AUTH} --cluster-yes
# 성공 메시지 출력
echo "Redis cluster has been created successfully."
그럼 아마 다음 내용이 나오게 될 텐데 다음을 확인해 보자.
[+] Running 9/9
✔ Network dateroad-server_redis-cluster Created 0.1s
✔ Network dateroad-server_default Created 0.1s
✔ Container redis-master-3 Started 0.4s
✔ Container redis Started 0.4s
✔ Container redis-master-1 Started 0.4s
✔ Container redis-master-2 Started 0.4s
✔ Container redis-slave-3 Started 0.4s
✔ Container redis-slave-2 Started 0.3s
✔ Container redis-slave-1 Started 0.4s
REDIS IP : 192.168.219.172
Waiting for Redis nodes to be ready...
Warning: Using a password with '-a' or '-u' option on the command line interface may not be safe.
>>> Performing hash slots allocation on 6 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
Adding replica redis-slave-2:7005 to redis-master-1:7001
Adding replica redis-slave-3:7006 to redis-master-2:7002
Adding replica redis-slave-1:7004 to redis-master-3:7003
M: f9a7de97e12c1576e76714c4874d15e84ff1c32a redis-master-1:7001
slots:[0-5460] (5461 slots) master
M: 0e4cd74e4a7ba60d2c74a418b2d91ae6bd0f7657 redis-master-2:7002
slots:[5461-10922] (5462 slots) master
M: ebde828a58be9210fd3f9c308f7301c46fc0ea5e redis-master-3:7003
slots:[10923-16383] (5461 slots) master
S: bfe872075ca8d3b5176f565d0c3c7c99769df72a redis-slave-1:7004
replicates ebde828a58be9210fd3f9c308f7301c46fc0ea5e
S: cdedab3ab6f6ca1d351f6cf5b59aac63ac41dd08 redis-slave-2:7005
replicates f9a7de97e12c1576e76714c4874d15e84ff1c32a
S: 2110bdcd514c7680486bb98c550d6bd9b56142f0 redis-slave-3:7006
replicates 0e4cd74e4a7ba60d2c74a418b2d91ae6bd0f7657
>>> Nodes configuration updated
>>> Assign a different config epoch to each node
>>> Sending CLUSTER MEET messages to join the cluster
Waiting for the cluster to join
>>> Performing Cluster Check (using node redis-master-1:7001)
M: f9a7de97e12c1576e76714c4874d15e84ff1c32a redis-master-1:7001
slots:[0-5460] (5461 slots) master
1 additional replica(s)
S: cdedab3ab6f6ca1d351f6cf5b59aac63ac41dd08 192.168.219.172:7005
slots: (0 slots) slave
replicates f9a7de97e12c1576e76714c4874d15e84ff1c32a
M: 0e4cd74e4a7ba60d2c74a418b2d91ae6bd0f7657 192.168.219.172:7002
slots:[5461-10922] (5462 slots) master
1 additional replica(s)
S: bfe872075ca8d3b5176f565d0c3c7c99769df72a 192.168.219.172:7004
slots: (0 slots) slave
replicates ebde828a58be9210fd3f9c308f7301c46fc0ea5e
S: 2110bdcd514c7680486bb98c550d6bd9b56142f0 192.168.219.172:7006
slots: (0 slots) slave
replicates 0e4cd74e4a7ba60d2c74a418b2d91ae6bd0f7657
M: ebde828a58be9210fd3f9c308f7301c46fc0ea5e 192.168.219.172:7003
slots:[10923-16383] (5461 slots) master
1 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
각 샤드마다 매핑된 slot의 정보와 모든 16384개의 slot이 mapping 되었다고 나온다.
혹시 Waiting for the cluster to join... 부분에서... 이 계속 뜨게 된다면 cluster끼리 연결을 못하거나 문제가 생겼을 수도 있으니
잘 확인해 보길 바란다.
이렇게 클러스터가 실행되면 redis-cli나 redis 클라이언트를 사용해 이에 접속할 수 있다.
다음 명령어로 cluster가 잘 mapping 되었는지 확인할 수 있는데 mapping이 되지 않았다면 fail?, fail, 혹은 node가 1개뿐인 상황이 발생할 수 있으니 제대로 확인을 해야 한다.
$ redis-cli -p 7001 cluster nodes
'DB' 카테고리의 다른 글
[DB] JAVA 개발자라면 알아야 하는 SQL Best Practice (3) | 2024.10.23 |
---|---|
[DB] Redis Cluster (5) | 2024.10.19 |
[DataBase] 트랜잭션에 대해서 얼마나 알고있는가? (1) | 2024.10.11 |