redis cluster 구성하기

Redis clustering 튜토리얼페이지 요약

참고 : http://redis.io/topics/cluster-tutorial


이 페이지에서는 Redis cluster의 구성방법을 다룬다.더 자세한 정보는 아래를 참고하자. 

Redis Cluster specification


Redis cluster tutorial

Redis Cluster 101

Redis cluster는 데이터가 자동으로 sharding되어 다수의 Redis 노드로 분산되어 들어가도록 해준다. 

여러가지 Key에 대한 command는 cluster에서 다루지 않는데 이유는 이런 방식을 노드간의 data이동을 필요로하고 부하가 많은 경우에 Redis cluster의 redis 본연의 성능을 보장해 주지 못하게 하기 때문이다. 


Redis Custer는 노드의 fail이나 통신 불능 상태등에 대해 지속적으로 동작할 수 있도록 파티션 간의 어느정도의 가용성을 제공한다. 

그게 뭐냐하면 

  • 여러 노드들에 데이터들을 분리해서 넣을 수 있다. 
  • 부분적인 노드들에서 실패 혹은 통신 불능 상태에서 지속적인 동작을 제공한다. 

Redis Cluster TCP ports


모든 Redis cluster 노드는 두개의 TCP connection을 필요로한다. 기본적인 Redis TCP port는 client를 위한 것이고, (예를 들어 6379), 추가로 10000을 더한 portt는 데이터 port로 쓰인다. (예를 들면 16379)


두번째 port는 Cluster bus로 사용되는데 노드간의 바이너리 커뮤니케이션 채널로 사용된다. Cluster bus는 노드의 실패 detection이나 설정 업데이트, failover authorization 등등에 쓰이게 된다. Client는 당연히 이 port를 사용하면 안된다. 아까 설명한 첫번째 Redis command port를 사용하자. 

그리고 이 두개의 port가 방화벽에서 열려있는지도 체크하자. 안그러면 cluster가 작동하지 않을 수 있다. 


command port와 cluster bus port의 offset은 항상 10000으로 정해져 있다. 

Cluster가 제대로 작동하기 위해서는 다음을 잘 숙지해야한다. 

1. 기본 client communication port (보통 6379)는 cluster에 접근하고자하는 모든 client와 다른 cluster 노드들(Key 이동을 위해)에 의해서 사용된다.

2. cluster bus port (client port + 10000)은 다른 node cluster에게 항상 개방되어 있어야한다. 


Redis Cluster data sharding


Redis Cluster는 consistency hashing을 사용하지 않고 모든 key가 개념적으로 hash slot의 일부인 다른 sharding을 사용한다. 

Redis cluster는 16384개의 hash slot을 사용하고 key modulo 16384를 사용하는 CRC16를 사용해서 hash slot을 계산한다. 


Redis cluster의 모든 노드는 hash slot의 subset이다. 만약 3개의 노드가 있다면 아래와 같이 slot이 나뉘어 질 수 있다. 

  • Node A contains hash slots from 0 to 5500.
  • Node B contains hash slots from 5501 to 11000.
  • Node C contains hash slots from 11001 to 16384.

이 방법은 노드를 더하고 제가하는 것을 쉽게 한다. 예를 들어 D라는 노드가 추가되었다면 A,B,C의 어느정도의 slot을 D에게 할당하면 된다. 비슷하게 노드 A를 제가한다면 단순히 A의 slot을 B나 C로 옮기면된다. 노드 A가 empty가 되면 언제든지 이 노드를 제거할 수 있다. 


hash slot을 이동하는 것이 cluster를 정지할 필요가 없기 때문에 노드를 더하거나 제거하거나 hash slot의 비율을 변경하는 것이 언제고 가능하다. 


Redis Cluster master-slave model


부분적인 노드가 실패나 통신 불능에서도 가용성을 확보하기 위해 Redis cluster에서는 master-slave를 지원한다. 


아까 예로들었던 A,B, C로 구성된 Cluster에서 B가 실패한다면 5501-11000의 slot을 서비스 할 수 없게 된다. 

그러나 slave를 추가해서 A,B,C가 master이고 A1, B1, C1이 slave가 되도록 cluater를 구성한다면 B1은 B를 replication하게 B의 장애시에 B1가 elaction(투표)를 통해 새로운 master로 선출된다. 

하지만 B와 B1이 동시에 fail되면 지속적으로 서비스 할 수 없다. 


Redis Cluster consistency guarantees

Reids Cluster는 strong consistency를 보장하지 않는다. 쉽게 말하면 특정 조건에서 Redis Cluster에 성공적으로 응답을 받은 데이터를 Cluster가 잃어 버릴 수 있다는 뜻이다. 


첫번째 이유는 Redis Cluster가 asynchronous replication을 사용하기 때문이다. 이 말은 write를 할 때 아래와 같은 절차를 따른다는 뜻이다. 

  • Client가 Master B에 데이터를 쓴다. 
  • master B가 Client에 OK라고 대답한다. 
  • Master B가 B1, B2, B3의 replica에 쓰기를 전달한다. 

보시다싶이 B는 B1, B2, B3에서의 확인을 받기 전에 client에 확인 결과를 보낸다. 이유는 저런 확인이 비용이 비싼 잠재적인 패널티가 될 수 있기 때문이다. B가 쓰기를 확인하고 slave에 이를 전달하기 전에 fail일 발생한다면 데이터가 손실 될 수 있다. 


이것은 대다수의 데이터 베이스들이 1초마다 disk로 데이터를 flush하는 경우에서와 마찬가지다. client에 확인 요청을 보내기 전에 disk에 강제로 flush할 수 있지만 이런 방법은 보통 엄청난 성능 저하를 가져온다. 


기본적으로 consistency와 성능간에는 trade off가 있다. 


또 데이터를 잃을 수 있는 다른 시나리오가 있는데 네트워크 파티션이 일어나고 client가 minor한 파티션에 속하게 되었을 때이다. 

예를 들어보자. 6개의 노드가 있는데 A, B, C, A1, B1, C1으로 3개의 master와 3개의 slaver로 구성되었다고 생각하자. 또 Z1이라는 client도 존재한다고 치자. 


만약 네트워크 파티션이 발생하면 A, C, A1, B1, C1과 B, Z1으로 분리될 수 있다. Z1은 B에게 계속 쓸 수 있다. 만약 파티션된 네트워크가 금방 회복 된다면 cluster는 평상시처럼 지속될 것이다. 

하지만 이 시간이 B1이 master로 선출될 수 있는 충분한 시간이라면 Z1이 B에게 썼던 데이터는 잃게 된다. 


이 설정을 node timeout이라 부르는데 Redis cluster 설정에서 에서 매우 중요하다. 


node timeout이 발생하면 master node는 실패되었다고 간주되고 다른 replica들중에 하나로 교체된다. 비슷하게 node timeout이 발생하고 major한 master 노드들을 감지할 수 없다면 이 노드는 에러 상태로 들어가고 쓰기를 받지 않게된다. 




Creating and using a Redis Cluster


Cluster를 생성하기 위해 필요한 것은 몇개의 cluster mode로 동작하고 있는 empty상태의 Redis instance들이다. 이것은 기본적으로 이 Redist instatnce들이 보통의 instance들이 아니라 Cluster에 부합하는 특징과 command들을 사용할 수 있도록 설정된 특별한 모드의 instance들이라는 것이다. 


아래는 최소한의 Redis cluster 설정파일이다. 

port 7000
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes

보이는 것 처럼 cluster mode를 enable시키는 것은 cluster-enabled 지시자이다. 모든 instance들은 마찬가지로 파일의 경로를 가지고 있는데 이것은 이 노드의 설정이 어디에 저장되어 있는지를 알리는 것이며 default로 nodes.conf를 사용한다. 

이 파일들은 사람에 의해서 수정되어서는 안되고 단순히 Redis Cluster가 시작될때 cluster에 의해서 생성되고 매 시간 필요시마다 업데이트된다. 


적어도 세게 이상의 master node가 있어야 minimal cluster가 될 수 있다는 점을 숙지해야한다. 당신을 위해 처음에 3개의 master와 3개의 slave로 구성된 cluster를 강력하게 추천한다. 


그러기 위해서는 새 디렉토리에서 아래와 같이 새로운 instance들이 실행될 수 있는 instance port number를 따르는 디렉토리들을  만들djdi gksek. 

mkdir cluster-test
cd cluster-test
mkdir 7000 7001 7002 7003 7004 7005


각각의 7000부터 7005까지의 directory안에 redis.conf 파일을 생성한다. 템플릿으로는 위의 최소한의 example을 사용하며 port를 각 디렉토리 이름에 맞도록 수정해야한다. 

이제 Github의 unsatble branch를 최신으로 컴파일되거나 실행가능한 redis-server를 복사해서 cluster-test디렉토리에 복사한다. 

아래와 같이 모든 insatance를 실행시킨다. 

cd 7000
../redis-server ./redis.conf

아래와 같이 모든 instance에서 다음과 같은 log를 볼 수 있다. 이것은 nodes.conf 파일이 존재하지 않기 때문이며 모든 노드들을 각자 새로운 ID를 자신에게 할당한다. 

[82462] 26 Nov 11:56:55.329 * No cluster configuration found, I'm 97a3a64667477371c4479320d683e4c8db5858b1


이 ID는 cluster안에서 각자의 유일한 이름으로 영원히 사용된다. 모든 노드들은 IP혹은 port가 아닌 ID로 다른 노드들을 기억한다. IP와 port는 바뀔 수 있지만 이 유일한 노드 식별자는 노드의 일생동안 바뀌지 않는다. 이것을 Node ID라고 부른다. 


Creating the cluster


이제 우리는 실행되고 있는 instance들을 가지고 있고, 노드들에게 뭔가 의미있는 설정을 생성해서 cluster에 써줄 필요가 있다. 

redis-trib이라는 utility를 사용해서 하면 되는데, 이것은 새로운 cluster를 생성하거나 shard를 바꾸거나 하는데 쓰인다. 


redis-trib utility는 배포 코드의 src 디렉토리에 위치한다. cluster를 생성하기위해서는 간단히 아래와 같이 하면 된다. 

./redis-trib.rb create --replicas 1 127.0.0.1:7000 127.0.0.1:7001 \
127.0.0.1:7002 127.0.0.1:7003 127.0.0.1:7004 127.0.0.1:7005


우리가 원하는 것은 새로운 cluster를 생성하는 것이기 때문에 여기서 사용된 command는 create이다. --replica 1이라는 옵션은 우리가 생성되는  모든 master에 대해서 slave를 원한다는 것을 의밓나다. 다른 argument들은 생성하고자 하는 새로운 cluster에 주소 목록이다. 


명백히 우리가 단지 원하는 것은 3개의 master와 3개의 slave를 가진 cluster이다. 


Redis-trib은 설정을 위해 쓰인다. Yes를 typing한다. 그러면 cluster들이 설정되고 join될 것이다. 이것은 instace들이 서로 이야기하면서 시동됨을 의미한다. 

결론적으로 문제가 없다면 우리는 아래와 같은 메시지를 보게 될 것이다. 

[OK] All 16384 slots covered


이것은 적어도 master instance들의 16384개의 slot들이 사용가능하다는 것을 뜻ㅎ나다. 


Playing with the cluster

현재 단계에서 문제는 Redis Cluster에 대한 client 구현이 미비하다는 것이다. 

현재 알려진 구현은 아래와 같다. 


  • redis-rb-cluster is a Ruby implementation written by me (@antirez) as a reference for other languages. It is a simple wrapper around the original redis-rb, implementing the minimal semantics to talk with the cluster efficiently.
  • redis-py-cluster appears to be a port of redis-rb-cluster to Python. Not recently updated (last commit 6 months ago) however it may be a starting point.
  • The popular Predis has support for Redis Cluster, the support was recently updated and is in active development.
  • The most used Java client, Jedis recently added support for Redis Cluster, see the Jedis Cluster section in the project README.
  • The redis-cli utility in the unstable branch of the Redis repository at Github implements a very basic cluster support when started with the -c switch.


쉽게 Redis Cluster를 테스트 해보는 방법은 redis-cli command line 유틸을 사용하는 것이다. 아래는 그 예이다. 


$ redis-cli -c -p 7000
redis 127.0.0.1:7000> set foo bar
-> Redirected to slot [12182] located at 127.0.0.1:7002
OK
redis 127.0.0.1:7002> set hello world
-> Redirected to slot [866] located at 127.0.0.1:7000
OK
redis 127.0.0.1:7000> get foo
-> Redirected to slot [12182] located at 127.0.0.1:7002
"bar"
redis 127.0.0.1:7000> get hello
-> Redirected to slot [866] located at 127.0.0.1:7000
"world"


redis-cli cluster가 제공하는 것은 기본적인 것이어서 항상 Redis Cluster 노드들이 올바를 client로 redirect된다는 것만을 사용한다. 

좀더 좋은 client는 이것보다 더 나은데 hash slot이나 nodes의 주소 정보 map을 caching하여 바로 필요한 node로 연결될 수 있게 한다. map은 cluster의 설정이 변경될 때만 갱신된다. 예를 들면 failover가 일어 났다거나 admin이 노드를 추가 삭제하여 cluster의 형태가 변경되었을 때이다. 


Writing an example app with redis-rb-cluster


좀더 알아보기 전에 Redis cluster가 어떻게 failover, resharding등을 어떻게 하는지 새로운 example 어플리케이션을 통해서 알아보자. 

이를 통해 우리는 Redis cluster가 실제 상황에서 어떻게 행동하는지 보기 위해 example code를 돌려 fail과 resharding을 관찰수 있다. 


이 섹션은 redis-rb-cluster의 기본적인 두개의 example을 설명한다. 첫째는 아래와 같고 redis-rb-cluster 배포본의 example.rb 파일에 있다. 


     1  require './cluster'
     2
     3  startup_nodes = [
     4      {:host => "127.0.0.1", :port => 7000},
     5      {:host => "127.0.0.1", :port => 7001}
     6  ]
     7  rc = RedisCluster.new(startup_nodes,32,:timeout => 0.1)
     8
     9  last = false
    10
    11  while not last
    12      begin
    13          last = rc.get("__last__")
    14          last = 0 if !last
    15      rescue => e
    16          puts "error #{e.to_s}"
    17          sleep 1
    18      end
    19  end
    20
    21  ((last.to_i+1)..1000000000).each{|x|
    22      begin
    23          rc.set("foo#{x}",x)
    24          puts rc.get("foo#{x}")
    25          rc.set("__last__",x)
    26      rescue => e
    27          puts "error #{e.to_s}"
    28      end
    29      sleep 0.1
    30  }


이 프로그램을 돌리면 결과는 아래와 같은 command의 흐름이다. 

  • SET foo0 0
  • SET foo1 1
  • SET foo2 2
  • And so forth...

7번째 줄에서는 각각의 instance에 대해서 32개의 connection과 timeout 0.1초를 설정해 주고 있다. 


위에서 sartup 노드들은 꼭 cluster의 모든 노드일 필요는 없다. 중요한 점은 적어도 하나의 노드는 접근 가능해야한다는 것이다. redis-rb-cluster는  첫번째 노드에 접속할 수 있을때 startup 노드들의 목록을 업데이트한다. 

이제 rc라는 변수에 Redis cluster instance가 저장되었고 우리는 이 object를 통해서 보통의 Redis object instance처럼 Redis를 사용할 수 있다. 


이것이 11~19째줄에 나타나있다. 우리가 프로그램을 재시작했을 때 foo0부터 다시 시작하길 바라지 않기 때문에 우리는 현재의 count도 Redis에 저장한다. 그래서 11~19줄에서는 예전값을 redis로 부터 읽거나 없다면 0으로 할당하도록 디자인되어 있다. 


21~30째 줄에서는 loop를 돌면서 key를 set하거나 에러를 출력한다. 


결과는 아래와 같다. 


ruby ./example.rb
1
2
3
4
5
6
7
8
9
^C (I stopped the program here)



Resharding the cluster


resharding을 해보자. 

resharding은 기본적으로 특정 node들의 hash slot을 다른 노드들의 slot으로 이동하는 것을 뜻한다. cluster를 생성하는 것과 마찬가지로 redis-trip 유틸을 사용하면된다. 


resharding을 진행하게 위해서는 단순히 아래와 같이 하면 된다. 

./redis-trib.rb reshard 127.0.0.1:7000


하나의 노드만 정하면 다른 노드들은 redis-trib이 알아서 찾는다. 

현재의 redis-trip은 5%정도를 옮겨줘와 같은 것은 지원하지 않는다. redis-trip은 아래와 같은 질문으로 어마만큼 resharding하고 싶은지 물어본다. 


How many slots do you want to move (from 1 to 16384)?


우리는 1000개의 hash slot을 resharding해볼 수 있다. 만약 위의 예제에서 sleep을 주지 않는다면 이는 무시하지 못할 양의 key들이다. 

그다음 redis-trib에게 hash slot들을 받게될 resharding target 을 알려주어야한다. 우리는 127.0.0.1:7000인 첫번째 노드를 사용할 것이며 instance의 Node ID를 사용하게 된다. 아래와 같은 command로 원하는 node의 Node ID를 볼 수 잇따. 


$ redis-cli -p 7000 cluster nodes | grep myself
97a3a64667477371c4479320d683e4c8db5858b1 :0 myself,master - 0 0 0 connected 0-5460


이제 어떤 node들에 이 key들이 속할지 알려주어야한다. 우리는 hash slot들을 다른 master node들로 부터 가져오기위해 all이라고 입력할 것이다. 

마지막 conform후에 우리는 선택한 노드로 부터 다른 노드로 이동되는 모든 slot에 대한 메시지를 볼 수있을 것이다. 그리고 이동되는 모든 key마다 .이 출력될것이다. 


resharding이 진행되는 동안 우리는 example program이 영향을 받지 않고 동작함을 확인 할 수 있다. 

resharding이 완료되면 아래 명령어를 통해서 cluster의 health를 check할 수 있다. 


./redis-trib.rb check 127.0.0.1:7000


확인 해보면 127.0.0.1:7000에 더 많은 slot이 할당되어 있음을 확인 할 수 있다. 





'Programing' 카테고리의 다른 글

Redis 대량 insert  (0) 2014.09.23
redis cluster 구성하기  (0) 2014.09.23
redis transaction  (0) 2014.09.10
redis pipeline  (0) 2014.09.10
redis  (0) 2014.09.08
logback  (0) 2014.07.25
0 0