読者です 読者をやめる 読者になる 読者になる

Akatsuki Hackers Lab | 株式会社アカツキ(Akatsuki Inc.)

Akatsuki Hackers Labは株式会社アカツキが運営しています。

5分で分かるRedis Clusterの構築方法

はじめに

Developpers Summit 2016で「大規模Redisサーバ縮小化の戦い」というテーマで発表してきました。

Redisのdumpファイルを取得して、それらをマージする方法や、Redis内で使用するdb数を増やせば、接続数も増えていく、といった話をしました。 特にAWS上でRedisを運用する場合、ElastiCacheの接続数上限は変更できないことは見落としがちなポイントなので、サーバを何十台もスケールアウトする人たちにとって役に立つノウハウが共有できたのではないでしょうか。

当日はネタスライドを山程仕込んで 会場は大爆笑だったのですがslideshareではネタスライドは割愛しております。

今回のお話

デブサミのスライドでは、ほとんどの話が縮小についての話だったので、今回はRedisの信頼性について考えてみたいと思います。

元々、Redisの縮小計画は

の2つの目的がありました。

サーバ台数を64台から8台に縮小して、その後、冗長構成(16台)にする計画を立てていました。64台の状態でも冗長化はできたのですが、冗長構成で128台運用はいくらなんでもコスト的に厳しかったため、泣く泣くシングル構成を許容して64台運用をしていました。これらを縮小化することで、冗長化に踏み出せました。

Redisの信頼性向上施策といえば

さて、Redisの信頼性向上施策と言えば、以下の設定が思い付きます。

マルチAZ

AWSのElastiCacheでRedisを運用していると、一番最初に思いつく信頼性向上の施策はマルチAZではないでしょうか。 version2.8.6以降でマルチAZにすることで、プライマリノードの自動フェイルオーバーという恩恵が受けられます。今回、Redisを縮小できたおかげで、我々も各RedisノードにマルチAZでスレーブを構築しました。

AOF(Append-Only File)

こちらも信頼性向上の施策の一つで、キャッシュデータを変更するすべてのコマンドを AOFファイルに書き込みます。DBの更新ログみたいなものです。こちらは自サーバ内に書き込みを行うため、物理障害に対応できません。

Redis Cluster

Redisは2015/4にver3がリリースされて、クラスタリングをネイティブでサポートしております。もうすぐリリースされて1年が経過するところですが、AWSのElastiCacheはRedisのver3以降がまだ対応していないため、我々のシステムでもまだ導入しておりません。とは言え、そろそろAWSでRedisのver3が対応してもおかしくはありません。

ちょうどいい機会だったので、クラスタリングについて触ってみました。

ドキュメントの確認

Redis ClusterのドキュメントはRedis Cluster Specificationです。 このドキュメントの中で最も試してみたい機能が Replica Migrationです。ドキュメントを一部抜粋すると、以下のような記載があります。

Replica Migration

  • A, B, Cのmasterに、A1, B1, C1, C2というslaveが構築されている状態(Cだけslaveが2台)があるとします。
  • Aが死ぬと、A1がmasterに昇格し、C2がA1のslaveとして移動します。
  • その後、またしてもA1が死にます。
  • C2がA1に代わってmasterに昇格し、クラスタは継続された状態のまま運用できます。

なんと、惚れるじゃありませんか!せっかくなので試してみましょう。

試してみた

Redis cluster tutorialを参考に、クラスタを構築してみました。 EC2上で7つのRedisを起動し、各portを7000〜7006で設定しております。 これらの7ノードでclusterを組み、7000, 7001, 7002をmaster, それ以外をslaveとしております。

Redis起動

redisのversionは以下のとおりです。

$ redis-server -v
Redis server v=3.0.7 sha=00000000:0 malloc=jemalloc-3.6.0 bits=64 build=5ece30e075eed8d2
$ redis-cli -v
redis-cli 3.0.7

ディレクトリを作成して

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

ディレクトリに/etc/redis.confをコピーしてきて、以下の設定に変更します。

port 7000
cluster-enabled yes
cluster-config-file nodes.conf
cluster-node-timeout 5000
appendonly yes
daemonize yes
loglevel debug
logfile /home/ec2-user/cluster-test/7000/redis.log
cluster-config-file /home/ec2-user/cluster-test/7000/nodes-6379.conf

各redisを起動し、redis.logにエラーが出力されていないことを確認します。

$ sudo redis-server ./7000/redis.conf
$ sudo redis-server ./7001/redis.conf
$ sudo redis-server ./7002/redis.conf
$ sudo redis-server ./7003/redis.conf
$ sudo redis-server ./7004/redis.conf
$ sudo redis-server ./7005/redis.conf
$ sudo redis-server ./7006/redis.conf
$ ps aux | grep redis | grep -v grep
root 23180 0.0 0.4 142808 4312 ? Ssl 11:20 0:02 redis-server 127.0.0.1:7000 [cluster]
root 23189 0.0 0.4 142808 4308 ? Ssl 11:22 0:01 redis-server 127.0.0.1:7001 [cluster]
root 23197 0.0 0.4 142808 4228 ? Ssl 11:22 0:01 redis-server 127.0.0.1:7002 [cluster]
root 23202 0.0 0.4 142808 4184 ? Ssl 11:22 0:01 redis-server 127.0.0.1:7003 [cluster]
root 23207 0.0 0.4 142808 4256 ? Ssl 11:22 0:01 redis-server 127.0.0.1:7004 [cluster]
root 23212 0.0 0.4 142808 4148 ? Ssl 11:22 0:01 redis-server 127.0.0.1:7005 [cluster]
root 23217 0.0 0.4 142808 4160 ? Ssl 11:22 0:01 redis-server 127.0.0.1:7006 [cluster]
$

起動できました。

上記の設定でRedisを起動すると、nodes-6379.confが出力されるので、中身を確認すると、port7000〜7006の全てのRedisがmasterとして認識されております。

$ cat 7000/nodes-6379.conf
8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf :0 myself,master - 0 0 0 connected
vars currentEpoch 0 lastVoteEpoch 0
$

この時点ではクラスタ設定がONになっているだけで、各ノードはお互いを認識していません。

クラスタ構築

redis-tribを使用してクラスタを作成します。

$ ./redis-trib.rb create 127.0.0.1:7000 127.0.0.1:7001 127.0.0.1:7002
>>> Creating cluster
>>> Performing hash slots allocation on 3 nodes...
Using 3 masters:
127.0.0.1:7000
127.0.0.1:7001
127.0.0.1:7002
M: 8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf 127.0.0.1:7000
slots:0-5460 (5461 slots) master
M: eb6c6a4924cd449dcac420159cab592bc7dc8a4e 127.0.0.1:7001
slots:5461-10922 (5462 slots) master
M: 6082772a27ae6465b9f3d26959e1de5991b41356 127.0.0.1:7002
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:7000)
M: 8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf 127.0.0.1:7000
slots:0-5460 (5461 slots) master
M: eb6c6a4924cd449dcac420159cab592bc7dc8a4e 127.0.0.1:7001
slots:5461-10922 (5462 slots) master
M: 6082772a27ae6465b9f3d26959e1de5991b41356 127.0.0.1:7002
slots:10923-16383 (5461 slots) master
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.

これでクラスタが組まれ、以下の状態になりました。

$ cat 7000/nodes-6379.conf
6082772a27ae6465b9f3d26959e1de5991b41356 127.0.0.1:7002 master - 1458646033493 1458646032693 3 connected 10923-16383
eb6c6a4924cd449dcac420159cab592bc7dc8a4e 127.0.0.1:7001 master - 0 1458646032693 2 connected 5461-10922
8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf 127.0.0.1:7000 myself,master - 0 0 1 connected 0-5460
vars currentEpoch 3 lastVoteEpoch 0

logを見てみると、クラスタの各ノードに生存確認の通信が頻繁に走っていることが分かります。

$ tail -f 7001/redis.log | grep Ping
23189:M 22 Mar 11:29:29.814 . Ping packet received: (nil)
23189:M 22 Mar 11:29:30.117 . Pinging node 8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf
23189:M 22 Mar 11:29:30.821 . Ping packet received: (nil)
23189:M 22 Mar 11:29:31.119 . Pinging node 6082772a27ae6465b9f3d26959e1de5991b41356
23189:M 22 Mar 11:29:31.818 . Ping packet received: (nil)
23189:M 22 Mar 11:29:32.120 . Pinging node 8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf
23189:M 22 Mar 11:29:32.825 . Ping packet received: (nil)
23189:M 22 Mar 11:29:33.123 . Pinging node 6082772a27ae6465b9f3d26959e1de5991b41356
23189:M 22 Mar 11:29:33.825 . Ping packet received: (nil)
23189:M 22 Mar 11:29:34.125 . Pinging node 8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf
23189:M 22 Mar 11:29:34.828 . Ping packet received: (nil)
23189:M 22 Mar 11:29:35.128 . Pinging node 8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf

slave構築

slaveもclusterに組み込みます。

$ ./redis-trib.rb add-node --slave --master-id 8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf 127.0.0.1:7003 127.0.0.1:7000
>>> Adding node 127.0.0.1:7003 to cluster 127.0.0.1:7000
>>> Performing Cluster Check (using node 127.0.0.1:7000)
M: 8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf 127.0.0.1:7000
slots:0-5460 (5461 slots) master
0 additional replica(s)
M: 6082772a27ae6465b9f3d26959e1de5991b41356 127.0.0.1:7002
slots:10923-16383 (5461 slots) master
0 additional replica(s)
M: eb6c6a4924cd449dcac420159cab592bc7dc8a4e 127.0.0.1:7001
slots:5461-10922 (5462 slots) master
0 additional replica(s)
[OK] All nodes agree about slots configuration.
>>> Check for open slots...
>>> Check slots coverage...
[OK] All 16384 slots covered.
>>> Send CLUSTER MEET to node 127.0.0.1:7003 to make it join the cluster.
Waiting for the cluster to join.
>>> Configure node as replica of 127.0.0.1:7000.
[OK] New node added correctly.

同じように設定していきます。

$ ./redis-trib.rb add-node --slave --master-id 8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf 127.0.0.1:7003 127.0.0.1:7000
$ ./redis-trib.rb add-node --slave --master-id 8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf 127.0.0.1:7004 127.0.0.1:7000
$ ./redis-trib.rb add-node --slave --master-id eb6c6a4924cd449dcac420159cab592bc7dc8a4e 127.0.0.1:7005 127.0.0.1:7001
$ ./redis-trib.rb add-node --slave --master-id 6082772a27ae6465b9f3d26959e1de5991b41356 127.0.0.1:7006 127.0.0.1:7002

これで想定どおりのクラスタが組めました。

クラスタの状態を確認すると、以下のように見えます。

$ redis-cli -p 7000 cluster nodes
004e547e24fbdede970cd95a8801fdef6c51ee3c 127.0.0.1:7006 slave 6082772a27ae6465b9f3d26959e1de5991b41356 0 1458647137056 3 connected
00697c7f90fc0d5a3d1c55bf51b2790881b6c36a 127.0.0.1:7003 slave 8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf 0 1458647136055 1 connected
8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf 127.0.0.1:7000 myself,master - 0 0 1 connected 0-5460
eb7278d6b3018e321d1fdb4a79e630d295776e63 127.0.0.1:7005 slave eb6c6a4924cd449dcac420159cab592bc7dc8a4e 0 1458647141066 2 connected
f0784fa504d9a64ba3cedf8a66d2c23cea9f6e16 127.0.0.1:7004 slave 8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf 0 1458647139061 1 connected
eb6c6a4924cd449dcac420159cab592bc7dc8a4e 127.0.0.1:7001 master - 0 1458647140064 2 connected 5461-10922
6082772a27ae6465b9f3d26959e1de5991b41356 127.0.0.1:7002 master - 0 1458647138058 3 connected 10923-16383
$
$ redis-cli -p 7000 cluster slots
1) 1) (integer) 0
   2) (integer) 5460
   3) 1) "127.0.0.1"
      2) (integer) 7000
   4) 1) "127.0.0.1"
      2) (integer) 7003
   5) 1) "127.0.0.1"
      2) (integer) 7004
2) 1) (integer) 5461
   2) (integer) 10922
   3) 1) "127.0.0.1"
      2) (integer) 7001
   4) 1) "127.0.0.1"
      2) (integer) 7005
3) 1) (integer) 10923
   2) (integer) 16383
   3) 1) "127.0.0.1"
      2) (integer) 7002
   4) 1) "127.0.0.1"
      2) (integer) 7006

壊してみる

やっと準備が整いました。Replica Migrationの機能を確認したいので、まず、7002のnodeをkillしてみます。

$ ps aux | grep redis | grep -v grep
root 23180 0.0 0.4 142808 4312 ? Ssl 11:20 0:02 redis-server 127.0.0.1:7000 [cluster]
root 23189 0.0 0.4 142808 4308 ? Ssl 11:22 0:01 redis-server 127.0.0.1:7001 [cluster]
root 23197 0.0 0.4 142808 4228 ? Ssl 11:22 0:01 redis-server 127.0.0.1:7002 [cluster]
root 23202 0.0 0.4 142808 4184 ? Ssl 11:22 0:01 redis-server 127.0.0.1:7003 [cluster]
root 23207 0.0 0.4 142808 4256 ? Ssl 11:22 0:01 redis-server 127.0.0.1:7004 [cluster]
root 23212 0.0 0.4 142808 4148 ? Ssl 11:22 0:01 redis-server 127.0.0.1:7005 [cluster]
root 23217 0.0 0.4 142808 4160 ? Ssl 11:22 0:01 redis-server 127.0.0.1:7006 [cluster]
$
$ sudo kill 23197
$

さて、クラスタの状態を確認してみると、7006が7002の代わりにmasterになっており、かつ、7003が7006のslaveになっていることが分かります。

$ redis-cli -p 7000 cluster nodes
004e547e24fbdede970cd95a8801fdef6c51ee3c 127.0.0.1:7006 master - 0 1458648558314 4 connected 10923-16383
00697c7f90fc0d5a3d1c55bf51b2790881b6c36a 127.0.0.1:7003 slave 004e547e24fbdede970cd95a8801fdef6c51ee3c 0 1458648555305 4 connected
8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf 127.0.0.1:7000 myself,master - 0 0 1 connected 0-5460
eb7278d6b3018e321d1fdb4a79e630d295776e63 127.0.0.1:7005 slave eb6c6a4924cd449dcac420159cab592bc7dc8a4e 0 1458648557311 2 connected
f0784fa504d9a64ba3cedf8a66d2c23cea9f6e16 127.0.0.1:7004 slave 8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf 0 1458648554305 1 connected
eb6c6a4924cd449dcac420159cab592bc7dc8a4e 127.0.0.1:7001 master - 0 1458648559316 2 connected 5461-10922
6082772a27ae6465b9f3d26959e1de5991b41356 127.0.0.1:7002 master,fail - 1458648522304 1458648519195 3 disconnected

すなわち、こういう状態になりました。

今度は、7006をkillしてみます。

$ sudo kill 23217

クラスタの状態を確認してみると、7003が代わりにmasterになっていることが分かります。

$ redis-cli -p 7000 cluster nodes
004e547e24fbdede970cd95a8801fdef6c51ee3c 127.0.0.1:7006 master,fail - 1458648779623 1458648779022 4 disconnected
00697c7f90fc0d5a3d1c55bf51b2790881b6c36a 127.0.0.1:7003 master - 0 1458648899466 5 connected 10923-16383
8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf 127.0.0.1:7000 myself,master - 0 0 1 connected 0-5460
eb7278d6b3018e321d1fdb4a79e630d295776e63 127.0.0.1:7005 slave eb6c6a4924cd449dcac420159cab592bc7dc8a4e 0 1458648896455 2 connected
f0784fa504d9a64ba3cedf8a66d2c23cea9f6e16 127.0.0.1:7004 slave 8437edc8825ef2cb0d33fcaff028ad6eb13ed5bf 0 1458648894445 1 connected
eb6c6a4924cd449dcac420159cab592bc7dc8a4e 127.0.0.1:7001 master - 0 1458648898463 2 connected 5461-10922
6082772a27ae6465b9f3d26959e1de5991b41356 127.0.0.1:7002 master,fail - 1458648522304 1458648519195 3 disconnected
$
$ redis-cli -p 7000 cluster slots
1) 1) (integer) 10923
   2) (integer) 16383
   3) 1) "127.0.0.1"
      2) (integer) 7003
2) 1) (integer) 0
   2) (integer) 5460
   3) 1) "127.0.0.1"
      2) (integer) 7000
   4) 1) "127.0.0.1"
      2) (integer) 7004
3) 1) (integer) 5461
   2) (integer) 10922
   3) 1) "127.0.0.1"
      2) (integer) 7001
   4) 1) "127.0.0.1"
      2) (integer) 7005

想定通りの挙動をしていますね。

詳細なアルゴリズムはドキュメントに記載してあるため、興味がある方は一度見てみるのはいかがでしょうか。

所感

クラスタ再構成中のデータ更新は当然データ消失の可能性がありますし、今回のようなほとんどデータがない状態でもクラスタ再構成に数秒かかったため、実データサイズでの時間計測はやっておきたいところです。とは言え、システムの信頼性向上にとっては素晴らしい機能だと思います。

他にも、resharding機能など、ver3では、スケールアウトの仕組みなども整っており、試したい機能がいっぱいです!AWSがRedis ver3に早く対応することに期待ですね!