Ruby から Redis Cluster を使うための gem を作ってみた
概要
もう半年くらい前のことになりますが、ElastiCache で Redis の 3.2 系がサポートされ、Redis Cluster が使えるようになりました (https://aws.amazon.com/jp/blogs/news/amazon-elasticache-for-redis-update-sharded-clusters-engine-improvements-and-more/ )。これで書き込みを大量にするケースでもスケールアウトできるし、最大で 3.5TiB のメモリ上にデータをのせることができるようになりました。
早速 Ruby で使ってみようと思ったら Ruby 用のまともな Redis Cluster の gem が見つからなかったので作ってみました。この記事では簡単に使い方を紹介してみようかと思います。
Renoir
redis-cluster
という gem 名がすでに取られていたので、Memcached のそこそこ有名な gem である dalli
を微妙に意識しつつ renoir
という名前にしました。
Gem: https://rubygems.org/gems/renoir
GitHub: https://github.com/saidie/renoir
Renoir がやってくれること
例えば、gem install renoir
した後、こんな感じで使えます。
require 'renoir'
rc = Renoir::Client.new(cluster_nodes: ['127.0.0.1:30001'])
p rc.set('hoge', 123, nx: true)
p rc.zrange('fuga', 0, -1, with_scores: true)
127.0.0.1:30001 がクラスタの一つのノードのアドレスです。クラスタは例えば http://qiita.com/uryyyyyyy/items/fc767f7f41144e5f10a1 に載っている create-cluster スクリプトを使うと簡単に立ち上げられます。
上記コードでは hoge
というキーに値をセットし、fuga
というキーの sorted-sets から全要素とスコアを取得しています。シングルインスタンスの Redis にこれらのコマンドを投げると、普通にそのインスタンスがコマンドを処理してレスポンスを返してくるわけですが、Redis Cluster の場合はキー毎にそれを担当する Redis のインスタンス (ノード) が異なっているので、コマンドを投げるべきノードを同定する必要があります。間違ったノードにコマンドを投げると、正しいノードへのリダイレクト指示 (MOVED
レスポンス) が返ってきます。また、ノード追加に伴う resharding などでキーをノード間で移行したりすることがあるのですが、この最中はあるキーを見つけるために移行先と移行元の両方のノードに問い合わせる必要があったりします。
で、Renoir はこれら
- キー (正確にはキースロット) とノードの割当情報の管理
- リダイレクトのハンドリング
- 移行中のキーのハンドリング
をよしなにやってくれるというわけです。
redis
gem との互換性
個人的に Ruby から Redis を使う場合は redis
gem をよく使っており、同じように使えるようにしたいなと思ったので、Renoir::Client
は ::Redis
と「できるだけ」互換性のあるインターフェースを提供するという設計方針で実装しました。また、内部的なコネクションとしても ::Redis
インスタンスを使っています。というわけで、redis
と同様にドライバとして hiredis
を使ったりもできます。redis
gem は割とメジャーだと思うので、学習コストほぼなく使えるのではないかと思います。
ちなみに、このあたりはアダプタ (Renoir::ConnectionAdapters::
系) で吸収しているので、例えば redic
向けのアダプタを提供することもできます (そのうち実装するかも?)
注意点としては、::Redis
の全てのメソッドが同じように使えるわけではない、ということです。すなわち、コマンドのキーからノードを特定できない場合は、コマンドの送り先が不定なので Renoir::Client
はエラーを返します。例えば、
-
MSET
,MGET
などで複数キーを指定して、それらのキースロットが異なる場合- 全部同一キースロットなら OK なので、キーハッシュタグ を使って下さい
-
KEYS
,INFO
などそもそもキーがないコマンド- ただし、
KEYS
に関してはパターンにキーハッシュタグが明示的に含まれてたら良しとするかもしれないです
- ただし、
などです。後者のケースは、Renoir::Client#each_node
が全てのノードの ::Redis
インスタンスを返すので、それを使って置き換えることはできます。
実際に使う時
gem をテストしている時に遭遇したいくつかの例外を考慮して、以下のような感じでコードを書くのが良さそうです。
rc = Renoir::Client.new(cluster_nodes: ['127.0.0.1:30001'])
begin
rc.set('hoge', 123, nx: true)
rescue Redis::CannotConnectError
# そもそもノードに繋がらないケース。そのうち直るかもしれないのでリトライしてみるといいかも
rescue Redis::ConnectionError, Redis::TimeoutError
# 繋がったんだけど途中で切れたケース。コマンドが既に届いてる可能性があるからリトライしない方がいいかも
rescue Redis::CommandError => e
if /CLUSTERDOWN/.match(e.message)
# failover 中などクラスタの状態が不完全な時に返ってくる。そのうち直るはずなのでリトライすると良さげ
end
raise
rescue Renoir::RedirectionError
# ノード間のクラスタ情報の差異が大きいと起こる可能性があるけど低確率だと思う
# クラスタに存在しなかったキーのスロットが移行中で起きる(たぶん)ケースが多いはず
# 数回リトライしてもいいかも
end
まとめ
redis
gem と同じような手触りで使うことのできる Redis Cluster 用の gem の紹介でした。より詳しくはドキュメント http://www.rubydoc.info/gems/renoir とかを見ていただければと思います。
ちなみに、このコードは Redis の作者である antirez 氏のリファレンス実装 https://github.com/antirez/redis-rb-cluster をベースにしていろいろ整理して gem にしたものになっています。
Author And Source
この問題について(Ruby から Redis Cluster を使うための gem を作ってみた), 我々は、より多くの情報をここで見つけました https://qiita.com/saidie/items/784c2f4ce9bb64d56460著者帰属:元の著者の情報は、元のURLに含まれています。著作権は原作者に属する。
Content is automatically searched and collected through network algorithms . If there is a violation . Please contact us . We will adjust (correct author information ,or delete content ) as soon as possible .