在 Redis Sentinel 环境中运行 redis-namespace 和 sidekiq
这篇文章距离最后更新已经过去了一年以上。请小心。
使用Redis Sentinel構建的Redis集群环境相当复杂,建议客户端意识到Redis是Sentinel环境,最好使用专用驱动程序。
我在Redis Sentinel环境中尝试使用Ruby常用的Redis库redis-namespace和Sidekiq。
修正信息:我们之前使用了一个Monkey Patch来实现对Sidekiq的支持,然而由于Sidekiq的开发者Perham先生亲自给出了指正,我们决定修正这篇文章。
这就是你应该配置Redis Sentinel的方式。请不要进行专门订制。
因为有一个叫做 Redis Sentinel 的宝石,所以我决定使用它。
使用redis-sentinel对redis-namespace进行重写
首先,我们将通过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时似乎不需要特别考虑。
使用Redis哨兵与Sidekiq
下一个是Sidekiq,首先是附带的示例。
从Sinatra的WEB界面将任务加入队列,然后由worker进行处理。
# 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
通过Sentinel连接Sidekiq的方法可以在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 %>
※在初版中这里是用猴子补丁修复的,看起来现在已经能够正确地设置了。
样本日志
使用上述样例,通过 `bundle exec` sidekiq -r ./sinkiq_sentinel.rb -v 命令启动 `sidekiq_worker`,将 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
变得很棒了,对吧。 dé le, duì ba.)