Skip to content

Commit 998fb73

Browse files
committed
Add the :nearest mode, which selects the closest node by ping, regardless of role
1 parent d1ee530 commit 998fb73

File tree

2 files changed

+88
-26
lines changed

2 files changed

+88
-26
lines changed

lib/redis/client.rb

+37-22
Original file line numberDiff line numberDiff line change
@@ -484,7 +484,8 @@ def check(client)
484484

485485
class Sentinel < Connector
486486
EXPECTED_ROLES = {
487-
"nearest_slave" => "slave"
487+
"nearest_slave" => "slave",
488+
"nearest" => "any"
488489
}
489490

490491
def initialize(options)
@@ -502,14 +503,15 @@ def check(client)
502503
# Check the instance is really of the role we are looking for.
503504
# We can't assume the command is supported since it was introduced
504505
# recently and this client should work with old stuff.
506+
expected_role = EXPECTED_ROLES.fetch(@role, @role)
505507
begin
506508
role = client.call([:role])[0]
507509
rescue Redis::CommandError
508510
# Assume the test is passed if we can't get a reply from ROLE...
509-
role = EXPECTED_ROLES.fetch(@role, @role)
511+
role = expected_role
510512
end
511513

512-
if role != EXPECTED_ROLES.fetch(@role, @role)
514+
if role != expected_role && "any" != expected_role
513515
client.disconnect
514516
raise ConnectionError, "Instance role mismatch. Expected #{EXPECTED_ROLES.fetch(@role, @role)}, got #{role}."
515517
end
@@ -521,6 +523,8 @@ def resolve
521523
resolve_master
522524
when "slave"
523525
resolve_slave
526+
when "nearest"
527+
resolve_nearest
524528
when "nearest_slave"
525529
resolve_nearest_slave
526530
else
@@ -573,30 +577,41 @@ def resolve_slave
573577
end
574578
end
575579

580+
def resolve_nearest
581+
resolve_nearest_for [:master, :slaves]
582+
end
583+
576584
def resolve_nearest_slave
585+
resolve_nearest_for [:slaves]
586+
end
587+
588+
def resolve_nearest_for(types)
577589
sentinel_detect do |client|
578-
if reply = client.call(["sentinel", "slaves", @master])
579-
ok_slaves = reply.map {|r| Hash[*r] }.select {|r| r["master-link-status"] == "ok" }
580-
581-
ok_slaves.each do |slave|
582-
client = Client.new @options.merge(
583-
:host => slave["ip"],
584-
:port => slave["port"],
585-
:reconnect_attempts => 0
586-
)
587-
begin
588-
client.call [:ping]
589-
start = Time.now
590-
client.call [:ping]
591-
slave["response_time"] = (Time.now - start).to_f
592-
ensure
593-
client.disconnect
594-
end
590+
ok_nodes = []
591+
types.each do |type|
592+
if reply = client.call(["sentinel", type, @master])
593+
ok_nodes += reply.map {|r| Hash[*r] }.select {|r| r["role-reported"] == "master" || r["master-link-status"] == "ok" }
595594
end
595+
end
596596

597-
slave = ok_slaves.sort_by {|slave| slave["response_time"] }.first
598-
{:host => slave.fetch("ip"), :port => slave.fetch("port")} if slave
597+
ok_nodes.each do |node|
598+
client = Client.new @options.merge(
599+
:host => node["ip"],
600+
:port => node["port"],
601+
:reconnect_attempts => 0
602+
)
603+
begin
604+
client.call [:ping]
605+
start = Time.now
606+
client.call [:ping]
607+
node["response_time"] = (Time.now - start).to_f
608+
ensure
609+
client.disconnect
610+
end
599611
end
612+
613+
node = ok_nodes.sort_by {|node| node["response_time"] }.first
614+
{:host => node.fetch("ip"), :port => node.fetch("port")} if node
600615
end
601616
end
602617

test/sentinel_test.rb

+51-4
Original file line numberDiff line numberDiff line change
@@ -253,13 +253,60 @@ def test_sentinel_retries
253253
assert_match(/No sentinels available/, ex.message)
254254
end
255255

256+
def test_sentinel_nearest
257+
sentinels = [{:host => "127.0.0.1", :port => 26381}]
258+
259+
master = { :role => lambda { ["master"] }, :node_id => lambda { ["master"] }, :ping => lambda { ["OK"] } }
260+
s1 = { :role => lambda { ["slave"] }, :node_id => lambda { ["1"] }, :ping => lambda { sleep 0.1; ["OK"] } }
261+
s2 = { :role => lambda { ["slave"] }, :node_id => lambda { ["2"] }, :ping => lambda { sleep 0.2; ["OK"] } }
262+
s3 = { :role => lambda { ["slave"] }, :node_id => lambda { ["3"] }, :ping => lambda { sleep 0.3; ["OK"] } }
263+
264+
5.times do
265+
RedisMock.start(master) do |master_port|
266+
RedisMock.start(s1) do |s1_port|
267+
RedisMock.start(s2) do |s2_port|
268+
RedisMock.start(s3) do |s3_port|
269+
270+
sentinel = lambda do |port|
271+
{
272+
:sentinel => lambda do |command, *args|
273+
case command
274+
when "master"
275+
[
276+
%W[role-reported master ip 127.0.0.1 port #{master_port}]
277+
]
278+
when "slaves"
279+
[
280+
%W[master-link-status down ip 127.0.0.1 port #{s1_port}],
281+
%W[master-link-status ok ip 127.0.0.1 port #{s2_port}],
282+
%W[master-link-status ok ip 127.0.0.1 port #{s3_port}]
283+
].shuffle
284+
else
285+
["127.0.0.1", port.to_s]
286+
end
287+
end
288+
}
289+
end
290+
291+
RedisMock.start(sentinel.call(master_port)) do |sen_port|
292+
sentinels[0][:port] = sen_port
293+
redis = Redis.new(:url => "redis://master1", :sentinels => sentinels, :role => :nearest)
294+
assert_equal ["master"], redis.node_id
295+
end
296+
end
297+
end
298+
end
299+
end
300+
end
301+
end
302+
256303
def test_sentinel_nearest_slave
257304
sentinels = [{:host => "127.0.0.1", :port => 26381}]
258305

259306
master = { :role => lambda { ["master"] } }
260-
s1 = { :role => lambda { ["slave"] }, :slave_id => lambda { ["1"] }, :ping => lambda { ["OK"] } }
261-
s2 = { :role => lambda { ["slave"] }, :slave_id => lambda { ["2"] }, :ping => lambda { sleep 0.1; ["OK"] } }
262-
s3 = { :role => lambda { ["slave"] }, :slave_id => lambda { ["3"] }, :ping => lambda { sleep 0.2; ["OK"] } }
307+
s1 = { :role => lambda { ["slave"] }, :node_id => lambda { ["1"] }, :ping => lambda { ["OK"] } }
308+
s2 = { :role => lambda { ["slave"] }, :node_id => lambda { ["2"] }, :ping => lambda { sleep 0.1; ["OK"] } }
309+
s3 = { :role => lambda { ["slave"] }, :node_id => lambda { ["3"] }, :ping => lambda { sleep 0.2; ["OK"] } }
263310

264311
5.times do
265312
RedisMock.start(master) do |master_port|
@@ -287,7 +334,7 @@ def test_sentinel_nearest_slave
287334
RedisMock.start(sentinel.call(master_port)) do |sen_port|
288335
sentinels[0][:port] = sen_port
289336
redis = Redis.new(:url => "redis://master1", :sentinels => sentinels, :role => :nearest_slave)
290-
assert_equal redis.slave_id, ["2"]
337+
assert_equal redis.node_id, ["2"]
291338
end
292339
end
293340
end

0 commit comments

Comments
 (0)