冗長構成にしたRedisをRubyから使う

次のことの覚え書きです。

* Redisを冗長構成で立ち上げる
* Redis Sentinelでフェイルオーバーさせる
* この環境をRubyで使う

## Redisとは

インメモリKVSの一種。とっても速い。1台以上のスレーブへのレプリケーションによって、読み出しをスケールさせることができる。書き込みはマスターしかできないが、読み出しはスレーブからでもできる。

## Redis Sentinelとは

Redisのフェールオーバーの仕組み。Sentinel同士で監視しあって、マスターが死んだ時に次のマスターを決める。Sentinelは、外部からRedisの設定を変更する。

Sentinelの投票によって、マスターが生きているか、マスターが死んだ場合にはどのスレーブを昇格させるかを決めている。通常は、3台以上のSentinelを稼働させる。

フェールオーバーをする際に、Redis SentinelがRedisの設定ファイル(redis.conf)を書き換えることに注意すること。

この記事の例では、Redisのポートは6379, Redis Sentinelのポートは26379を使っている。

## この記事での構成

* xxx.xxx.xxx.aaa - 初期設定でマスターになるホスト。Redis Sentinelを動かしている
* xxx.xxx.xxx.bbb - 初期設定でスレーブになるホスト。Redis Sentinelを動かしている
* xxx.xxx.xxx.ccc - 初期設定でスレーブになるホスト。Redis Sentinelを動かしている

# インストール

CentOS 6.xで試した。

Redisのインストールにはjemallocが必要。EPELのリポジトリをenableにしてyumを叩けばいい(はず)。

```sh
$ yum install -y ftp://195.220.108.108/linux/remi/enterprise/6/test/i386/redis-3.0.0-2.el6.remi.i686.rpm --enablerepo=epel
```

# Redisの設定

とりあえず /etc/redis.conf を書き換える

* bind 127.0.0.1 があったらコメントアウトする。(他のホストからアクセスさせる場合)
* slaveof xxx.xxx.xxx.aaa 6379 を指定する。(レプリケーションのスレーブになるホストだけ)
* 必要に応じて、パスワードやiptablesなどを使ってセキュリティを確保する。

## 自動起動の設定

```sh
$ chkconfig redis on
```

## Redisの起動

```sh
$ service redis start
```

## Redis Sentinelの設定

通常、Redis Sentinelは3つ以上のホストで動かす。Redisを動かしていないノードでも構わない。
/etc/redis-sentinel.confを書き換える。

* sentinel monitor mymaster xxx.xxx.xxx.aaa 6379 2 を書く (このアドレスとポートは、Redisマスターを示す)

### 自動起動の設定

```sh
$ chkconfig redis-sentinel on
```

## Redis Sentinelの起動

```sh
$ service redis-sentinel start
```

# 動作確認

インストールと動作確認が全てのマシンで終わったら動作を確認してみる。

## マスターでの確認

```sh
$ redis-cli info replication
# Replication
role:master
connected_slaves:2
slave0:ip=xxx.xxx.xxx.bbb,port=6379,state=online,offset=326322,lag=1
slave1:ip=xxx.xxx.xxx.ccc,port=6379,state=online,offset=326322,lag=2
master_repl_offset:326322
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:326321
```

## スレーブでの確認

```sh
$ redis-cli info replication
# Replication
role:slave
master_host:xxx.xxx.xxx.aaa
master_port:6379
master_link_status:up
master_last_io_seconds_ago:0
master_sync_in_progress:0
slave_repl_offset:301712
slave_priority:100
slave_read_only:1
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
```

## 書き込んでみる

スレーブでは書き込み(set)ができないので、マスターにつなぐ必要がある。
読み出しはスレーブでもできる。

aaaがマスター、bbbが自分(スレーブ)、cccが別のホスト(スレーブ)。

```sh
$ redis-cli -h xxx.xxx.xxx.aaa set foo "FOO"  # マスターにつないで書く
OK
$ redis-cli -h xxx.xxx.xxx.aaa get foo  # マスターにつないで読む
"FOO"
$ redis-cli get foo  # ローカルにつないで読む
"FOO"
$ redis-cli -h xxx.xxx.xxx.ccc get foo  # 別のスレーブにつないで読む
"FOO"
```

## フェイルオーバーを試してみる

マスターでRedisを止めると、いずれかのスレーブがマスターに昇格しているはず。

```sh
$ service redis stop
Stopping redis-server:                                     [  OK  ]
$ redis-cli info replication
Could not connect to Redis at 127.0.0.1:6379: Connection refused  # 止まっている
```

スレーブbbbで状態を見てみると、bbbがマスターになっていた。

```sh
$ redis-cli info replication
# Replication
role:master
connected_slaves:1
slave0:ip=xxx.xxx.xxx.ccc,port=6379,state=online,offset=16735,lag=1
master_repl_offset:17017
repl_backlog_active:1
repl_backlog_size:1048576
repl_backlog_first_byte_offset:2
repl_backlog_histlen:17016
```

スレーブcccで状態を見てみると、bbbがマスターになっていた。

```sh
$ redis-cli info replication
# Replication
role:slave
master_host:xxx.xxx.xxx.bbb
master_port:6379
master_link_status:up
master_last_io_seconds_ago:0
master_sync_in_progress:0
slave_repl_offset:40395
slave_priority:100
slave_read_only:1
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
```

もともとマスターであったaaaを再起動して状態を見てみるが、bbbがマスターになっているのがそのままである。元マスターを再起動してもマスターにはならない。

```sh
$ service redis start
Starting redis-server:                                     [  OK  ]
$ redis-cli info replication
# Replication
role:slave
master_host:xxx.xxx.xxx.bbb
master_port:6379
master_link_status:up
master_last_io_seconds_ago:1
master_sync_in_progress:0
slave_repl_offset:57832
slave_priority:100
slave_read_only:1
connected_slaves:0
master_repl_offset:0
repl_backlog_active:0
repl_backlog_size:1048576
repl_backlog_first_byte_offset:0
repl_backlog_histlen:0
```

# Rubyから使ってみる

RubyからRedisを使うためにredis Gemを使う。このGemはSentinelをサポートしている。Redis.newの引数にSentinelに関するパラメータを与えればいい。

```rb
require 'redis'

sentinels = [
             { host: 'xxx.xxx.xxx.aaa', port: 26379 },
             { host: 'xxx.xxx.xxx.bbb', port: 26379 },
             { host: 'xxx.xxx.xxx.ccc', port: 26379 },
            ]

redis = Redis.new(url:'redis://mymaster', sentinels: sentinels, role:'master')

p redis.get('foo') #=> "FOO"  # 読んでみる

redis.set('bar', 'BAR')  # 書いてみる
p redis.get('bar') #=> "BAR"
```