おさかな日誌

魚類がプログラミング

Redis cluster

Redis 初心者が Redis 3.0 から追加された cluster 機能を使ってみました。勘違いや憶測が多分に含まれます、ご注意を><


セットアップ

現時点では 3.0 がパッケージマネージャでインストールできなかったので、Redis 公式ページから latest stable をダウンロードして、make, make test した。

大体 http://redis.io/topics/cluster-tutorial を見てセットアップする。今回はまずは 4 master node で動かしてみる。後でノードを追加したいので余分に設定を作っておく。

mkdir 7000 7001 7002 7003 7004 7005
❯ cat <<EOS > redis.conf
port 7000
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
EOSfor i in {0..5}; do cp -av redis.conf "700$i/"; sed -i -e "s/port 7000/port 700$i/g" "700$i/redis.conf"; done

cluster-config-file は redis を起動させたディレクトリにできる。この config ファイルは cluster enabled な Redis サーバーが自動で生成するもので各インスタンス固有にしなければならない。なので redis-server 起動時は各インスタンスの CWD が重ならないようにする。そこに気をつけてそれぞれのディレクトリに cd しながら各 config ファイルで4つの Redis インスタンスを起動する。redis-server redis.conf

原始的な方法だと、それぞれの Redis インスタンスを cluster enable モードで立ち上げておいて、redis/src/redis-trib.rb スクリプトを使ってクラスタを作成する。今回は replica なしで 4 master でセットアップした。(--replicate 0 を指定)

~/Downloads/redis-3.0.0/src
❯ ./redis-trib.rb create --replicas 0 \
  127.0.0.1:7000 \
  127.0.0.1:7001 \
  127.0.0.1:7002 \
  127.0.0.1:7003

クラスタを作成すると各インスタンスのログに 1925:M 04 Apr 15:04:55.489 # Cluster state changed: ok とか出た。

~/Downloads/redis-3.0.0/src
❯ redis-cli -p 7000 cluster nodes
f368ccf3ca85a5007a34e8e3b93f3e37797055f6 127.0.0.1:7003 master - 0 1428129180876 4 connected 12288-16383
2b4114a2b99ba696950ad919196223eb25c9ede2 127.0.0.1:7000 myself,master - 0 0 1 connected 0-4095
0e1301d83a174b598c6315167b868be93003e4c4 127.0.0.1:7002 master - 0 1428129182430 3 connected 8192-12287
968d6f7c12ae4c6c70239c59aafd89c302dfd427 127.0.0.1:7001 master - 0 1428129181397 2 connected 4096-8191

うん、動いている。

redis-cluster は hash slot という概念でシャーディングしている。キーの集合からなる hash slot があり、クラスタ内の1ノードが複数の hash 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.

ある割合の hash slot をノードからノードへ移動することで、master ノードの追加や削除に対応する。

コマンドオプションから察せれるように、ノードの replication にも対応していて、3 master - 6 slave 構成とかを取ることもできるらしい。

ノード間の通信は次のことを通信している。(画像は link のスライドから)

f:id:aladhi:20150404165517p:plain

コマンド操作

redis-cli を使えば透過的にクラスタ内のノードにコマンドを発行できるが、試しにクラスタ機能に対応してなさそうなクライアントでコマンド発行してみた。

# gem i pry redis
# pry -r redis
[15] pry(main)> redis = Redis.new(port: 7000)
=> #<Redis client v3.2.1 for redis://127.0.0.1:7000/0>
[16] pry(main)> redis.get('my_key')
Redis::CommandError: MOVED 13711 127.0.0.1:7003
from /Users/taiki45/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/redis-3.2.1/lib/redis/client.rb:113:in `call'
[17] pry(main)> redis = Redis.new(port: 7003)
=> #<Redis client v3.2.1 for redis://127.0.0.1:7003/0>
[18] pry(main)> redis.get('my_key')
=> nil
[19] pry(main)> redis.set('my_key', 123)
=> "OK"
[20] pry(main)> redis.get('my_key')
=> "123"
[21] pry(main)> redis = Redis.new(port: 7000)
=> #<Redis client v3.2.1 for redis://127.0.0.1:7000/0>
[22] pry(main)> redis.get('my_key')
Redis::CommandError: MOVED 13711 127.0.0.1:7003
from /Users/taiki45/.rbenv/versions/2.2.1/lib/ruby/gems/2.2.0/gems/redis-3.2.1/lib/redis/client.rb:113:in `call'

キーによってクラスタ内のノードが決定されていて、キーから決定されるノードと違うノードにコマンドを発行すると MOVED とレスポンスされた。レスポンスされたノードへ繋ぐといつも通りにコマンド発行ができるっぽい。プロキシーを用意しない方式は初めてみたがシンプルでなんとなく Redis っぽいさを感じる。(画像は link のスライドから)

f:id:aladhi:20150404165748p:plain

クラスタ機能に対応する実装ではこのエラーレスポンスをパースして自動的に接続するノードを切り替えることで透過的な操作を可能にしている。

link

if errv[0] == "MOVED" || errv[0] == "ASK"
    (...)
    newslot = errv[1].to_i
    node_ip,node_port = errv[2].split(":")
    (...)
else

このあたりの話はまだ読んでない Redis Cluster Specification に書いてありそう。

ノードの追加と re-shard

試しに1台ノードを追加してみる。コマンドは add-node new_host:new_port existing_host:existing_port だ。

~/Downloads/redis-3.0.0/src
❯ ./redis-trib.rb add-node 127.0.0.1:7004 127.0.0.1:7000
>>> Adding node 127.0.0.1:7004 to cluster 127.0.0.1:7000
(...)
[OK] New node added correctly.

~/Downloads/redis-3.0.0/src
❯ redis-cli -p 7000 cluster nodes
2b4114a2b99ba696950ad919196223eb25c9ede2 127.0.0.1:7000 myself,master - 0 0 1 connected 0-4095
968d6f7c12ae4c6c70239c59aafd89c302dfd427 127.0.0.1:7001 master - 0 1428129717741 2 connected 4096-8191
f368ccf3ca85a5007a34e8e3b93f3e37797055f6 127.0.0.1:7003 master - 0 1428129718766 4 connected 12288-16383
48da16af4dc5a15b844f2977eefb490ea7b9720e 127.0.0.1:7004 master - 0 1428129719281 0 connected
0e1301d83a174b598c6315167b868be93003e4c4 127.0.0.1:7002 master - 0 1428129718766 3 connected 8192-12287

クラスタに参加できだ。クラスタに参加しただけだとまだ hash slot は割り当てられない。割り当てられないだけでクライアントからのリクエストには応答できるようだ。

[23] pry(main)> redis = Redis.new(port: 7004)
=> #<Redis client v3.2.1 for redis://127.0.0.1:7004/0>
[24] pry(main)> redis.get('my_key')
Redis::CommandError: MOVED 13711 127.0.0.1:7003

新しく追加した port 7004 で listen しているノードに hash slot を割り当てる。コマンドの出力が長いのと対話的なやりとりがあるのでログに直接コメントを書いた。# で始まる行が加工したコメントで、別途見やすいように改行を追加してある。

# クラスタ内のノード。左がノードID。一番右が担当している hash slot 番号。
~/Downloads/redis-3.0.0/src
❯ redis-cli -p 7000 cluster nodes
2b4114a2b99ba696950ad919196223eb25c9ede2 127.0.0.1:7000 myself,master - 0 0 1 connected 0-4095
968d6f7c12ae4c6c70239c59aafd89c302dfd427 127.0.0.1:7001 master - 0 1428131195055 2 connected 4096-8191
f368ccf3ca85a5007a34e8e3b93f3e37797055f6 127.0.0.1:7003 master - 0 1428131194539 4 connected 12288-16383
48da16af4dc5a15b844f2977eefb490ea7b9720e 127.0.0.1:7004 master - 0 1428131193501 0 connected
0e1301d83a174b598c6315167b868be93003e4c4 127.0.0.1:7002 master - 0 1428131195561 3 connected 8192-12287

~/Downloads/redis-3.0.0/src
❯ ./redis-trib.rb reshard 127.0.0.1:7004

# (1) クラスタ内ノードのチェック
Connecting to node 127.0.0.1:7004: OK
Connecting to node 127.0.0.1:7003: OK
Connecting to node 127.0.0.1:7000: OK
Connecting to node 127.0.0.1:7002: OK
Connecting to node 127.0.0.1:7001: OK
>>> Performing Cluster Check (using node 127.0.0.1:7004)
M: 48da16af4dc5a15b844f2977eefb490ea7b9720e 127.0.0.1:7004
   slots: (0 slots) master
   0 additional replica(s)
M: f368ccf3ca85a5007a34e8e3b93f3e37797055f6 127.0.0.1:7003
   slots:12288-16383 (4096 slots) master
   0 additional replica(s)
M: 2b4114a2b99ba696950ad919196223eb25c9ede2 127.0.0.1:7000
   slots:0-4095 (4096 slots) master
   0 additional replica(s)
M: 0e1301d83a174b598c6315167b868be93003e4c4 127.0.0.1:7002
   slots:8192-12287 (4096 slots) master
   0 additional replica(s)
M: 968d6f7c12ae4c6c70239c59aafd89c302dfd427 127.0.0.1:7001
   slots:4096-8191 (4096 slots) master
   0 additional replica(s)
[OK] All nodes agree about slots configuration.

# (2) いくつ hash slot 動かすか質問される。
# re-shard 前は 16384 slot が各ノードへ4096ずつ割り当てられていた。
# 16384 / 5 がだいたい 3276 なので3276個を動かすことにする。
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
How many slots do you want to move (from 1 to 16384)? 3276

# (3) どのノードが今回動かす hash slot を受け取るか質問される。
# 新ノードのIDを指定する。
What is the receiving node ID? 48da16af4dc5a15b844f2977eefb490ea7b9720e

# (4) どのノードから hash slot を動かすか質問される。
# 今回はすべてのノードから平等に新ノードへ hash slot を動かしたいので `all` を指定する。
# ノードごとの性能が違ったりして、あるノードの負担を減らしたいときなどに個別指定が便利なのかもしれない。
Please enter all the source node IDs.
  Type 'all' to use all the nodes as source nodes for the hash slots.
  Type 'done' once you entered all the source nodes IDs.
Source node #1:all

# (5) Resharding plan が表示されるので眺めてから同意する。
eady to move 3276 slots.
  Source nodes:
    M: f368ccf3ca85a5007a34e8e3b93f3e37797055f6 127.0.0.1:7003
   slots:12288-16383 (4096 slots) master
   0 additional replica(s)
    M: 2b4114a2b99ba696950ad919196223eb25c9ede2 127.0.0.1:7000
   slots:0-4095 (4096 slots) master
   0 additional replica(s)
    M: 0e1301d83a174b598c6315167b868be93003e4c4 127.0.0.1:7002
   slots:8192-12287 (4096 slots) master
   0 additional replica(s)
    M: 968d6f7c12ae4c6c70239c59aafd89c302dfd427 127.0.0.1:7001
   slots:4096-8191 (4096 slots) master
   0 additional replica(s)
  Destination node:
    M: 48da16af4dc5a15b844f2977eefb490ea7b9720e 127.0.0.1:7004
   slots: (0 slots) master
   0 additional replica(s)
  Resharding plan:
    Moving slot 12288 from f368ccf3ca85a5007a34e8e3b93f3e37797055f6
    Moving slot 12289 from f368ccf3ca85a5007a34e8e3b93f3e37797055f6
(...)
Do you want to proceed with the proposed reshard plan (yes/no)? yes

# (6) あとはひたすら Resharding の様子が眺められる。

特に計測してないが、MBP (13, Early 2015) のマシンで30秒くらいで Resharding が終わった模様。実環境だとどれくらいだろうか...

cluster コマンドで各ノードを確認してみる。

~/Downloads/redis-3.0.0/src
❯ redis-cli -p 7000 cluster nodes
2b4114a2b99ba696950ad919196223eb25c9ede2 127.0.0.1:7000 myself,master - 0 0 1 connected 819-4095
968d6f7c12ae4c6c70239c59aafd89c302dfd427 127.0.0.1:7001 master - 0 1428131461381 2 connected 4915-8191
f368ccf3ca85a5007a34e8e3b93f3e37797055f6 127.0.0.1:7003 master - 0 1428131463435 4 connected 13107-16383
48da16af4dc5a15b844f2977eefb490ea7b9720e 127.0.0.1:7004 master - 0 1428131462415 5 connected 0-818 4096-4914 8192-9010 12288-13106
0e1301d83a174b598c6315167b868be93003e4c4 127.0.0.1:7002 master - 0 1428131462415 3 connected 9011-12287

後から追加した ID 48da16af4dc5a15b844f2977eefb490ea7b9720e のノードに無事新しく hash slot が割り当てられた。

まとめ

  • Redis cluster についてざっくり説明
  • クラスタのセットアップ
  • No proxies, redirection を確かめた
  • ノードの追加と Resharding を試した

次は master-slave 構成にしてみてノードのダウン時にどうするかやってみたい。

参考にしたリスト