redis clusterを自力で構築してみた

redis clusterを構築する際は、redis-tribという便利なツールがありますが、ここではredis-tribを使わずに自力でclusterを構築する手順を書きます。
**※redis-tribを使った方が安全に、簡単に構築できますし、いろんな機能があるのでちゃんと作るときはredis-tribを使うのを強く推奨します。**

# redisを3台起動する

[[redis clusterの作り方]](http://qiita.com/sawada_masahiko/items/7b7956373b1a159e20c4)を参考に3台起動してください。(参照先URLの手順は3まで行います)

ここでは7000～7002 portを使って3台でクラスタを構築します。

redis.confにて`cluster-enabled yes`とすると`cluster-config-file`に設定されたファイル名のクラスタ設定ファイルが作られます。
ただ起動しただけだと、こんな感じ

```lang:7000/nodes.conf
2e6a31d530b15d79cf2bf58550899a7073ca5704 :0 myself,master - 0 0 0 connected
vars currentEpoch 0 lastVoteEpoch 0
```

#SLOTの計算

redis clusterはキーのHASH値を元にデータが分配されます。GETやSETをした時は、キーのHASH値からどのノードにほしい情報が入っているか探し、そのノードへリダイレクトします。
それぞれのノードがどのHASH値のバリューを持つかを指定するのがSLOTです。
ここでは、各ノードが持つSLOT(HASH値)を計算します。

## redis clusterのHASHの最大値は16383

今回は3台でクラスタを構築するので、それぞれのノードが持つSLOTの範囲は、`16383 / 3 = 5461`となります。
つまり、

|ノード|SLOTの範囲|
|:---:|:--------:|
|7000|0 ～ 5461|
|7001|5462  ～ 10922|
|7002|10933 ～ 16383|


# SLOTの割り当て

各ノードにSLOTを割り当てていきます。SLOTの割り当てにはCLUSTER ADDSLOTS <SLOT番号> コマンドを使用します。
例えば0番のSLOT一つを割り当てるときは、

```lang:
$ redis-cli -p 7000 CLUSTER ADDSLOT 0
OK
```

とします。

~~ただ、CLUSTER ADDSLOTSコマンド`は一つずつしかSLOTを割り当てられない(←たぶん)ので、さすがに手作業で16383はきつい。~~
~~ということで、以下のコマンドで入れました。~~

`CLUSTER ADDSLOTS 1 2 3 4 ..`とすることで１コマンドで複数のSLOTを割り当てることができます。
とわいえ、全部列挙するのはつらいので、以下のコマンドをお使いください。

```bash
$ for slot in `seq 0 5461`;
do
    redis-cli -p 7000 CLUSTER ADDSLOTS $slot; > /dev/null

    # 進捗状況を定期的に表示する
    if [ $(($slot % 1000)) == 0 ]; then
        echo '$slot: '$slot
    fi
done
```

上記のような感じで他のノードへもSLOTを割り当てます。

## SLOTを割り当てた後のnodes.confの状態

```lang:7000/nodes.conf
2e6a31d530b15d79cf2bf58550899a7073ca5704 :7000 myself,master - 0 0 0 connected 0-5461
vars currentEpoch 0 lastVoteEpoch 0
```

一行目の一番右が`0-5461`となっているのが7000 portのノードが割り当てられているSLOTです。
`0番～5461番`までというのを示しています。

## 連番ではなく飛び飛びのSLOTを割り当てると?
今回はSLOTを連番で割り当てたので、nodes.confは`0-5461`という風になります。なので、redisのSLOTはもしかして連番でないと割り当てられない？と思ったので試してみました。

```lang:5462番と5424番のSLOTを割り当ててた後のnodes.conf
0efa999a0f4a9acd7f4af505d8677246c93e8ae8 :7001 myself,master - 0 0 0 connected 5462 5464
vars currentEpoch 0 lastVoteEpoch 0
```

```lang:さらに5463番を割り当てた後のnodes.conf
0efa999a0f4a9acd7f4af505d8677246c93e8ae8 :7001 myself,master - 0 0 0 connected 5462-5464
vars currentEpoch 0 lastVoteEpoch 0
```

ということで、連番の時だけ`XXX-YYY`という形式になるんですねー

## すべてのSLOTを割り当てた後のnodes.conf
7000, 7001, 7002すべてのノードにSLOTを割り当てたとのnodes.confの状態は以下のようになります。

```lang:7000のnodes.conf
2e6a31d530b15d79cf2bf58550899a7073ca5704 :7000 myself,master - 0 0 0 connected 0-5461
vars currentEpoch 0 lastVoteEpoch 0
```

```lang:7001のnodes.conf
0efa999a0f4a9acd7f4af505d8677246c93e8ae8 :7001 myself,master - 0 0 0 connected 5462-10922
vars currentEpoch 0 lastVoteEpoch 0
```

```lang:7002のnodes.conf
87651510d1a7e22eb8282a027e03f56486eaf9f5 :7002 myself,master - 0 0 0 connected 10923-16383
vars currentEpoch 0 lastVoteEpoch 0
```

# クラスタリング
SLOTの割り当てが終わったので、次は3台をつなげるクラスタリングをします。
クラスタリングでは「ある基点となるノード一台(基点ノード)を決めて、そのノード以外から基点ノードへ接続要求を出す」という感じでつなぎます。

|ノード|役割|
|:---:|:--:|
|7000|基点ノード|
|7001|接続要求を7000に出す|
|7002|接続要求を7000に出す|

## CLUSTER MEET
クラスタリングの接続要求は`CLUSTER MEET <ノードのIP> <ノードのPort>コマンド`を使用します。
まずは、7001 portのノードから7000 portのノードへ`CLUSTER MEET`を送ります。

```lang:
$ redis-cli -p 7001 CLUSTER MEET 127.0.0.1 7000
OK
```

そうするとnodes.confの状態は、

```lang:7000/nodes.conf
0efa999a0f4a9acd7f4af505d8677246c93e8ae8 127.0.0.1:7001 master - 0 1422498533660 1 connected 5462-10922
2e6a31d530b15d79cf2bf58550899a7073ca5704 127.0.0.1:7000 myself,master - 0 0 0 connected 0-5461
vars currentEpoch 1 lastVoteEpoch 0
```

```lang:7001/nodes.conf
2e6a31d530b15d79cf2bf58550899a7073ca5704 127.0.0.1:7000 master - 0 1422498533152 0 connected 0-5461
0efa999a0f4a9acd7f4af505d8677246c93e8ae8 127.0.0.1:7001 myself,master - 0 0 1 connected 5462-10922
vars currentEpoch 1 lastVoteEpoch 0
```

となり、それぞれのnodes.confへ一つエントリが増えたことがわかります。

そして、7002 portのノードから7000 portのノードへ`CLUSTER MEET`を送ります。

```lang:
$ redis-cli -p 7002 CLUSTER MEET 127.0.0.1 7000
OK
```

最終的にnodes.confは以下のようになります。

```lang:7000/nodes.conf
0efa999a0f4a9acd7f4af505d8677246c93e8ae8 127.0.0.1:7001 master - 0 1422498614878 1 connected 5462-10922
2e6a31d530b15d79cf2bf58550899a7073ca5704 127.0.0.1:7000 myself,master - 0 0 2 connected 0-5461
87651510d1a7e22eb8282a027e03f56486eaf9f5 127.0.0.1:7002 master - 0 1422498614207 0 connected 10923-16383
vars currentEpoch 2 lastVoteEpoch 0
```

ちなみに、`CLUSTER INFO`の結果は、

```lang:
CLUSTER INFO
cluster_state:ok
cluster_slots_assigned:16384
cluster_slots_ok:16384
cluster_slots_pfail:0
cluster_slots_fail:0
cluster_known_nodes:3
cluster_size:3
cluster_current_epoch:2
cluster_my_epoch:1
cluster_stats_messages_sent:253
cluster_stats_messages_received:253
```

となります。

# 動作確認
`CLUSTER INFO`の`cluster_state`も`ok`となっていますし、なんとなくredis clusterができたっぽいところで、
簡単に動作確認をしてみます。
**クラスタに対してredis-cliをするときは-cオプションを忘れずに**

```lang:動作確認
$ redis-cli -c -p 7000
127.0.0.1:7000> set num:1 1
-> Redirected to slot [15698] located at 127.0.0.1:7002
OK
127.0.0.1:7002> set num:2 2
-> Redirected to slot [3377] located at 127.0.0.1:7000
OK
127.0.0.1:7000> get num:1
-> Redirected to slot [15698] located at 127.0.0.1:7002
"1"
```

リダイレクトもされて、ちゃんと動くことが確認できました。

`redis-trib.rb create`を使った時と同じことができました。