JAVA

5분 안에 구축하는 Redis-Cluster

코린이s 2021. 5. 10. 16:14
728x90

Redis-Cluster 특징?

  • master를 여러개 두어 분산 저장이 가능하며(Sharding), scale out 이 가능하다.
    • 서버를 늘릴수록 저장할수 있는 공간이 무한대로 커진다. 
  • master에 하나 이상의 slave 를 둘 수 있다.
  • master 1,2,3 이 있다면 데이터는 3개중에 하나에 저장되며, client 가 데이터 읽기 요청시 저장된 곳이 아닌 다른 마스터에 요청 했다면 저장된 마스터 정보를 알려주며, 클라이언트는 전달받은 마스터 정보에 다시 요청해서 데이터를 받아와야 한다.
    • But, 해당 부분은 redis-cluster 를 지원하는 라이브러리에서 다 해준다.

* 참고

- scale up : 단일 서버의 스펙을 올려 서버 성능을 높힌다.

- scale out : 서버를 추가하여 서버 성능을 높힌다.

  • slave 가 죽어서 복제 노드가 없는 마스터가 생길시 다른 마스터 노드에 여유분이 있다면 해당 노드로 빈자리를 채울 수 있다.
    • 사용자가 개입하지 않고 클러스터가 알아서 다 해준다.

  • 마스터가 죽었을시 Slave 가 master 로 자동 faliover 된다.
  • 최소 3개의 마스터 노드가 있어야 구성 가능하다.
  • 센티널이 노드를 감시했지만, 클러스터에서는 모든 노드가 서로 감시한다.

구성해보자!

서버가 3대라고 하면 아래 구성이 Best 이지만 (한대가 죽어도 운영이 가능한 형태, 물론 서버 자원이 충분 하다면 한대 서버에 Master 1개, Slave 2개로 구성한다면 두대가 죽어도 운영 가능함) 로컬 테스트일 경우는 간단하게 구성해본다.

우리가 구성할 간단한 아키텍쳐는 아래와 같다.

:: github.com/works-code/redis-cluster

 

works-code/redis-cluster

redis-cluster. Contribute to works-code/redis-cluster development by creating an account on GitHub.

github.com

1) 레디스를 설치 한다

// 만약 mac 에 wget 이 설치 되어 있지 않다면 Homebrew를 통해 설치
$brew install wget
$ mkdir redis
$ cd redis
$ wget http://download.redis.io/releases/redis-5.0.6.tar.gz
$ tar xzf redis-5.0.6.tar.gz
$ cd redis-5.0.6
$ make // 소스 컴파일(=소스 파일을 실행 가능한 형태로 만들어 준다)
$ sudo make install // /usr/local/bin에 redis-server, redis-cli 등 실행 파일이 복사
// redis 서버 실행 (오류 안뜨고 잘되면 성공!)
$ src/redis-server redis.conf
or
$ redis-server redis_6382.conf

 

2) 설정

// 설정파일 복사 (master 3개, slave 3개)
$ cp redis.conf redis_6300.conf
$ cp redis.conf redis_6301.conf
$ cp redis.conf redis_6302.conf
$ cp redis.conf redis_6400.conf
$ cp redis.conf redis_6401.conf
$ cp redis.conf redis_6402.conf
// 로그파일 저장 경로
$ mkdir logs

// 설정 수정 (포트를 다르게 넣는 부분 빼고 모두 동일하게 수정한다.)
$ vi redis_[각자포트].conf
port [각자포트]
# 백그라운드에서 시작하도록 설정
daemonize yes
# 클러스터를 사용하겠다.
cluster-enabled yes 
# 클러스터 구성 내용을 저장한는 파일명 지정 (자동 생성됨)
cluster-config-file nodes-[각자포트].conf 
# 클러스터 노드가 다운되었는지 판단하는 시간 (3s)
cluster-node-timeout 3000 
# Appendonly를 yes로 설정하면 rdb에 저장 안되고 aof에 저장됨 (각각 장단점이 있으니 해당 부분은 선택 사항)
appendonly yes 
# append only yes 시 해당 부분도 수정
appendfilename appendonly_[각자포트].aof 
# 프로세스 아이디 저장 경로 설정
pidfile /var/run/redis_[각자포트].pid
# 로그 파일 저장 경로 지정
logfile logs/redis_[각자포트].log

3) 기동

$ src/redis-server redis_6300.conf
$ src/redis-server redis_6301.conf
$ src/redis-server redis_6302.conf
$ src/redis-server redis_6400.conf
$ src/redis-server redis_6401.conf
$ src/redis-server redis_6402.conf

아래와 같이 클러스터 모드로 기동 된것을 볼 수 있다.

프로세스 확인!

$ ps -ef | grep 'src/redis'
  501 54964     1   0  4:25PM ??         0:00.22 src/redis-server 127.0.0.1:6300 [cluster]
  501 54967     1   0  4:25PM ??         0:00.22 src/redis-server 127.0.0.1:6301 [cluster]
  501 54969     1   0  4:25PM ??         0:00.21 src/redis-server 127.0.0.1:6302 [cluster]
  501 54973     1   0  4:25PM ??         0:00.20 src/redis-server 127.0.0.1:6400 [cluster]
  501 54975     1   0  4:25PM ??         0:00.20 src/redis-server 127.0.0.1:6401 [cluster]
  501 54980     1   0  4:25PM ??         0:00.19 src/redis-server 127.0.0.1:6402 [cluster]

4) 클러스터 구성 

1. 마스터 설정

// 마스터 설정
$ redis-cli --cluster create 127.0.0.1:6300 127.0.0.1:6301 127.0.0.1:6302
>>> Performing hash slots allocation on 3 nodes...
Master[0] -> Slots 0 - 5460
Master[1] -> Slots 5461 - 10922
Master[2] -> Slots 10923 - 16383
M: 1421ba67b8753514d8b4468bfba6691492600b15 127.0.0.1:6300
   slots:[0-5460] (5461 slots) master
M: a9a9cf5addf2b403f9d7a19f2a7436a69b9ee29a 127.0.0.1:6301
   slots:[5461-10922] (5462 slots) master
M: b6d509a5de3a12df80922d5cc9508a0e9031f4c9 127.0.0.1:6302
   slots:[10923-16383] (5461 slots) master
Can I set the above configuration? (type 'yes' to accept): yes
>>> 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 127.0.0.1:6300)
M: 1421ba67b8753514d8b4468bfba6691492600b15 127.0.0.1:6300
   slots:[0-5460] (5461 slots) master
M: b6d509a5de3a12df80922d5cc9508a0e9031f4c9 127.0.0.1:6302
   slots:[10923-16383] (5461 slots) master
M: a9a9cf5addf2b403f9d7a19f2a7436a69b9ee29a 127.0.0.1:6301
   slots:[5461-10922] (5462 slots) master
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

// 혹시나 아래와 같은 에러 발생시 데이터 삭제후 진행
$ redis-cli --cluster create 127.0.0.1:6300 127.0.0.1:6301 127.0.0.1:6302
[ERR] Node 127.0.0.1:6300 is not empty. Either the node already knows other nodes (check with CLUSTER NODES) or contains some key in database 0.
$ redis-cli -p 6300
127.0.0.1:6300> flushall
OK

- Master 노드 로그 

// 변경시 마스터 포트 로그 (logs/redis_[마스터포트].log)
54964:M 10 May 2021 16:30:21.493 # configEpoch set to 1 via CLUSTER SET-CONFIG-EPOCH
54964:M 10 May 2021 16:30:21.534 # IP address for this node updated to 127.0.0.1
54964:M 10 May 2021 16:30:24.445 # Cluster state changed: ok

 

2. Slave 등록

$ redis-cli --cluster add-node 127.0.0.1:6400 127.0.0.1:6300 --cluster-slave
$ redis-cli --cluster add-node 127.0.0.1:6401 127.0.0.1:6301 --cluster-slave
$ redis-cli --cluster add-node 127.0.0.1:6402 127.0.0.1:6302 --cluster-slave
>>> Adding node 127.0.0.1:6400 to cluster 127.0.0.1:6300
>>> Performing Cluster Check (using node 127.0.0.1:6300)
M: 1421ba67b8753514d8b4468bfba6691492600b15 127.0.0.1:6300
   slots:[0-5460] (5461 slots) master
M: b6d509a5de3a12df80922d5cc9508a0e9031f4c9 127.0.0.1:6302
   slots:[10923-16383] (5461 slots) master
M: a9a9cf5addf2b403f9d7a19f2a7436a69b9ee29a 127.0.0.1:6301
   slots:[5461-10922] (5462 slots) master
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
Automatically selected master 127.0.0.1:6300
>>> Send CLUSTER MEET to node 127.0.0.1:6400 to make it join the cluster.
Waiting for the cluster to join
>>> Configure node as replica of 127.0.0.1:6300.
[OK] New node added correctly.

- Master 로그 

54964:M 10 May 2021 16:34:22.630 * Replica 127.0.0.1:6400 asks for synchronization
54964:M 10 May 2021 16:34:22.630 * Partial resynchronization not accepted: Replication ID mismatch (Replica asked for '110ddb8f80bc1e3b9f1c3b3cc59fa493af6eea36', my replication IDs are '7b13c1296713bc7dd5c601340dcf9dad86e2363d' and '0000000000000000000000000000000000000000')
54964:M 10 May 2021 16:34:22.630 * Starting BGSAVE for SYNC with target: disk
54964:M 10 May 2021 16:34:22.631 * Background saving started by pid 55414
55414:C 10 May 2021 16:34:22.633 * DB saved on disk
54964:M 10 May 2021 16:34:22.713 * Background saving terminated with success
54964:M 10 May 2021 16:34:22.713 * Synchronization with replica 127.0.0.1:6400 succeeded

- Slave 로그

54973:M 10 May 2021 16:34:21.399 # IP address for this node updated to 127.0.0.1
54973:S 10 May 2021 16:34:22.238 * Before turning into a replica, using my master parameters to synthesize a cached master: I may be able to synchronize with the new master with just a partial transfer.
54973:S 10 May 2021 16:34:22.239 # Cluster state changed: ok
54973:S 10 May 2021 16:34:22.626 * Connecting to MASTER 127.0.0.1:6300
54973:S 10 May 2021 16:34:22.627 * MASTER <-> REPLICA sync started
54973:S 10 May 2021 16:34:22.628 * Non blocking connect for SYNC fired the event.
54973:S 10 May 2021 16:34:22.629 * Master replied to PING, replication can continue...
54973:S 10 May 2021 16:34:22.629 * Trying a partial resynchronization (request 110ddb8f80bc1e3b9f1c3b3cc59fa493af6eea36:1).
54973:S 10 May 2021 16:34:22.632 * Full resync from master: 0e7351058dd9d919b7ff062325611333b0145ed7:0
54973:S 10 May 2021 16:34:22.632 * Discarding previously cached master state.
54973:S 10 May 2021 16:34:22.713 * MASTER <-> REPLICA sync: receiving 175 bytes from master
54973:S 10 May 2021 16:34:22.713 * MASTER <-> REPLICA sync: Flushing old data
54973:S 10 May 2021 16:34:22.714 * MASTER <-> REPLICA sync: Loading DB in memory
54973:S 10 May 2021 16:34:22.714 * MASTER <-> REPLICA sync: Finished with success
54973:S 10 May 2021 16:34:22.715 * Background append only file rewriting started by pid 55415
54973:S 10 May 2021 16:34:22.738 * AOF rewrite child asks to stop sending diffs.
55415:C 10 May 2021 16:34:22.739 * Parent agreed to stop sending diffs. Finalizing AOF...
55415:C 10 May 2021 16:34:22.739 * Concatenating 0.00 MB of AOF diff received from parent.
55415:C 10 May 2021 16:34:22.739 * SYNC append only file rewrite performed
54973:S 10 May 2021 16:34:22.828 * Background AOF rewrite terminated with success
54973:S 10 May 2021 16:34:22.828 * Residual parent diff successfully flushed to the rewritten AOF (0.00 MB)
54973:S 10 May 2021 16:34:22.829 * Background AOF rewrite finished successfully

3. 클러스터 확인

// 클러스터 확인
$ redis-cli --cluster info 127.0.0.1:6300
127.0.0.1:6300 (1421ba67...) -> 0 keys | 5461 slots | 1 slaves.
127.0.0.1:6301 (a9a9cf5a...) -> 0 keys | 5462 slots | 1 slaves.
127.0.0.1:6302 (b6d509a5...) -> 0 keys | 5461 slots | 1 slaves.
$ redis-cli -p 6300
127.0.0.1:6300> cluster nodes
0df82b6c2cd75aa97d9cd8a69b28123ab0465b49 127.0.0.1:6401@16401 slave a9a9cf5addf2b403f9d7a19f2a7436a69b9ee29a 0 1620633082000 2 connected
1421ba67b8753514d8b4468bfba6691492600b15 127.0.0.1:6300@16300 myself,master - 0 1620633081000 1 connected 0-5460
a3c547fe9b5ec17d4e690c6f3aadf8c0127b9e69 127.0.0.1:6402@16402 slave b6d509a5de3a12df80922d5cc9508a0e9031f4c9 0 1620633082859 3 connected
24c18c75d55e9cf864870f9e2a411d450a5c2f54 127.0.0.1:6400@16400 slave 1421ba67b8753514d8b4468bfba6691492600b15 0 1620633082000 1 connected
a9a9cf5addf2b403f9d7a19f2a7436a69b9ee29a 127.0.0.1:6301@16301 master - 0 1620633082000 2 connected 5461-10922
b6d509a5de3a12df80922d5cc9508a0e9031f4c9 127.0.0.1:6302@16302 master - 0 1620633082353 3 connected 10923-16383

5) 자바와 연동한다

- 이전 포스팅과 동일하며, 설정 부분만 아래와 같이 수정한다.

// lettuce 사용시
    @Bean
    public RedisConnectionFactory redisConnectionFactory(){
        LettuceClientConfiguration clientConfiguration = LettuceClientConfiguration.builder()
                .readFrom(ReadFrom.REPLICA_PREFERRED) // 복제본 노드에서 읽지 만 사용할 수없는 경우 마스터에서 읽습니다.
                .build();
        // 모든 클러스터(master, slave) 정보를 적는다. (해당 서버중 접속되는 서버에서 cluster nodes 명령어를 통해 모든 클러스터 정보를 읽어오기에 다운 됐을 경우를 대비하여 모든 노드 정보를 적어두는편이 좋다.)
        RedisClusterConfiguration redisClusterConfiguration = new RedisClusterConfiguration()
                .clusterNode("localhost", 6300)
                .clusterNode("localhost", 6301)
                .clusterNode("localhost", 6302)
                .clusterNode("localhost", 6400)
                .clusterNode("localhost", 6401)
                .clusterNode("localhost", 6402);
        LettuceConnectionFactory lettuceConnectionFactory = new LettuceConnectionFactory(redisClusterConfiguration, clientConfiguration);
        return lettuceConnectionFactory;
    }

6) 테스트

1. 데이터를 넣는다. 

- 데이터 3 sets 을 넣었을시 마스터 6300, 6301, 6302 번에 나눠서 들어간다.

2. 마스터 6300을 죽인다.

- Slave 노드 6400번이 마스터가 되었다.

// 6400 서버 로그
54973:S 10 May 2021 17:34:21.748 # configEpoch set to 4 after successful failover
54973:M 10 May 2021 17:34:21.748 # Setting secondary replication ID to 0e7351058dd9d919b7ff062325611333b0145ed7, valid up to offset: 4963. New replication ID is 99ae9c2fb9c1ceb128f642ed526b6612601c8759
54973:M 10 May 2021 17:34:21.749 * Discarding previously cached master state.
54973:M 10 May 2021 17:34:21.749 # Cluster state changed: ok

3. 마스터(6300) 에 들어 있던 데이터를 요청한다.

- 정상적으로 가져와 진다. > 마스터 ip:port 로 연동해두어도 자동으로 승격된 Slave (6400)에서 데이터를 읽어 온다.

4. 마스터로 승격된 서버(6400) 도 죽이고 해당 데이터를 읽어온다.

- 설정에 의해 master, slave 한쌍이 모두 죽었다면 클러스터가 동작할 수 없도록 되어있다. 해당 설정은 수정시 다른 데이터만이라도 읽게 할 수도 있지만 데이터 정합성이 깨진다고 한다.

5. 6400, 6300 모두 살린다 

- 클러스터도 기존 레디스와 동일하게 죽었다 살아나도 변경된 롤(master, slave)이 유지 된다.

6. 클라이언트에 직접 접속해서 테스트

- 해당 마스터에 데이터가 없으면 어디에 있는지 알려준다. (key_01은 6400 에 저장되어 있다고 나온다.)

$ redis-cli -p 6302
127.0.0.1:6302> keys *
1) "key_02"
127.0.0.1:6302> get key_02
"value_02"
127.0.0.1:6302> get key_01
(error) MOVED 1579 127.0.0.1:6400

- 6400에 접속해서 확인해보면 해당 데이터를 조회할 수 있다. > 이걸 lettuce가 알아서 해주는거다.

(base) hongyoolee@hateyou redis-5.0.6 % redis-cli -p 6400
127.0.0.1:6400> keys *
1) "key_01"
127.0.0.1:6400> get key_01
"value_01"

 

그러나 클라이언트에서 매번 이렇게 데이터를 찾는거는 부담이기도 하고, redis 서버에서도 클라이언트가 늘어날때마다 connection 이 증가 하여 redis 서버 부하가 생겨 성능이 떨어질 수 밖에 없으며, redis 를 사용 하고자 하는 클라이언트가 생길때 마다 설정 부분에 여러 엔드 포인트를 지정해주어야 하는 단점이 있어 이 부분을 대신 해줄 서버가 따로 필요하다. 

Predixy 이용 !! (다음 포스팅 예정)

# 참고 사항

해당 라이브러리를 이용하면 첫 호출시 하나의 마스터만 호출할까? 

확인을 위해 모니터링 도구 redis-stat 을 설치 한다! (ruby 언어로 만들어져 있어 ruby 설치가 안되어 있다면 설치)

$ brew install ruby
$ gem install redis-stat

 

그리고 실행을 한다!

// redis stat를 8888 port 로 UI 띄운다.
$ redis-stat localhost:6300 localhost:6301 localhost:6302 localhost:6400 localhost:6401 localhost:6402 --daemon --server=8888

redis stat UI 를 접속한다. (localhost:8888)



테스트를 위해 등록을 시도했을시 하나의 마스터에 계속적으로 등록 되는 것이 아니라 여러 서버에 순차적으로 저장 되었다. (key 수, aofcs 크기 : 명령어 입력시 해당 명령어를 저장하여 aof 가 증가 되므로)

읽기 시도시에는 슬레이브에 접근해서 데이터를 가져왔다. (hit/s 로 접근 서버 확인)

그러나 hit/s 로 확인하기에는 어느 슬레이브 서버에 처음에 접근했는지 알 수 없었다.

hit 는 실제 키를 가져와서 리턴했을 경우에만 표기가 되서 서버에서 키를 가져오기 해서 다른 서버에 있다면 해당 서버에서 hit수가 증가하지 않아 어떤 서버에 먼저 접근했는지 알 수 없다는점!

그러나 공식 문서를 보니 모든 마스터 서버에 요청을 보내 key를 찾는다고 한다 ㅎㅎ (현재 설정으로는 Slave 서버에서 읽기로 하였으니 모든 Slave 서버에 요청을 보냄 / 만약 slave 한대가 죽었다면 master 1대(죽은 slave를 대신하여 읽기 작업을 할 마스터 서버), slave 2대에 get key 요청을 보냄)

역시 공식 문서를 보면 해결 됨 ! 다음에는 공식문서를 꼼꼼하게 보고 시작해야 겠다 ㅎㅎ

:: https://docs.spring.io/spring-data/data-redis/docs/current/reference/html/#cluster

 

Spring Data Redis

Some commands (such as SINTER and SUNION) can only be processed on the server side when all involved keys map to the same slot. Otherwise, computation has to be done on client side. Therefore, it is useful to pin keyspaces to a single slot, which lets make

docs.spring.io

그리고 참고로 한대가 죽었을시 아래와 같이 화면에 표기 된다.

- 끝 -

# Reference

- meetup.toast.com/posts/226

 

개발자를 위한 레디스 튜토리얼 03 : NHN Cloud Meetup

지금부터는 레디스의 `HA`(High Availability)에 대해서 알아보겠습니다. 레디스는 **Master - Replica** 형태의 복제를 제공합니다.

meetup.toast.com

- lascrea.tistory.com/214

 

Redis Cluster

Redis Cluster 레디스는 단일 인스턴스만으로도 운영이 가능하지만 물리 머신이 가진 메모리의 한계를 초과하는 데이터를 저장하고 싶거나 failover에 대한 처리를 통해 HA를 보장하려면 센티널이나

lascrea.tistory.com

- redisgate.kr/redis/clients/lettuce_cluster.php

 

Lettuce Cluster

lettuce_cluster Lettuce Cluster Cluster Class 변수로 선언 public static RedisClusterClient redisCluster = null; public static StatefulRedisClusterConnection connection = null; public static RedisAdvancedClusterCommands cluster = null; public static Re

redisgate.kr

728x90