この記事は最終更新から1年以上経過しています。 気をつけてね。
Redis Sentinelで構成されたRedisのクラスタ環境はそこそこに複雑です、クライアントはRedisがSentinel環境であることを意識して専用のドライバを使うほうがよいでしょう。
Rubyでよく使うRedisのライブラリ、redis-namespaceと SidekiqをRedis Sentinel 環境の環境で使ってみました。
修正情報:sidekiq対応がモンキーパッチでしたが、Sidekiq開発者のPerham氏から直々にツッコミを頂いたので記事を修正。
This is how you should configure Redis Sentinel. Please don't monkeypatch. https://t.co/gKf98gnivd
redis-sentinelというGemがあるので、それを利用することにします。
redis-namespace with redis-sentinel
まずredis-sentinelのサンプルで使い方を確認します。Redis::Client
をオープンしてsentinel
関係の設定がうまいことやってくれるようになっていました。テスト環境起動用のコンフィグもついていたのですぐに試せます。
require 'redis'
require 'redis-sentinel'
redis = Redis.new(:master_name => "example-test",
:sentinels => [
{:host => "localhost", :port => 26379},
{:host => "localhost", :port => 26380}
])
redis.set "foo", "bar"
while true
begin
puts redis.get "foo"
rescue => e
puts "failed?", e
end
sleep 1
end
このサンプルからredis-namespaceを使うようにしたのがこちら。
普段の使い方と通りにRedis Sentinelの環境に適応させることができました。
require 'redis'
require 'redis-namespace'
require 'redis-sentinel'
redis_s = Redis.new(:master_name => "example-test",
:sentinels => [
{:host => "localhost", :port => 26379},
{:host => "localhost", :port => 26380}
])
redis = Redis::Namespace.new(:myspace, :redis => redis_s)
redis.set "foo", "bar"
while true
begin
puts redis.get "foo"
rescue => e
puts "failed?", e
end
sleep 1
end
redis-namespaceに関しては特に考慮なしでよさそうですね。
Sidekiq with redis-sentinel
次はSidekiqです、まずは付属のサンプル。
sinatra
のWEBインターフェースからキューして、ワーカに処理させています。
# Make sure you have Sinatra installed, then start sidekiq with
# ./bin/sidekiq -r ./examples/sinkiq.rb
# Simply run Sinatra with
# ruby examples/sinkiq.rb
# and then browse to http://localhost:4567
#
require 'sinatra'
require 'sidekiq'
require 'redis'
$redis = Redis.new
class SinatraWorker
include Sidekiq::Worker
def perform(msg="lulz you forgot a msg!")
$redis.lpush("sinkiq-example-messages", msg)
end
end
get '/' do
stats = Sidekiq::Stats.new
@failed = stats.failed
@processed = stats.processed
@messages = $redis.lrange('sinkiq-example-messages', 0, -1)
erb :index
end
post '/msg' do
SinatraWorker.perform_async params[:msg]
redirect to('/')
end
__END__
@@ layout
<html>
<head>
<title>Sinatra + Sidekiq</title>
<body>
<%= yield %>
</body>
</html>
@@ index
<h1>Sinatra + Sidekiq Example</h1>
<h2>Failed: <%= @failed %></h2>
<h2>Processed: <%= @processed %></h2>
<form method="post" action="/msg">
<input type="text" name="msg">
<input type="submit" value="Add Message">
</form>
<a href="/">Refresh page</a>
<h3>Messages</h3>
<% @messages.each do |msg| %>
<p><%= msg %></p>
<% end %>
起動コマンド
一応このサンプルの起動コマンドはヘッダに書いてあるとおりこんな感じです。
- sinatra_web:
(bundle exec) ruby ./sidekiq/examples/sinkiq.rb
- sidekiq_worker:
(bundle exec) sidekiq -r ./sidekiq/examples/sinkiq.rb -v
sinatraはlocalhost:4567
で起動するので、メッセージをキューしてワーカの出力を見るといいです。
Sentinelを使う
SidekiqをSentinel経由にする方法は Advanced Options | sidekiq wiki にありました。
Redisのインスタンスを自由に設定するやり方を参考に前述のサンプルを変更してみます。
require 'sinatra'
require 'sidekiq'
require 'redis'
require 'redis-sentinel'
redis_conn = proc {
Redis.new(:master_name => "example-test",
:sentinels => [
{:host => "localhost", :port => 26379},
{:host => "localhost", :port => 26380}
])
}
Sidekiq.configure_server do |config|
config.redis = ConnectionPool.new(size: 10, &redis_conn)
end
Sidekiq.configure_client do |config|
config.redis = ConnectionPool.new(size: 5, &redis_conn)
end
class SinatraWorker
include Sidekiq::Worker
def perform(msg="lulz you forgot a msg!")
$redis.lpush("sinkiq-example-messages", msg)
end
end
get '/' do
stats = Sidekiq::Stats.new
@failed = stats.failed
@processed = stats.processed
@messages = $redis.lrange('sinkiq-example-messages', 0, -1)
erb :index
end
post '/msg' do
SinatraWorker.perform_async params[:msg]
redirect to('/')
end
__END__
@@ layout
<html>
<head>
<title>Sinatra + Sidekiq</title>
<body>
<%= yield %>
</body>
</html>
@@ index
<h1>Sinatra + Sidekiq Example</h1>
<h2>Failed: <%= @failed %></h2>
<h2>Processed: <%= @processed %></h2>
<form method="post" action="/msg">
<input type="text" name="msg">
<input type="submit" value="Add Message">
</form>
<a href="/">Refresh page</a>
<h3>Messages</h3>
<% @messages.each do |msg| %>
<p><%= msg %></p>
<% end %>
※初版ではここはモンキーパッチでした、ちゃんとスマートに設定できたようです。
サンプルログ
上記サンプルを使って、sidekiq_workerを (bundle exec) sidekiq -r ./sinkiq_sentinel.rb -v
で起動すればStat APIがデータストアにRedis Sentinel環境を使いだします。
ワーカはそれぞれID(@hash)を持っているようなので複数起動しても問題無さ気でした。
マスタ切替時のログはこの様に出力されます。
TID-oxgwckouo INFO: Running in ruby 2.0.0p247 (2013-06-27 revision 41674) [x86_64-darwin12.3.0]
TID-oxgwcg8yk ERROR: Error fetching message: Error connecting to Redis on 127.0.0.1:16380 (ECONNREFUSED)
# -- 接続切れた
TID-oxgwcg8yk ERROR: /Users/sawanoboriyu/.rvm/gems/ruby-2.0.0-p247@redis-sentinel/gems/redis-3.0.4/lib/redis/client.rb:276:in `rescue in
TID-oxgwcg8yk ERROR: /Users/sawanoboriyu/.rvm/gems/ruby-2.0.0-p247@redis-sentinel/gems/redis-3.0.4/lib/redis/client.rb:271:in `establish
TID-oxgwcg8yk ERROR: /Users/sawanoboriyu/.rvm/gems/ruby-2.0.0-p247@redis-sentinel/gems/redis-3.0.4/lib/redis/client.rb:69:in `connect'
TID-oxgwcg8yk ERROR: /Users/sawanoboriyu/github/others/redis-sentinel/lib/redis-sentinel/client.rb:25:in `block in connect_with_sentinel
TID-oxgwcg8yk ERROR: /Users/sawanoboriyu/github/others/redis-sentinel/lib/redis-sentinel/client.rb:42:in `call'
TID-oxgwcg8yk ERROR: /Users/sawanoboriyu/github/others/redis-sentinel/lib/redis-sentinel/client.rb:42:in `auto_retry_with_timeout'
# -- redis-sentinel gemによるマスタ再発見まわり
TID-oxgwcg8yk ERROR: /Users/sawanoboriyu/github/others/redis-sentinel/lib/redis-sentinel/client.rb:23:in `connect_with_sentinel'
TID-oxgwevm7g ERROR: The master: example-test is currently not available.
TID-oxgwevm7g ERROR: /Users/sawanoboriyu/github/others/redis-sentinel/lib/redis-sentinel/client.rb:75:in `discover_master'
# -- 復旧
TID-oxgwcg8yk INFO: Redis is online, 16.16007900238037 sec downtime
良い感じに切り替わりましたね。