Skip to content

Commit 0e52d43

Browse files
committed
Add the :nearest mode, which selects the closest node by ping, regardless of role
1 parent 3893355 commit 0e52d43

File tree

2 files changed

+53
-29
lines changed

2 files changed

+53
-29
lines changed

lib/redis/client.rb

Lines changed: 44 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -530,7 +530,8 @@ def check(client); end
530530

531531
class Sentinel < Connector
532532
EXPECTED_ROLES = {
533-
"nearest_slave" => "slave"
533+
"nearest_slave" => "slave",
534+
"nearest" => "any"
534535
}
535536

536537
def initialize(options)
@@ -547,14 +548,15 @@ def check(client)
547548
# Check the instance is really of the role we are looking for.
548549
# We can't assume the command is supported since it was introduced
549550
# recently and this client should work with old stuff.
551+
expected_role = EXPECTED_ROLES.fetch(@role, @role)
550552
begin
551553
role = client.call([:role])[0]
552554
rescue Redis::CommandError
553555
# Assume the test is passed if we can't get a reply from ROLE...
554-
role = EXPECTED_ROLES.fetch(@role, @role)
556+
role = expected_role
555557
end
556558

557-
if role != EXPECTED_ROLES.fetch(@role, @role)
559+
if role != expected_role && "any" != expected_role
558560
client.disconnect
559561
raise ConnectionError, "Instance role mismatch. Expected #{EXPECTED_ROLES.fetch(@role, @role)}, got #{role}."
560562
end
@@ -566,6 +568,8 @@ def resolve
566568
resolve_master
567569
when "slave"
568570
resolve_slave
571+
when "nearest"
572+
resolve_nearest
569573
when "nearest_slave"
570574
resolve_nearest_slave
571575
else
@@ -629,30 +633,49 @@ def resolve_slave
629633
end
630634
end
631635

636+
def resolve_nearest
637+
resolve_nearest_for [:master, :slaves]
638+
end
639+
632640
def resolve_nearest_slave
641+
resolve_nearest_for [:slaves]
642+
end
643+
644+
def resolve_nearest_for(types)
633645
sentinel_detect do |client|
634-
if reply = client.call(["sentinel", "slaves", @master])
635-
ok_slaves = reply.map {|r| Hash[*r] }.select {|r| r["master-link-status"] == "ok" }
636-
637-
ok_slaves.each do |slave|
638-
client = Client.new @options.merge(
639-
:host => slave["ip"],
640-
:port => slave["port"],
641-
:reconnect_attempts => 0
642-
)
643-
begin
644-
client.call [:ping]
645-
start = Time.now
646-
client.call [:ping]
647-
slave["response_time"] = (Time.now - start).to_f
648-
ensure
649-
client.disconnect
646+
ok_nodes = []
647+
types.each do |type|
648+
if reply = client.call(["sentinel", type, @master])
649+
reply = [reply] if type == :master
650+
ok_nodes += reply.map {|r| Hash[*r] }.select do |r|
651+
case type
652+
when :master
653+
r["role-reported"] == "master"
654+
when :slaves
655+
r["master-link-status"] == "ok" && !r.fetch("flags", "").match(/s_down|disconnected/)
656+
end
650657
end
651658
end
659+
end
652660

653-
slave = ok_slaves.sort_by {|slave| slave["response_time"] }.first
654-
{:host => slave.fetch("ip"), :port => slave.fetch("port")} if slave
661+
ok_nodes.each do |node|
662+
client = Client.new @options.merge(
663+
:host => node["ip"],
664+
:port => node["port"],
665+
:reconnect_attempts => 0
666+
)
667+
begin
668+
client.call [:ping]
669+
start = Time.now
670+
client.call [:ping]
671+
node["response_time"] = (Time.now - start).to_f
672+
ensure
673+
client.disconnect
674+
end
655675
end
676+
677+
node = ok_nodes.sort_by {|node| node["response_time"] }.first
678+
{:host => node.fetch("ip"), :port => node.fetch("port")} if node
656679
end
657680
end
658681

test/sentinel_test.rb

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -378,13 +378,13 @@ def test_sentinel_with_string_option_keys
378378
assert_equal [%w[get-master-addr-by-name master1]], commands
379379
end
380380

381-
def test_sentinel_nearest_slave
381+
def test_sentinel_nearest
382382
sentinels = [{:host => "127.0.0.1", :port => 26381}]
383383

384-
master = { :role => lambda { ["master"] } }
385-
s1 = { :role => lambda { ["slave"] }, :slave_id => lambda { ["1"] }, :ping => lambda { ["OK"] } }
386-
s2 = { :role => lambda { ["slave"] }, :slave_id => lambda { ["2"] }, :ping => lambda { sleep 0.1; ["OK"] } }
387-
s3 = { :role => lambda { ["slave"] }, :slave_id => lambda { ["3"] }, :ping => lambda { sleep 0.2; ["OK"] } }
384+
master = { :role => lambda { ["master"] }, :node_id => lambda { ["master"] }, :ping => lambda { ["OK"] } }
385+
s1 = { :role => lambda { ["slave"] }, :node_id => lambda { ["1"] }, :ping => lambda { sleep 0.1; ["OK"] } }
386+
s2 = { :role => lambda { ["slave"] }, :node_id => lambda { ["2"] }, :ping => lambda { sleep 0.2; ["OK"] } }
387+
s3 = { :role => lambda { ["slave"] }, :node_id => lambda { ["3"] }, :ping => lambda { sleep 0.3; ["OK"] } }
388388

389389
5.times do
390390
RedisMock.start(master) do |master_port|
@@ -396,6 +396,8 @@ def test_sentinel_nearest_slave
396396
{
397397
:sentinel => lambda do |command, *args|
398398
case command
399+
when "master"
400+
%W[role-reported master ip 127.0.0.1 port #{master_port}]
399401
when "slaves"
400402
[
401403
%W[master-link-status down ip 127.0.0.1 port #{s1_port}],
@@ -411,14 +413,13 @@ def test_sentinel_nearest_slave
411413

412414
RedisMock.start(sentinel.call(master_port)) do |sen_port|
413415
sentinels[0][:port] = sen_port
414-
redis = Redis.new(:url => "redis://master1", :sentinels => sentinels, :role => :nearest_slave)
415-
assert_equal redis.slave_id, ["2"]
416+
redis = Redis.new(:url => "redis://master1", :sentinels => sentinels, :role => :nearest)
417+
assert_equal ["master"], redis.node_id
416418
end
417419
end
418420
end
419421
end
420422
end
421423
end
422-
423424
end
424425
end

0 commit comments

Comments
 (0)