在 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.)

bannerAds