2016.10.04

Redis clusterを使ってみた


こんにちは。次世代システム研究室のM.Mです。
現在、関わっている開発案件にてRedis Clusterを検証する機会があったので、今回はその際に実施した内容を共有したいと思います。
尚、今回利用するredisのバージョンは3.2.3になります。

目次

  1. 作成するクラスター構成について
  2. インストール
  3. 接続確認
  4. 障害時挙動確認
  5. Yii2からの利用について

1.作成するクラスター構成について

フィジビリティ確認の意味合いが強かったのでバランスは悪いですが、仮想サーバー2台にてredisをmaster 3ノード、slave 3ノードの計6ノードのクラスター構成でインストールおよび動作確認を行うこととしました。
構成イメージは以下の通り、1台のサーバーに複数のredisを動かす必要があるのでポートを分けて動かすことにしました。

■仮想サーバーA(CentOS6.7)
 redis port:6379 master
 redis port:6380 master
 redis port:6381 slave

■仮想サーバーB(CentOS6.7)
 redis port:6379 master
 redis port:6380 slave
 redis port:6381 slave

なぜmaster 3ノード、slave 3ノードでの検証になったのか?

master は最低3ノード必要になります。
master 2ノードでインストールしようとするとクラスター構成時に以下のようなエラーがでます。
>>> Creating cluster
*** ERROR: Invalid configuration for cluster creation.
*** Redis Cluster requires at least 3 master nodes.
*** This is not possible with 2 nodes and 0 replicas per node.
*** At least 3 nodes are required.
master3ノードのみでslaveはなくてもインストールできますが、masterダウン時もサービスを維持できるようにしたかったので今回はslaveもありとしました。


2.インストール

ソースからインストールします。
redisをインストールするツールやクラスターを作成するツールが備わっているので便利です。

まず仮想サーバーAにて

redisのインストール

# cd /usr/local/src
# wget http://download.redis.io/releases/redis-3.2.3.tar.gz
# tar xzf redis-3.2.3.tar.gz
# cd redis-3.2.3/
# make
# make install

# cd utils/
# ./install_server.sh 
install_server.shを実行すると対話形式でポート番号などを指定してインストールできます。
そのため今回のようにサーバー1台にポート番号を変えてインストールするというケースも簡単です。
以下がポート番号6379を指定してインストールした際の実際のログになります。
(ポート番号以外はすべて未入力のデフォルト)
# ./install_server.sh 
Welcome to the redis service installer
This script will help you easily set up a running redis server

Please select the redis port for this instance: [6379] 6379
Please select the redis config file name [/etc/redis/6379.conf] 
Selected default - /etc/redis/6379.conf
Please select the redis log file name [/var/log/redis_6379.log]
Selected default - /var/log/redis_6379.log
Please select the data directory for this instance [/var/lib/redis/6379] 
Selected default - /var/lib/redis/6379
Please select the redis executable path [/usr/local/bin/redis-server] 
Selected config:
Port           : 6379
Config file    : /etc/redis/6379.conf
Log file       : /var/log/redis_6379.log
Data dir       : /var/lib/redis/6379
Executable     : /usr/local/bin/redis-server
Cli Executable : /usr/local/bin/redis-cli
Is this ok? Then press ENTER to go on or Ctrl-C to abort.
Copied /tmp/6379.conf => /etc/init.d/redis_6379
Installing service...
Successfully added to chkconfig!
Successfully added to runlevels 345!
Starting Redis server...
Installation successful!
今回は1サーバーに3ノード作成するので、引き続きinstall_server.shを実行してポート番号6380、6381としてもインストールしていきます。

その後プロセスを確認すると以下のようにredisが起動していることを確認できます。
# ps -ef
root     19434     1  0 17:25 ?        00:00:00 /usr/local/bin/redis-server 127.0.0.1:6379
root     19463     1  0 17:26 ?        00:00:00 /usr/local/bin/redis-server 127.0.0.1:6380
root     19491     1  0 17:26 ?        00:00:00 /usr/local/bin/redis-server 127.0.0.1:6381
続いて、仮想サーバーBについても上記と同様の作業を行います。
結果、
・仮想サーバーAにてredisが6379,6380,6381ポートで3ノードインストールされている
・仮想サーバーBにてredisが6379,6380,6381ポートで3ノードインストールされている
状態となります。

redis clusterへ設定変更

redis clusterを有効にするためは、redisの設定ファイルの内容を何箇所か変更する必要があります。
以下が変更内容になります。
# vi /etc/redis/6379.conf

#bind 127.0.0.1     -> コメントアウトする
cluster-enabled yes   -> コメントをはずす
appendonly yes      -> yesに変更する
protected-mode no    -> noに変更する

上記変更を仮想サーバーAの6379,6380,6381ポートのredisノード、仮想サーバーBの6379,6380,6381ポートのredisノード、すべての設定ファイルに対して同様の変更を行いredisの再起動を行います。
再起動してプロセスを確認すると
# /etc/init.d/redis_6379 restart
# /etc/init.d/redis_6380 restart
# /etc/init.d/redis_6381 restart

# ps -ef
root     19506     1  0 17:35 ?        00:00:00 /usr/local/bin/redis-server *:6379 [cluster] 
root     19516     1  0 17:35 ?        00:00:00 /usr/local/bin/redis-server *:6380 [cluster] 
root     19526     1  0 17:35 ?        00:00:00 /usr/local/bin/redis-server *:6381 [cluster] 

設定変更前と異なり、[cluster]との記載があることが分かります。
ただ現時点ではクラスター構成の準備が整ったのみでクラスター構成にはなっていません。
続いてクラスター構成にしていきます。

クラスター構成にする

クラスター構成にする作業もインストール時に実施したようにツールが用意されているのでそのツールを実行するのみとなります。
また仮想サーバーA、仮想サーバーBの2台構成にしていますが、作業するのは1台でのみで問題ありません。以下の手順は仮想サーバーAのみで実施しております。

クラスター構成にするには redis-trib.rbというスクリプトを利用します。
えっ、rubyもいるの?と思いましたが、、このスクリプトを使うにはrubyもインストールが必要となります。
以下はrubyのインストール例です。
# yum -y install gcc cc zlib-devel openssl-devel readline-devel libffi-devel
#
# cd /usr/local/src
# wget http://cache.ruby-lang.org/pub/ruby/2.3/ruby-2.3.1.tar.gz 
# tar zxvf ruby-2.3.1.tar.gz
# cd ruby-2.3.1
# ./configure
# make
# make install
#
# gem install redis
これでrubyのインストールおよびrubyからredisが使えるようになったので、実際にクラスターの作成を行っていきます。
# cd /usr/local/src/redis-3.2.3/src
# ./redis-trib.rb create --replicas 1 192.168.33.21:6379 192.168.33.22:6379 192.168.33.21:6380 192.168.33.22:6380 192.168.33.21:6381 192.168.33.22:6381

構成イメージ
■仮想サーバーA(CentOS6.7) :192.168.33.21
 redis port:6379 master
 redis port:6380 master
 redis port:6391 slave

■仮想サーバーB(CentOS6.7) :192.168.33.22
 redis port:6379 master
 redis port:6380 slave
 redis port:6391 slave

実行すると以下のような結果が得られます。
# ./redis-trib.rb create --replicas 1 192.168.33.21:6379 192.168.33.22:6379 192.168.33.21:6380 192.168.33.22:6380 192.168.33.21:6381 192.168.33.22:6381
>>> Creating cluster
>>> Performing hash slots allocation on 6 nodes...
Using 3 masters:
192.168.33.21:6379
192.168.33.22:6379
192.168.33.21:6380
Adding replica 192.168.33.22:6380 to 192.168.33.21:6379
Adding replica 192.168.33.21:6381 to 192.168.33.22:6379
Adding replica 192.168.33.22:6381 to 192.168.33.21:6380
M: 7edf96394d3ba7bc653dfab86a6bf18d1e0dcc2b 192.168.33.21:6379
   slots:0-5460 (5461 slots) master
M: 3b7a0482e83b485d8530e8d44cf945520a4e12ef 192.168.33.22:6379
   slots:5461-10922 (5462 slots) master
M: c367b2fb5a0c0cd86ae1d2c85a27d7ca68b6ce18 192.168.33.21:6380
   slots:10923-16383 (5461 slots) master
S: 03d37f428ae60e6205663d0d7927c3e78ba9d527 192.168.33.22:6380
   replicates 7edf96394d3ba7bc653dfab86a6bf18d1e0dcc2b
S: 36c9b04a355f79f78b60d6407af3b70cda0d8a0a 192.168.33.21:6381
   replicates 3b7a0482e83b485d8530e8d44cf945520a4e12ef
S: b14114c7f9809bb7f9f11d08b6dabb1502bcde5f 192.168.33.22:6381
   replicates c367b2fb5a0c0cd86ae1d2c85a27d7ca68b6ce18
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 192.168.33.21:6379)
M: 7edf96394d3ba7bc653dfab86a6bf18d1e0dcc2b 192.168.33.21:6379
   slots:0-5460 (5461 slots) master
M: 3b7a0482e83b485d8530e8d44cf945520a4e12ef 192.168.33.22:6379
   slots:5461-10922 (5462 slots) master
M: c367b2fb5a0c0cd86ae1d2c85a27d7ca68b6ce18 192.168.33.21:6380
   slots:10923-16383 (5461 slots) master
M: 03d37f428ae60e6205663d0d7927c3e78ba9d527 192.168.33.22:6380
   slots: (0 slots) master
   replicates 7edf96394d3ba7bc653dfab86a6bf18d1e0dcc2b
M: 36c9b04a355f79f78b60d6407af3b70cda0d8a0a 192.168.33.21:6381
   slots: (0 slots) master
   replicates 3b7a0482e83b485d8530e8d44cf945520a4e12ef
M: b14114c7f9809bb7f9f11d08b6dabb1502bcde5f 192.168.33.22:6381
   slots: (0 slots) master
   replicates c367b2fb5a0c0cd86ae1d2c85a27d7ca68b6ce18
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
ちょっと見にくいですが、上記4行目から22行目あたりを見るとmaster, slaveとしてどのサーバーIPアドレス、ポートが割り当てられたか判断できます。
また、slotsの割り当てがどうなっているかも判断できます。
slotsの値はredisに登録する値のkeyのhash値らしく、keyの値によって登録されるredisのノードが異なります。
slotsの最大値は16384であり、それをmaster3ノードで分割したため、
slots:0-5460 (5461 slots) master
slots:5461-10922 (5462 slots) master
slots:10923-16383 (5461 slots) master
のように3等分されています。(変更もできるようですがデフォルトでは)

クラスターの構成確認は以下のコマンドで確認が可能です。
#redis-cli -p 6379 cluster nodes

b14114c7f9809bb7f9f11d08b6dabb1502bcde5f 192.168.33.22:6381 slave c367b2fb5a0c0cd86ae1d2c85a27d7ca68b6ce18 0 1474863289192 6 connected
c367b2fb5a0c0cd86ae1d2c85a27d7ca68b6ce18 192.168.33.21:6380 master - 0 1474863285122 3 connected 10923-16383
7edf96394d3ba7bc653dfab86a6bf18d1e0dcc2b 192.168.33.21:6379 myself,master - 0 0 1 connected 0-5460
36c9b04a355f79f78b60d6407af3b70cda0d8a0a 192.168.33.21:6381 slave 3b7a0482e83b485d8530e8d44cf945520a4e12ef 0 1474863283901 5 connected
03d37f428ae60e6205663d0d7927c3e78ba9d527 192.168.33.22:6380 slave 7edf96394d3ba7bc653dfab86a6bf18d1e0dcc2b 0 1474863288210 4 connected
3b7a0482e83b485d8530e8d44cf945520a4e12ef 192.168.33.22:6379 master - 0 1474863287181 2 connected 5461-10922
以上でインストールは完了です。

■参考
実際には設定ファイルのbind、protected-modeの変更が不十分のままクラスター作成のスクリプトを実行して、結果が返ってこず不完全なクラスターになったりして何回かやり直しました。
以下のパスにクラスター関連の設定があるのでそれをまるっと削除すれば、redis-trib.rb createからやり直すことができました。
/var/lib/redis/[6379-6381]/*

redis-trib.rb create –replicas 1
といったように–replicasオプションを付けることでslaveも一緒に作成されますが、どうもmasterとslaveの組み合わせは引数に指定した順番になるわけではなさそうです。
その場合は以下のように先にmasterだけ作成して後からslaveを追加するという方法で意図したmasterとslaveの組み合わせにできます。
まずmasterだけ作る
# ./redis-trib.rb create 192.168.33.21:6379 192.168.33.22:6379 192.168.33.21:6380

master-idを調べる
# redis-cli cluster nodes

slaveを追加する
192.168.33.21:6379のslaveとして192.168.33.22:6380を追加する
# ./redis-trib.rb add-node --slave --master-id 7edf96394d3ba7bc653dfab86a6bf18d1e0dcc2b 192.168.33.22:6380 192.168.33.21:6379


3.接続確認

コマンドライン(redis-cli)で接続確認

適当に値をセットしてみる。
■仮想サーバーA(192.168.33.21)のredis:6379に対してtestというキーに123という値をセットしてみる。
# redis-cli -h 192.168.33.21 -p 6379
192.168.33.21:6379> set test 123
(error) MOVED 6918 192.168.33.22:6379
エラーが出ましたが・・・。
MOVEDとメッセージがあるので、仮想サーバーB(192.168.33.22)のredis:6379を確認してみる。
# redis-cli -h 192.168.33.22 -p 6379
192.168.33.22:6379> get test
(nil)   -> 別に値が設定されているわけでもない

192.168.33.22:6379> set test 123
OK   -> ここだと成功した
では再度、異なるキーで値をセットしてみる。
■仮想サーバーA(192.168.33.21)のredis:6379に対してtest1というキーに456という値をセットしてみる。
# redis-cli -h 192.168.33.21 -p 6379
192.168.33.21:6379> set test1 456
OK   -> 先ほどtestだと失敗したがtest1だと成功した
ということはおそらく仮想サーバーB(192.168.33.22)のredis:6379に対してset test1 456とやると(error) MOVEDになるんだろうなと。
# redis-cli -h 192.168.33.22 -p 6379
192.168.33.22:6379> set test1 789
(error) MOVED 4768 192.168.33.21:6379   -> やはり。エラー。
どうやらセットするキーの値から算出されるslotsの値が割り当てられたノードでsetコマンドを実施すれば成功するが、違うノードに割り当てられたslotsの値の場合はエラーになるようです。
うーむ。ようやくここでクラスターオプションが必要なことを知る。。(理解が少し深まったのでよしとする)

コマンドライン(redis-cli)にクラスタオプション(-c)を付けて接続確認

クラスタオプションを付けて接続する。-cを付けるだけ。
■仮想サーバーA(192.168.33.21)のredis:6379に対してtestというキーに123という値をセットしてみる。
※クラスタオプションなしでは(error) MOVEDとなったケース
# redis-cli -h 192.168.33.21 -p 6379 -c
192.168.33.21:6379> set test 123
-> Redirected to slot [6918] located at 192.168.33.22:6379
OK
192.168.33.22:6379> 
仮想サーバーB(192.168.33.22)のポート6379にリダイレクトされて値がセットされました。
クラスターオプションをつけることで、自身が割り当てられているslotsに当てはまるkey以外を操作しようとすると、そのslotsが割り当てられているredisノードにリダイレクトされて処理されるようです。


4.障害時挙動確認

先ほどの接続確認で仮想サーバーA(192.168.33.21)のredis:6379に対してtestというキーに123という値をセットしましたが、そこからtestというキーは slots:6918の 192.168.33.22:6379 に割り当てられていることが分かります。

では、それぞれのノードからget testをするとどうなるか確認します。
■仮想サーバーA(192.168.33.21)のredis:6379
# redis-cli -h 192.168.33.21 -p 6379 -c
192.168.33.21:6379> get test
-> Redirected to slot [6918] located at 192.168.33.22:6379
"123"
192.168.33.22:6379> quit

■仮想サーバーA(192.168.33.21)のredis:6380
# redis-cli -h 192.168.33.21 -p 6380 -c
192.168.33.21:6380> get test
-> Redirected to slot [6918] located at 192.168.33.22:6379
"123"
192.168.33.22:6379> quit

■仮想サーバーA(192.168.33.21)のredis:6381
# redis-cli -h 192.168.33.21 -p 6381 -c
192.168.33.21:6381> get test
-> Redirected to slot [6918] located at 192.168.33.22:6379
"123"
192.168.33.22:6379> quit

■仮想サーバーB(192.168.33.22)のredis:6379
# redis-cli -h 192.168.33.22 -p 6379 -c
192.168.33.22:6379> get test
"123"
192.168.33.22:6379> quit

 testというキーのslotsが割り当てられたredisノードの場合のみリダイレクトはされていません。

■仮想サーバーB(192.168.33.22)のredis:6380
# redis-cli -h 192.168.33.22 -p 6380 -c
192.168.33.22:6380> get test
-> Redirected to slot [6918] located at 192.168.33.22:6379
"123"
192.168.33.22:6379> quit

■仮想サーバーB(192.168.33.22)のredis:6381
# redis-cli -h 192.168.33.22 -p 6381 -c
192.168.33.22:6381> get test
-> Redirected to slot [6918] located at 192.168.33.22:6379
"123"
192.168.33.22:6379> quit


では、testというキーのslotsが割り当てられている仮想サーバーB(192.168.33.22)のredis:6379を停止して上記と同じ内容を試してみます。

■停止実施
仮想サーバーB(192.168.33.22)にて
# /etc/init.d/redis_6379 stop

■クラスターの状況確認
# redis-cli cluster nodes

b14114c7f9809bb7f9f11d08b6dabb1502bcde5f 192.168.33.22:6381 slave c367b2fb5a0c0cd86ae1d2c85a27d7ca68b6ce18 0 1474865589370 6 connected
c367b2fb5a0c0cd86ae1d2c85a27d7ca68b6ce18 192.168.33.21:6380 master - 0 1474865588361 3 connected 10923-16383
7edf96394d3ba7bc653dfab86a6bf18d1e0dcc2b 192.168.33.21:6379 myself,master - 0 0 1 connected 0-5460
36c9b04a355f79f78b60d6407af3b70cda0d8a0a 192.168.33.21:6381 master - 0 1474865590377 7 connected 5461-10922
03d37f428ae60e6205663d0d7927c3e78ba9d527 192.168.33.22:6380 slave 7edf96394d3ba7bc653dfab86a6bf18d1e0dcc2b 0 1474865591429 4 connected
3b7a0482e83b485d8530e8d44cf945520a4e12ef 192.168.33.22:6379 master,fail - 1474865363325 1474865361803 2 disconnected


# tail /var/log/redis_6381.log 

10435:S 26 Sep 13:49:39.099 * FAIL message received from 7edf96394d3ba7bc653dfab86a6bf18d1e0dcc2b about 3b7a0482e83b485d8530e8d44cf945520a4e12ef
10435:S 26 Sep 13:49:39.100 # Cluster state changed: fail
10435:S 26 Sep 13:49:39.112 # Start of election delayed for 785 milliseconds (rank #0, offset 3126).
10435:S 26 Sep 13:49:39.829 * Connecting to MASTER 192.168.33.22:6379
10435:S 26 Sep 13:49:39.829 * MASTER <-> SLAVE sync started
10435:S 26 Sep 13:49:39.829 # Error condition on socket for SYNC: Connection refused
10435:S 26 Sep 13:49:39.933 # Starting a failover election for epoch 7.
10435:S 26 Sep 13:49:39.952 # Failover election won: I'm the new master.
10435:S 26 Sep 13:49:39.952 # configEpoch set to 7 after successful failover
10435:M 26 Sep 13:49:39.952 * Discarding previously cached master state.
10435:M 26 Sep 13:49:39.952 # Cluster state changed: ok

上記8行目を見ると、停止した仮想サーバーB(192.168.33.22)のredis:6379はmaster,failとなってdisconnectedになっています。
また仮想サーバーB(192.168.33.22)のredis:6379のslaveだった仮想サーバーA(192.168.33.21)のredis:6381がmasterに昇格しているのが分かります。

※どれがslaveだったかは上記インストール手順の最後にあるクラスター構成の確認をしているところにある以下の部分を見れば分かります。
36c9b04a355f79f78b60d6407af3b70cda0d8a0a 192.168.33.21:6381 slave 3b7a0482e83b485d8530e8d44cf945520a4e12ef 0 1474863283901 5 connected
..
3b7a0482e83b485d8530e8d44cf945520a4e12ef 192.168.33.22:6379 master - 0 1474863287181 2 connected 5461-10922
※仮想サーバーA(192.168.33.21)のredis:6381のログを確認すると「Failover election won: I’m the new master.」と出力もされています。


では、停止した状態でそれぞれのノードからget testをするとどうなるか確認します。

■仮想サーバーA(192.168.33.21)のredis:6379
# redis-cli -h 192.168.33.21 -p 6379 -c
192.168.33.21:6379> get test
-> Redirected to slot [6918] located at 192.168.33.21:6381
"123"
192.168.33.21:6381> quit

■仮想サーバーA(192.168.33.21)のredis:6380
# redis-cli -h 192.168.33.21 -p 6380 -c
192.168.33.21:6380> get test
-> Redirected to slot [6918] located at 192.168.33.21:6381
"123"
192.168.33.21:6381> quit

■仮想サーバーA(192.168.33.21)のredis:6381
# redis-cli -h 192.168.33.21 -p 6381 -c
192.168.33.21:6381> get test
"123"
192.168.33.21:6381> quit

■仮想サーバーB(192.168.33.22)のredis:6379
# redis-cli -h 192.168.33.22 -p 6379 -c
Could not connect to Redis at 192.168.33.22:6379: Connection refused
Could not connect to Redis at 192.168.33.22:6379: Connection refused
not connected> quit

■仮想サーバーB(192.168.33.22)のredis:6380
# redis-cli -h 192.168.33.22 -p 6380 -c
192.168.33.22:6380> get test
-> Redirected to slot [6918] located at 192.168.33.21:6381
"123"
192.168.33.21:6381> quit

■仮想サーバーB(192.168.33.22)のredis:6381
# redis-cli -h 192.168.33.22 -p 6381 -c
192.168.33.22:6381> get test
-> Redirected to slot [6918] located at 192.168.33.21:6381
"123"
192.168.33.21:6381> quit

停止したredisノードにはConnection refusedと表示されて当然つながりません。
他のredisノードでは、停止前と違い、マスターに昇格した仮想サーバーA(192.168.33.21)のredis:6381にリダイレクトされていることが分かります。

■停止した仮想サーバーB(192.168.33.22)のredis:6379を起動すると
3b7a0482e83b485d8530e8d44cf945520a4e12ef 192.168.33.22:6379 master,fail - 1474865363325 1474865361803 2 disconnected
↓
3b7a0482e83b485d8530e8d44cf945520a4e12ef 192.168.33.22:6379 slave 36c9b04a355f79f78b60d6407af3b70cda0d8a0a 0 1474865953325 7 connected
master,failだったステータスがslaveになります。
復旧したからといってmasterに戻るわけではありません。

ダウンしたredisノードにアクセスしたら、別のredisノードにアクセスするといったことは必要そうだが、masterがダウンしてもslaveがmasterに自動で昇格するので、切り替わりタイミングは別としてredisを使った処理は継続できそうです。


5.Yii2からの利用について

現在開発しているYii2フレームワークを利用したPHPアプリケーションから今回作成したredis clusterに接続して実際に使えるか試してみました。
Yii2フレームワークにはすでにyii2-redisというライブラリが存在しており簡単に利用できるようになっています。
ただ残念ながらredis clusterには対応していないようで、上記のクラスタオプションなしで接続確認したときと同様、-MOVED 6918 192.168.33.22:6379というようなレスポンスが返ってきてエラーとなってしまいます。
MOVEDというレスポンスが返ってきたらMOVEDの後方に記載されたIPアドレスとポート番号に接続し直すようにすればどのredisノードにアクセスしても処理はできそうだし、簡単に実装もできそうではあります。
ただ、エラーレスポンスだった場合はどうこうするという対応方法があまり納得がいかないので、redis cluster関連のライブラリなどもう少し調べたいと思います。


最後に

次世代システム研究室では、アプリケーション開発や設計を行うアーキテクトを募集しています。アプリケーション開発者の方、次世代システム研究室にご興味を持って頂ける方がいらっしゃいましたら、ぜひ 募集職種一覧 からご応募をお願いします。

皆さんのご応募をお待ちしています。