Redis Sentinelを運用してみたお話
Redis

Redis Sentinelを運用してみたお話

このエントリーをはてなブックマークに追加

こんにちは、宇津井です。

弊社ではゲソてんというゲームプラットフォームを運営しております。ゲソてんではサービスリリース当初から主にキャッシュ的な役割でRedisを導入していました。つい先日の事ですが訳あってRedis 2.6からサポートされたRedis Sentinelを採用しました。今回は、採用に至った経緯をご紹介します。

簡単なシステム構成

image

パフォーマンスは良いし、Memcachedには無い便利な機能を提供するRedisですが約一年間運用していくつかの問題が出てきました。


  1. Redisサーバーが突然高負荷に陥る
  2. するとWebサーバーも高負荷に陥る
  3. RedisのReplicationは張ってるけどアプリケーション側でRedis Slaveへフェイルオーバーしない(MySQLを直接参照する)
  4. Redis落ちると恐らくMySQLサーバーが耐えられない(落ちた事無いけど)

という事で対策を行ってきました。

まずは1,2番の問題です。WebサーバーとRedisサーバーの荒ぶりはRedisサーバーのUpgradeで事なきを得ました。当時のRedis Versionは2.4.15です。ReleaseNotes(2.4,2.6)を拝見すると色々あります。というか2.6が出てます。ReleaseNotesをきちんと確認してきちんとUpgradeしていきましょう。

検証して問題なかったのでさっさと2.6系にUpgradeし、一旦高負荷問題は落ち着きました。この際に色々とチューニングを施しましたがRedisは元々高速で面白みに欠けます。(後で解ったことですがF5アタックも受けてました。これは別途Nginxで対処してます)

尚、2.4系から2.6系はRedisの使われ方にも依りますが比較的すんなりUpgrade出来ます。今回行った手順はざっくり以下のような感じです。


1. Slaveは現状アプリから使ってないのでslaveを2.6にUpgradeしておく

2. アプリをSlave側に向ける

3. Slaveはslave_read_only:0にしておく(書込可能)

4. Masterにアクセスがない事を確認したらSlaveをMaster昇格

(redis-02) slaveof no one

5. Masterをシャットダウン

(redis-01) superviserctl stop redis

6. 旧Master(redis-01)のRedisを2.6にUpgrade & 旧Master(redis-01)のRedisを起動

7. 旧Master(redis-01)を新Master(redis-02)のSlaveに設定

(redis-01) slaveof redis-02 6379

8. Slaveは書込可能にしておく

(redis-01) slave_read_only:0

 ※この後に出てくるSentinelに対応するためreadonlyを解除してしまいます


さて、高負荷問題が落ち着きRedisは安定している。もう対策しなくていいんじゃね?という空気が漂う中DBAの田中さんが「いやいや、フェイルオーバーまでしないとダメでしょ!落ちたらどーすんの?」とケツを叩いてくれます。

やっぱりSentinelの対応することになりました。Sentinelですが簡単に説明すると以下のような感じです。

  • MasterとSlaveの状態を監視
  • 何か起きたら通知
  • 何か起きたら自動フェイルオーバー

詳しくはドキュメントを読みましょう。(英語です)

加えて以下の特徴を持ちます。

  • 一つのMasterに対して複数のSentinelプロセスを起動してお互いにMasterの情報を共有し合う
  • Masterのダウンは複数個のSentinelプロセスが投票方式で検知する例:Sentinelを4プロセス立ち上げた場合、3個以上がダウン(SDOWN)と判定したらMasterダウン(ODOWN)と見なしフェイルオーバーする。※Masterダウン(ODOWN)を判定する条件は指定できます。
  • 夢のようなクラスター機能ではない
  • 通常はNoPreempt(フェイルバック機能は持たない)
  • VIP、或はプロキシ的な機能は持たない

最後に「VIP、あるいはプロキシ的な機能は持たない」と書きましたがこれが面倒な点です。障害時にSlaveのMaster昇格は行ってくれますが、アプリケーションからの接続はRedis, Redis Sentinel以外の何かしらで制御する必要があります。

ということでアプリケーション側できちんとMater/Slave状態を追いかけるにはどうするのか考えました。(自社データセンター内ですのであまり制約がありません) 

 


1. Load Balancer方式

image

  • Slaveの負荷分散には良いけど、フェイルオーバーを追いかけるのには向かない

2. VIP方式(Keepalived / VRRPとか)

image

  • Master:Slaveが1:1だったら良いけどSlaveが複数台ある要件に合わなくなってくる
  • 上のLoad Balancer方式と組み合わせれば何とかなるかもしれないが、構成がちょっと複雑になってくる

3. DNS方式

image

  • FuelPHP(実はフレームワークがFuelPHP)は redis-1-m:6379 に固定接続。TTLを短く(10秒とか)して障害を検知したらAレコードを書き換える。
  • Sentinelプロセス監視してMasterを検知しつづけるチェックプログラムをバッチサーバーとかで動かす。

 この方式のメリット

  • アプリケーションの修正不要
  • チェックプログラムは一つで大丈夫

 この方式のデメリット

  • DNSプロトコルに乗せるのでシステムがやや複雑になる
  • DNSキャッシュの考慮を慎重に行う必要がある
  • キャッシュサーバーの処理性能は20,000qpsぐらい
  • ベンチマークを取ったところ1~20ms程度のオーバーヘッドが有る(それならlocalにDNSキャッシュ作った方が良い。)

 想定切替秒数:最大85秒


4. hosts方式

image

  • cronでSentinelを監視して、Masterが切り替わったら/etc/hostsを書き換える
  • FuelPHPは redis-1-m:6379 に固定接続

 この方式のメリット

  • 単純

 この方式のデメリット

  • 各サーバーでチェックスクリプト動かす必要がある
  • /etc/hostsの即時反映はnetwork restartとか必要

5. iptables方式

image

  • cronでSentinelを監視して、Masterが切り替わったらiptablesのDestination Nat情報を書き換える
  • FuelPHPは 192.168.2.10:6379 に固定接続

 この方式のメリット

  • キャッシュ関係なく確実にパケット転送が出来る
  • 新たに追加されるミドルウェアとかが無い

 この方式のデメリット

  • 各サーバーでチェックスクリプト動かす必要がある
  • 誤ってiptablesのDaemon落とすとredisに接続できなくなってしまう
  • 想定切替秒数:最大25秒 

6. FuelPHP改修方式

image

  • Sentinelプロセス監視して自動failoverするようにフレームワークかアプリケーションを改修

 この方式のメリット

  • マージされるかライブラリを公開すれば世の役に立てる(かも)

 この方式のデメリット

  • 結構大変かも

7. twemproxy方式

image

  • twitter社が開発したMemcached / redisのproxyサーバー
  • https://github.com/twitter/twemproxy
  • 主にシャーディング用途で使われる。今回の要件には合わなかった。

というわけで色々考えて検証したわけですが最終的に5番目のiptables方式を採用しました。

image

Sentinelを監視するスクリプトはこんな感じです。このスクリプト、最初は私がやっつけで作ったんですがDBAの田中さんに思いっきり修正されました。

参考までにredis.confとsentinel.conf(認証情報は適切に入れましょう)

このような対策で以下の事が出来るようになりました。

  • Redisに接続できない時間は最大で60秒程度です。
  • この時アプリケーション側できちんとエラー処理されてればMySQLに接続します。(元々実装済みでしたが改めてテストしたら抜けがあって503返しちゃうケースがありました)60秒くらいならMySQLも頑張れます。
  • 安心して眠りにつけます。

Sentinelありがとう!

 


名無しのエンジニア
mroongaをXtraDB Clusterで冗長化できそうなメモ
放課後社内LT会 春の陣