Redisのreplication構成+自動failover(sentinel)をVagrantで構築してnode.js(express + ioredis)から利用する
はじめに
Redisのcluster構成に関する記述はありません。
Redisの構成に関しては公式ドキュメント(英語)にあるreplicationとsentinelの記述の通りです。
目新しいことは無いと思います。
node.jsのRedisクライアントにはioredisを利用します。node_redisに関する記述はありません。
expressのsession-storeにRedisを利用する前提で記述してあります。
なぜCookieだけでないのか、なぜsession-storeにRDBなどの他のミドルウェアを利用しないのか、のような話題の記述はありません。
取り扱う内容
- Vagrant
- 下記構成のRedisサーバを構築
- Redis
- replication
- master-slave構成のこと
- 自動failover
- masterのdown検知時に自動で(人の手を介さずに)slaveがmasterに昇格すること
- masterの監視はredis-sentinelを使用
- replication
- expressからの利用
- 構築したRedisをexpressのsession-storeとして利用
ローカルの仮想マシンでreplicationと自動failoverをしてもあまり意味がないので、可能ならばAWSの複数リージョンを使うなど、IaaSを活用したかったのですが、お金がかかるので諦めました。
以下は全てmacで実行していますが、Vagrantに依存するものがほとんどのため、Vagrantが動作する環境ならばどこでも大丈夫だと思います。
Redisの準備
Vagrantを使用してRedisサーバを3台、作成します。
1台のサーバあたりメモリを500MBちょっとつかうのでメモリをとっても圧迫します…。使用しているmacのメモリの8GBでは複数の仮想マシンを動作させるには辛い様子で、途中、動作が重たくなったのでchromeを閉じました。
あと、電池がよく減ります。
構成
1台はmasterのRedis、他2台はslaveのRedisを配置します。
それぞれのサーバにはRedisのプロセスとは別にsentinelのプロセスを起動します。
+---------------+
| 192.168.50.10 |
|---------------|
| master |
| sentinel-1 |
+---------------+
|
+---------------+ | +---------------+
| 192.168.50.11 |---------+---------| 192.168.50.12 |
|---------------| |---------------|
| slave-1 | | slave-2 |
| sentinel-2 | | sentinel-3 |
+---------------+ +---------------+
Vagrantのインストール
brew install Caskroom/cask/vagrant
Vagrantはとっても便利です。よく利用します。
今回はubuntu公式のBoxを使用しました。
余談ですが、仮想マシンに特化したメジャーなBoxは無いのでしょうか…。Boxのダウンロードも前述したメモリ使用量も電池の減りも、とにかく重いです…
それとも自分が情弱なだけですでに存在するのでしょうか。
Vagrantfile
仮想マシンの構成を定義するVagrantfileを用意します。
ココにRedisをインストールするVagrantfileとプロビジョニングのスクリプトがあったので利用させてもらいました。
ただし、これは1台の仮想マシン用のコードであるため、前述の構成に対応する変更を加えます。
Rubyは詳しくないのですが、end
の数がすごいことなっています。JavaScriptのコールバックヘルみたいです。
プロビジョニングのスクリプトにも変更を加えます。
1台はmaster、他2台はslaveにする処理とsentinelを起動する処理です。
本番運用ではmasterとslaveで設定ファイルを分け、sentinelは自動起動スクリプトを登録したりするべきかと思いますが手を抜きます。
sentinelの設定ファイルを追加します。
デーモン化とPIDファイル置き場所の指定、初期時点でのmasterの場所とquorumを設定します。
quorumについては割愛します。
まとめたコードはGithubに置いてあります。
起動
vagrant up
初回はとっても長いです。
osのboxイメージ取得から処理されるので強いネットワーク回線必須です。
自分は最初、テザリング環境でトライしてむりぽを悟り、ご自宅でリトライをいたしました。わかっていましたけどね…
構成の確認
ホストマシン(ここではmac)にRedisのクライアントをインストールしてRedisの状態を確認します。
クライアントをインストールと言いつつ、サーバもインストールされてしまいますが無視します。
brew install redis
replication
$ redis-cli -h 192.168.50.10 info replication # Replication role:master <- ココ connected_slaves:2 slave0:ip=192.168.50.11,port=6379,state=online,offset=245191,lag=0 slave1:ip=192.168.50.12,port=6379,state=online,offset=245191,lag=0 master_repl_offset:245191 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:2 repl_backlog_histlen:245190 $ redis-cli -h 192.168.50.11 info replication # Replication role:slave <- ココ master_host:192.168.50.10 master_port:6379 master_link_status:up master_last_io_seconds_ago:0 master_sync_in_progress:0 slave_repl_offset:247884 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 $ redis-cli -h 192.168.50.12 info replication # Replication role:slave <- ココ master_host:192.168.50.10 master_port:6379 master_link_status:up master_last_io_seconds_ago:0 master_sync_in_progress:0 slave_repl_offset:248744 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
少し使った後の実行結果なのでまっさらではないですが、良い感じです。
sentinel
$ redis-cli -h 192.168.50.10 -p 26379 info sentinel # Sentinel sentinel_masters:1 sentinel_tilt:0 sentinel_running_scripts:0 sentinel_scripts_queue_length:0 master0:name=mymaster,status=ok,address=192.168.50.10:6379,slaves=3,sentinels=3 $ redis-cli -h 192.168.50.11 -p 26379 info sentinel # Sentinel sentinel_masters:1 sentinel_tilt:0 sentinel_running_scripts:0 sentinel_scripts_queue_length:0 master0:name=mymaster,status=ok,address=192.168.50.10:6379,slaves=3,sentinels=3 $ redis-cli -h 192.168.50.12 -p 26379 info sentinel # Sentinel sentinel_masters:1 sentinel_tilt:0 sentinel_running_scripts:0 sentinel_scripts_queue_length:0 master0:name=mymaster,status=ok,address=192.168.50.10:6379,slaves=3,sentinels=3
大丈夫そうです。
動作確認
実際にreplicationと自動failoverの動作確認をします。
replication
masterに値をセットしてslaveで取得できるかを確認します。
$ redis-cli -h 192.168.50.10 set hoge 'fuga' OK $ redis-cli -h 192.168.50.10 get hoge "fuga" $ redis-cli -h 192.168.50.11 get hoge "fuga" $ redis-cli -h 192.168.50.12 get hoge "fuga"
良いですが、どうしてもローカル環境なので当たり前感が…。
AWSの複数のリージョンにサーバを設置するなど、遠隔地環境でタイムラグ等の実測値がどうなるかは気になります。
自動failoverの確認
masterを擬似的に落とします。
$ redis-cli -h 192.168.50.10 DEBUG sleep 30 OK
roleがどうなったかを確認。
$ redis-cli -h 192.168.50.10 info replication # Replication role:slave <- slaveに変わった master_host:192.168.50.11 master_port:6379 master_link_status:up master_last_io_seconds_ago:0 master_sync_in_progress:0 slave_repl_offset:374134 slave_priority:100 slave_read_only:1 connected_slaves:0 master_repl_offset:66095 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:2 repl_backlog_histlen:66094 $ redis-cli -h 192.168.50.11 info replication # Replication role:master <- masterに変わった connected_slaves:2 slave0:ip=192.168.50.10,port=6379,state=online,offset=376249,lag=0 slave1:ip=192.168.50.12,port=6379,state=online,offset=376263,lag=0 master_repl_offset:376263 repl_backlog_active:1 repl_backlog_size:1048576 repl_backlog_first_byte_offset:341012 repl_backlog_histlen:35252 $ redis-cli -h 192.168.50.12 info replication # Replication role:slave <- そのまま master_host:192.168.50.11 master_port:6379 master_link_status:up master_last_io_seconds_ago:1 master_sync_in_progress:0 slave_repl_offset:383073 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
グッドです。
sentinelも確認します。
$ redis-cli -h 192.168.50.10 -p 26379 info sentinel # Sentinel sentinel_masters:1 sentinel_tilt:0 sentinel_running_scripts:0 sentinel_scripts_queue_length:0 master0:name=mymaster,status=ok,address=192.168.50.11:6379,slaves=3,sentinels=3 $ redis-cli -h 192.168.50.11 -p 26379 info sentinel # Sentinel sentinel_masters:1 sentinel_tilt:0 sentinel_running_scripts:0 sentinel_scripts_queue_length:0 master0:name=mymaster,status=ok,address=192.168.50.11:6379,slaves=3,sentinels=3 $ redis-cli -h 192.168.50.12 -p 26379 info sentinel # Sentinel sentinel_masters:1 sentinel_tilt:0 sentinel_running_scripts:0 sentinel_scripts_queue_length:0 master0:name=mymaster,status=ok,address=192.168.50.11:6379,slaves=3,sentinels=3
がはは、グッドだー
因みにですが、slaveになった192.168.50.10には書き込めなくなります。
$ redis-cli -h 192.168.50.10 set hoge huga (error) READONLY You can't write against a read only slave.
つまり、クライアントはRedisサーバがfailoverで切り替わった際に向き先を変更しなければ書き込みができなくなってしまいます。
せっかくRedisサーバが自動failoverしてもクライアントが対応しないと意味無いです。
クライアントがnode.jsの場合どうするかを後述します。
再度、192.168.50.10
をmasterにします。
$ redis-cli -h 192.168.50.11 DEBUG sleep 30 OK
expressからの利用
自分のmacのスペック的にこれ以上は仮想マシンを追加したくなかったので、ホストマシンでnode.jsのアプリケーションを起動してそこから仮想マシン上のRedisをコールすることにします。
node.jsもプラットフォームの対応が素晴らしいのでmacに限らず大体どこでも大丈夫だと思います。
brew install node.js
npm install express-generator -g
express redis-failover-hello-world
cd redis-failover-hello-world && npm install
前述のとおり、ioredisを使用します。
npm install --save ioredis
セッションの保存先をRedisにするnpmパッケージをインストール。
npm install --save connect-redis express-session
app.jsを書き換えて、セッションの保存先をVagrant上に構築したRedisに指定します。
起動。
$ DEBUG=ioredis:* npm start > redis-failover-hello-world@0.0.0 start /Users/hhr/git/redis-failover-hello-world > node ./bin/www ioredis:redis status[localhost:6379]: [empty] -> connecting +0ms ioredis:redis status[192.168.50.10:26379]: [empty] -> connecting +7ms ioredis:redis queue command[0] -> sentinel(get-master-addr-by-name,mymaster) +4ms ioredis:redis status[192.168.50.10:26379]: connecting -> connect +76ms ioredis:redis status[192.168.50.10:26379]: connect -> ready +1ms ioredis:connection send 1 commands in offline queue +0ms ioredis:redis write command[0] -> sentinel(get-master-addr-by-name,mymaster) +1ms ioredis:redis status[192.168.50.10:6379]: connecting -> connect +11ms ioredis:redis write command[0] -> info() +1ms ioredis:redis status[192.168.50.10:6379]: connect -> ready +3ms ioredis:redis status[192.168.50.10:26379]: ready -> close +1ms ioredis:connection skip reconnecting since the connection is manually closed. +0ms ioredis:redis status[192.168.50.10:26379]: close -> end +0ms
ふむふむ、
192.168.50.10:26379
に接続(sentinelに接続)redis-cli -h 192.168.50.10 -p 26379 sentinel get-master-addr-by-name mymaster
的なコマンド実行(sentinelにmasterの場所を問い合わせ)- (
192.168.50.10
が返ってくる) 192.168.50.10:6379
に接続(masterに接続)redis-cli -h 192.168.50.10 -p 6379 info
的なコマンド実行(masterであることを確認している?)192.168.50.10:26379
を切断
のようです。
HTTPサーバが起動したので、ブラウザからアクセスして挙動を確認します。
ioredis:redis write command[0] -> set(sess:vp5z1WR_7aK8eHjzJkbv5xXLRZEKPahs,{"cookie":{"originalMaxAge":null,"expires":null,"httpOnly":true,"path":"/"}},EX,86400) +2m GET / 200 43.978 ms - 170 ioredis:redis write command[0] -> get(sess:vp5z1WR_7aK8eHjzJkbv5xXLRZEKPahs) +47ms ioredis:redis write command[0] -> expire(sess:vp5z1WR_7aK8eHjzJkbv5xXLRZEKPahs,86400) +3ms GET /stylesheets/style.css 200 1.763 ms - 111 ioredis:redis write command[0] -> get(sess:vp5z1WR_7aK8eHjzJkbv5xXLRZEKPahs) +162ms ioredis:redis write command[0] -> expire(sess:vp5z1WR_7aK8eHjzJkbv5xXLRZEKPahs,86400) +41ms GET /favicon.ico 404 40.017 ms - 1919 ioredis:redis write command[0] -> get(sess:vp5z1WR_7aK8eHjzJkbv5xXLRZEKPahs) +27ms ioredis:redis write command[0] -> expire(sess:vp5z1WR_7aK8eHjzJkbv5xXLRZEKPahs,86400) +28ms GET /favicon.ico 404 27.736 ms - 1919
favicon.ico
が無い、は無視します。
セッション情報をRedisに書き込んでそれを取得している様子が確認できます。
ブラウザの開発ツール等でHTTPヘッダやクッキーを見るとs:vp5z1WR_7aK8eHjzJkbv5xXLRZEKPahs...
があり、これがセッションIDであることがわかります。
ブラウザリロード。
ioredis:redis write command[0] -> get(sess:vp5z1WR_7aK8eHjzJkbv5xXLRZEKPahs) +4m ioredis:redis write command[0] -> expire(sess:vp5z1WR_7aK8eHjzJkbv5xXLRZEKPahs,86400) +14ms GET / 304 13.421 ms - - ioredis:redis write command[0] -> get(sess:vp5z1WR_7aK8eHjzJkbv5xXLRZEKPahs) +40ms ioredis:redis write command[0] -> expire(sess:vp5z1WR_7aK8eHjzJkbv5xXLRZEKPahs,86400) +4ms GET /stylesheets/style.css 304 2.832 ms - -
2回目以降はセッションIDの有効期限内であれば書き込みはされないようです。
有効期限切れのパターンは割愛します。
ターミナルからも見てみます。
$ redis-cli -h 192.168.50.10 keys '*' 1) "hoge" 2) "sess:vp5z1WR_7aK8eHjzJkbv5xXLRZEKPahs" $ redis-cli -h 192.168.50.10 get 'sess:vp5z1WR_7aK8eHjzJkbv5xXLRZEKPahs' "{\"cookie\":{\"originalMaxAge\":null,\"expires\":null,\"httpOnly\":true,\"path\":\"/\"}}"
セッションの保存、良い感じです。
slaveに保存されているかの確認は割愛します。
failoverさせます。
redis-cli -h 192.168.50.10 DEBUG sleep 30
ioredis:redis status[192.168.50.10:6379]: ready -> close +7m ioredis:connection reconnect in 2ms +0ms ioredis:redis status[192.168.50.10:6379]: close -> reconnecting +1ms ioredis:redis status[192.168.50.10:6379]: reconnecting -> connecting +2ms ioredis:redis status[192.168.50.11:26379]: [empty] -> connecting +1ms ioredis:redis queue command[0] -> sentinel(get-master-addr-by-name,mymaster) +1ms ioredis:redis status[192.168.50.11:26379]: connecting -> connect +1ms ioredis:redis status[192.168.50.11:26379]: connect -> ready +0ms ioredis:connection send 1 commands in offline queue +1ms ioredis:redis write command[0] -> sentinel(get-master-addr-by-name,mymaster) +0ms ioredis:redis status[192.168.50.11:6379]: connecting -> connect +2ms ioredis:redis write command[0] -> info() +0ms ioredis:redis status[192.168.50.11:26379]: ready -> close +1ms ioredis:connection skip reconnecting since the connection is manually closed. +1ms ioredis:redis status[192.168.50.11:26379]: close -> end +0ms ioredis:redis status[192.168.50.11:6379]: connect -> ready +1ms
おぉ、切り替わりました。 注目すべきは、HTTPアクセスが無い状態で勝手に接続先が切り替わったことです。大変よい感じです。うふふ。
さいごに
以上、ざっとですが自動failoverするRedisサーバをexpressで使用することができました。
masterがdownして、その後に復帰したらどうなるかとか無いので本当にざっとです…
個人で自動failoverする構成の需要が高くないためか、割りと調べることに苦労しました…
まぁ、AWSやherokuとかではたいてい機能が用意されているので地味なネタなのかもです。
- 作者: Josiah L. Carlson,長尾高弘
- 出版社/メーカー: KADOKAWA/アスキー・メディアワークス
- 発売日: 2013/12/27
- メディア: 大型本
- この商品を含むブログ (4件) を見る