diff --git a/lib/redis/client.rb b/lib/redis/client.rb index e95839669..d05386af9 100755 --- a/lib/redis/client.rb +++ b/lib/redis/client.rb @@ -530,7 +530,8 @@ def check(client); end class Sentinel < Connector EXPECTED_ROLES = { - "nearest_slave" => "slave" + "nearest_slave" => "slave", + "nearest" => "any" } def initialize(options) @@ -547,14 +548,15 @@ def check(client) # Check the instance is really of the role we are looking for. # We can't assume the command is supported since it was introduced # recently and this client should work with old stuff. + expected_role = EXPECTED_ROLES.fetch(@role, @role) begin role = client.call([:role])[0] rescue Redis::CommandError # Assume the test is passed if we can't get a reply from ROLE... - role = EXPECTED_ROLES.fetch(@role, @role) + role = expected_role end - if role != EXPECTED_ROLES.fetch(@role, @role) + if role != expected_role && "any" != expected_role client.disconnect raise ConnectionError, "Instance role mismatch. Expected #{EXPECTED_ROLES.fetch(@role, @role)}, got #{role}." end @@ -566,6 +568,8 @@ def resolve resolve_master when "slave" resolve_slave + when "nearest" + resolve_nearest when "nearest_slave" resolve_nearest_slave else @@ -629,30 +633,49 @@ def resolve_slave end end + def resolve_nearest + resolve_nearest_for [:master, :slaves] + end + def resolve_nearest_slave + resolve_nearest_for [:slaves] + end + + def resolve_nearest_for(types) sentinel_detect do |client| - if reply = client.call(["sentinel", "slaves", @master]) - ok_slaves = reply.map {|r| Hash[*r] }.select {|r| r["master-link-status"] == "ok" } - - ok_slaves.each do |slave| - client = Client.new @options.merge( - :host => slave["ip"], - :port => slave["port"], - :reconnect_attempts => 0 - ) - begin - client.call [:ping] - start = Time.now - client.call [:ping] - slave["response_time"] = (Time.now - start).to_f - ensure - client.disconnect + ok_nodes = [] + types.each do |type| + if reply = client.call(["sentinel", type, @master]) + reply = [reply] if type == :master + ok_nodes += reply.map {|r| Hash[*r] }.select do |r| + case type + when :master + r["role-reported"] == "master" + when :slaves + r["master-link-status"] == "ok" && !r.fetch("flags", "").match(/s_down|disconnected/) + end end end + end - slave = ok_slaves.sort_by {|slave| slave["response_time"] }.first - {:host => slave.fetch("ip"), :port => slave.fetch("port")} if slave + ok_nodes.each do |node| + client = Client.new @options.merge( + :host => node["ip"], + :port => node["port"], + :reconnect_attempts => 0 + ) + begin + client.call [:ping] + start = Time.now + client.call [:ping] + node["response_time"] = (Time.now - start).to_f + ensure + client.disconnect + end end + + node = ok_nodes.sort_by {|node| node["response_time"] }.first + {:host => node.fetch("ip"), :port => node.fetch("port")} if node end end diff --git a/test/sentinel_test.rb b/test/sentinel_test.rb index cce153e5e..8cf51bbb5 100755 --- a/test/sentinel_test.rb +++ b/test/sentinel_test.rb @@ -378,13 +378,13 @@ def test_sentinel_with_string_option_keys assert_equal [%w[get-master-addr-by-name master1]], commands end - def test_sentinel_nearest_slave + def test_sentinel_nearest sentinels = [{:host => "127.0.0.1", :port => 26381}] - master = { :role => lambda { ["master"] } } - s1 = { :role => lambda { ["slave"] }, :slave_id => lambda { ["1"] }, :ping => lambda { ["OK"] } } - s2 = { :role => lambda { ["slave"] }, :slave_id => lambda { ["2"] }, :ping => lambda { sleep 0.1; ["OK"] } } - s3 = { :role => lambda { ["slave"] }, :slave_id => lambda { ["3"] }, :ping => lambda { sleep 0.2; ["OK"] } } + master = { :role => lambda { ["master"] }, :node_id => lambda { ["master"] }, :ping => lambda { ["OK"] } } + s1 = { :role => lambda { ["slave"] }, :node_id => lambda { ["1"] }, :ping => lambda { sleep 0.1; ["OK"] } } + s2 = { :role => lambda { ["slave"] }, :node_id => lambda { ["2"] }, :ping => lambda { sleep 0.2; ["OK"] } } + s3 = { :role => lambda { ["slave"] }, :node_id => lambda { ["3"] }, :ping => lambda { sleep 0.3; ["OK"] } } 5.times do RedisMock.start(master) do |master_port| @@ -396,6 +396,8 @@ def test_sentinel_nearest_slave { :sentinel => lambda do |command, *args| case command + when "master" + %W[role-reported master ip 127.0.0.1 port #{master_port}] when "slaves" [ %W[master-link-status down ip 127.0.0.1 port #{s1_port}], @@ -411,14 +413,13 @@ def test_sentinel_nearest_slave RedisMock.start(sentinel.call(master_port)) do |sen_port| sentinels[0][:port] = sen_port - redis = Redis.new(:url => "redis://master1", :sentinels => sentinels, :role => :nearest_slave) - assert_equal redis.slave_id, ["2"] + redis = Redis.new(:url => "redis://master1", :sentinels => sentinels, :role => :nearest) + assert_equal ["master"], redis.node_id end end end end end end - end end