次のことの覚え書きです。
- 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を叩けばいい(はず)。
$ 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などを使ってセキュリティを確保する。
自動起動の設定
$ chkconfig redis on
Redisの起動
$ service redis start
Redis Sentinelの設定
通常、Redis Sentinelは3つ以上のホストで動かす。Redisを動かしていないノードでも構わない。
/etc/redis-sentinel.confを書き換える。
- sentinel monitor mymaster xxx.xxx.xxx.aaa 6379 2 を書く (このアドレスとポートは、Redisマスターを示す)
自動起動の設定
$ chkconfig redis-sentinel on
Redis Sentinelの起動
$ service redis-sentinel start
動作確認
インストールと動作確認が全てのマシンで終わったら動作を確認してみる。
マスターでの確認
$ 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
スレーブでの確認
$ 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が別のホスト(スレーブ)。
$ 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を止めると、いずれかのスレーブがマスターに昇格しているはず。
$ 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がマスターになっていた。
$ 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がマスターになっていた。
$ 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がマスターになっているのがそのままである。元マスターを再起動してもマスターにはならない。
$ 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に関するパラメータを与えればいい。
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"