redis 3.3.5 → 4.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +5 -5
- data/CHANGELOG.md +132 -2
- data/README.md +144 -79
- data/lib/redis.rb +1174 -405
- data/lib/redis/client.rb +150 -90
- data/lib/redis/cluster.rb +295 -0
- data/lib/redis/cluster/command.rb +81 -0
- data/lib/redis/cluster/command_loader.rb +34 -0
- data/lib/redis/cluster/key_slot_converter.rb +72 -0
- data/lib/redis/cluster/node.rb +107 -0
- data/lib/redis/cluster/node_key.rb +31 -0
- data/lib/redis/cluster/node_loader.rb +37 -0
- data/lib/redis/cluster/option.rb +93 -0
- data/lib/redis/cluster/slot.rb +86 -0
- data/lib/redis/cluster/slot_loader.rb +49 -0
- data/lib/redis/connection.rb +4 -2
- data/lib/redis/connection/command_helper.rb +5 -10
- data/lib/redis/connection/hiredis.rb +6 -5
- data/lib/redis/connection/registry.rb +2 -1
- data/lib/redis/connection/ruby.rb +126 -128
- data/lib/redis/connection/synchrony.rb +21 -8
- data/lib/redis/distributed.rb +147 -72
- data/lib/redis/errors.rb +48 -0
- data/lib/redis/hash_ring.rb +30 -73
- data/lib/redis/pipeline.rb +55 -15
- data/lib/redis/subscribe.rb +11 -12
- data/lib/redis/version.rb +3 -1
- metadata +49 -202
- data/.gitignore +0 -16
- data/.travis.yml +0 -89
- data/.travis/Gemfile +0 -11
- data/.yardopts +0 -3
- data/Gemfile +0 -4
- data/Rakefile +0 -87
- data/benchmarking/logging.rb +0 -71
- data/benchmarking/pipeline.rb +0 -51
- data/benchmarking/speed.rb +0 -21
- data/benchmarking/suite.rb +0 -24
- data/benchmarking/worker.rb +0 -71
- data/examples/basic.rb +0 -15
- data/examples/consistency.rb +0 -114
- data/examples/dist_redis.rb +0 -43
- data/examples/incr-decr.rb +0 -17
- data/examples/list.rb +0 -26
- data/examples/pubsub.rb +0 -37
- data/examples/sentinel.rb +0 -41
- data/examples/sentinel/sentinel.conf +0 -9
- data/examples/sentinel/start +0 -49
- data/examples/sets.rb +0 -36
- data/examples/unicorn/config.ru +0 -3
- data/examples/unicorn/unicorn.rb +0 -20
- data/redis.gemspec +0 -44
- data/test/bitpos_test.rb +0 -69
- data/test/blocking_commands_test.rb +0 -42
- data/test/client_test.rb +0 -59
- data/test/command_map_test.rb +0 -30
- data/test/commands_on_hashes_test.rb +0 -21
- data/test/commands_on_hyper_log_log_test.rb +0 -21
- data/test/commands_on_lists_test.rb +0 -20
- data/test/commands_on_sets_test.rb +0 -77
- data/test/commands_on_sorted_sets_test.rb +0 -137
- data/test/commands_on_strings_test.rb +0 -101
- data/test/commands_on_value_types_test.rb +0 -133
- data/test/connection_handling_test.rb +0 -277
- data/test/connection_test.rb +0 -57
- data/test/db/.gitkeep +0 -0
- data/test/distributed_blocking_commands_test.rb +0 -46
- data/test/distributed_commands_on_hashes_test.rb +0 -10
- data/test/distributed_commands_on_hyper_log_log_test.rb +0 -33
- data/test/distributed_commands_on_lists_test.rb +0 -22
- data/test/distributed_commands_on_sets_test.rb +0 -83
- data/test/distributed_commands_on_sorted_sets_test.rb +0 -18
- data/test/distributed_commands_on_strings_test.rb +0 -59
- data/test/distributed_commands_on_value_types_test.rb +0 -95
- data/test/distributed_commands_requiring_clustering_test.rb +0 -164
- data/test/distributed_connection_handling_test.rb +0 -23
- data/test/distributed_internals_test.rb +0 -79
- data/test/distributed_key_tags_test.rb +0 -52
- data/test/distributed_persistence_control_commands_test.rb +0 -26
- data/test/distributed_publish_subscribe_test.rb +0 -92
- data/test/distributed_remote_server_control_commands_test.rb +0 -66
- data/test/distributed_scripting_test.rb +0 -102
- data/test/distributed_sorting_test.rb +0 -20
- data/test/distributed_test.rb +0 -58
- data/test/distributed_transactions_test.rb +0 -32
- data/test/encoding_test.rb +0 -18
- data/test/error_replies_test.rb +0 -59
- data/test/fork_safety_test.rb +0 -65
- data/test/helper.rb +0 -232
- data/test/helper_test.rb +0 -24
- data/test/internals_test.rb +0 -417
- data/test/lint/blocking_commands.rb +0 -150
- data/test/lint/hashes.rb +0 -162
- data/test/lint/hyper_log_log.rb +0 -60
- data/test/lint/lists.rb +0 -143
- data/test/lint/sets.rb +0 -140
- data/test/lint/sorted_sets.rb +0 -316
- data/test/lint/strings.rb +0 -260
- data/test/lint/value_types.rb +0 -122
- data/test/persistence_control_commands_test.rb +0 -26
- data/test/pipelining_commands_test.rb +0 -242
- data/test/publish_subscribe_test.rb +0 -282
- data/test/remote_server_control_commands_test.rb +0 -118
- data/test/scanning_test.rb +0 -413
- data/test/scripting_test.rb +0 -78
- data/test/sentinel_command_test.rb +0 -80
- data/test/sentinel_test.rb +0 -255
- data/test/sorting_test.rb +0 -59
- data/test/ssl_test.rb +0 -73
- data/test/support/connection/hiredis.rb +0 -1
- data/test/support/connection/ruby.rb +0 -1
- data/test/support/connection/synchrony.rb +0 -17
- data/test/support/redis_mock.rb +0 -130
- data/test/support/ssl/gen_certs.sh +0 -31
- data/test/support/ssl/trusted-ca.crt +0 -25
- data/test/support/ssl/trusted-ca.key +0 -27
- data/test/support/ssl/trusted-cert.crt +0 -81
- data/test/support/ssl/trusted-cert.key +0 -28
- data/test/support/ssl/untrusted-ca.crt +0 -26
- data/test/support/ssl/untrusted-ca.key +0 -27
- data/test/support/ssl/untrusted-cert.crt +0 -82
- data/test/support/ssl/untrusted-cert.key +0 -28
- data/test/support/wire/synchrony.rb +0 -24
- data/test/support/wire/thread.rb +0 -5
- data/test/synchrony_driver.rb +0 -88
- data/test/test.conf.erb +0 -9
- data/test/thread_safety_test.rb +0 -62
- data/test/transactions_test.rb +0 -264
- data/test/unknown_commands_test.rb +0 -14
- data/test/url_param_test.rb +0 -138
data/lib/redis/client.rb
CHANGED
@@ -1,29 +1,38 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "errors"
|
2
4
|
require "socket"
|
3
5
|
require "cgi"
|
4
6
|
|
5
7
|
class Redis
|
6
8
|
class Client
|
7
|
-
|
9
|
+
# Defaults are also used for converting string keys to symbols.
|
8
10
|
DEFAULTS = {
|
9
|
-
:
|
10
|
-
:
|
11
|
-
:
|
12
|
-
:
|
13
|
-
:
|
14
|
-
:
|
15
|
-
:
|
16
|
-
:
|
17
|
-
:
|
18
|
-
:
|
19
|
-
:
|
20
|
-
:
|
21
|
-
:
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
11
|
+
url: -> { ENV["REDIS_URL"] },
|
12
|
+
scheme: "redis",
|
13
|
+
host: "127.0.0.1",
|
14
|
+
port: 6379,
|
15
|
+
path: nil,
|
16
|
+
read_timeout: nil,
|
17
|
+
write_timeout: nil,
|
18
|
+
connect_timeout: nil,
|
19
|
+
timeout: 5.0,
|
20
|
+
username: nil,
|
21
|
+
password: nil,
|
22
|
+
db: 0,
|
23
|
+
driver: nil,
|
24
|
+
id: nil,
|
25
|
+
tcp_keepalive: 0,
|
26
|
+
reconnect_attempts: 1,
|
27
|
+
reconnect_delay: 0,
|
28
|
+
reconnect_delay_max: 0.5,
|
29
|
+
inherit_socket: false,
|
30
|
+
logger: nil,
|
31
|
+
sentinels: nil,
|
32
|
+
role: nil
|
33
|
+
}.freeze
|
34
|
+
|
35
|
+
attr_reader :options
|
27
36
|
|
28
37
|
def scheme
|
29
38
|
@options[:scheme]
|
@@ -53,6 +62,10 @@ class Redis
|
|
53
62
|
@options[:read_timeout]
|
54
63
|
end
|
55
64
|
|
65
|
+
def username
|
66
|
+
@options[:username]
|
67
|
+
end
|
68
|
+
|
56
69
|
def password
|
57
70
|
@options[:password]
|
58
71
|
end
|
@@ -86,11 +99,14 @@ class Redis
|
|
86
99
|
|
87
100
|
@pending_reads = 0
|
88
101
|
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
102
|
+
@connector =
|
103
|
+
if !@options[:sentinels].nil?
|
104
|
+
Connector::Sentinel.new(@options)
|
105
|
+
elsif options.include?(:connector) && options[:connector].respond_to?(:new)
|
106
|
+
options.delete(:connector).new(@options)
|
107
|
+
else
|
108
|
+
Connector.new(@options)
|
109
|
+
end
|
94
110
|
end
|
95
111
|
|
96
112
|
def connect
|
@@ -99,7 +115,17 @@ class Redis
|
|
99
115
|
# Don't try to reconnect when the connection is fresh
|
100
116
|
with_reconnect(false) do
|
101
117
|
establish_connection
|
102
|
-
|
118
|
+
if password
|
119
|
+
if username
|
120
|
+
begin
|
121
|
+
call [:auth, username, password]
|
122
|
+
rescue CommandError # Likely on Redis < 6
|
123
|
+
call [:auth, password]
|
124
|
+
end
|
125
|
+
else
|
126
|
+
call [:auth, password]
|
127
|
+
end
|
128
|
+
end
|
103
129
|
call [:select, db] if db != 0
|
104
130
|
call [:client, :setname, @options[:id]] if @options[:id]
|
105
131
|
@connector.check(self)
|
@@ -120,7 +146,7 @@ class Redis
|
|
120
146
|
reply = process([command]) { read }
|
121
147
|
raise reply if reply.is_a?(CommandError)
|
122
148
|
|
123
|
-
if block_given?
|
149
|
+
if block_given? && reply != 'QUEUED'
|
124
150
|
yield reply
|
125
151
|
else
|
126
152
|
reply
|
@@ -152,13 +178,16 @@ class Redis
|
|
152
178
|
end
|
153
179
|
|
154
180
|
def call_pipeline(pipeline)
|
181
|
+
return [] if pipeline.futures.empty?
|
182
|
+
|
155
183
|
with_reconnect pipeline.with_reconnect? do
|
156
184
|
begin
|
157
|
-
pipeline.finish(call_pipelined(pipeline
|
185
|
+
pipeline.finish(call_pipelined(pipeline)).tap do
|
158
186
|
self.db = pipeline.db if pipeline.db
|
159
187
|
end
|
160
188
|
rescue ConnectionError => e
|
161
189
|
return nil if pipeline.shutdown?
|
190
|
+
|
162
191
|
# Assume the pipeline was sent in one piece, but execution of
|
163
192
|
# SHUTDOWN caused none of the replies for commands that were executed
|
164
193
|
# prior to it from coming back around.
|
@@ -167,8 +196,8 @@ class Redis
|
|
167
196
|
end
|
168
197
|
end
|
169
198
|
|
170
|
-
def call_pipelined(
|
171
|
-
return [] if
|
199
|
+
def call_pipelined(pipeline)
|
200
|
+
return [] if pipeline.futures.empty?
|
172
201
|
|
173
202
|
# The method #ensure_connected (called from #process) reconnects once on
|
174
203
|
# I/O errors. To make an effort in making sure that commands are not
|
@@ -178,6 +207,8 @@ class Redis
|
|
178
207
|
# already successfully executed commands. To circumvent this, don't retry
|
179
208
|
# after the first reply has been read successfully.
|
180
209
|
|
210
|
+
commands = pipeline.commands
|
211
|
+
|
181
212
|
result = Array.new(commands.size)
|
182
213
|
reconnect = @reconnect
|
183
214
|
|
@@ -185,13 +216,14 @@ class Redis
|
|
185
216
|
exception = nil
|
186
217
|
|
187
218
|
process(commands) do
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
result[i
|
219
|
+
pipeline.timeouts.each_with_index do |timeout, i|
|
220
|
+
reply = if timeout
|
221
|
+
with_socket_timeout(timeout) { read }
|
222
|
+
else
|
223
|
+
read
|
224
|
+
end
|
225
|
+
result[i] = reply
|
226
|
+
@reconnect = false
|
195
227
|
exception = reply if exception.nil? && reply.is_a?(CommandError)
|
196
228
|
end
|
197
229
|
end
|
@@ -234,12 +266,13 @@ class Redis
|
|
234
266
|
end
|
235
267
|
|
236
268
|
def connected?
|
237
|
-
!!
|
269
|
+
!!(connection && connection.connected?)
|
238
270
|
end
|
239
271
|
|
240
272
|
def disconnect
|
241
273
|
connection.disconnect if connected?
|
242
274
|
end
|
275
|
+
alias close disconnect
|
243
276
|
|
244
277
|
def reconnect
|
245
278
|
disconnect
|
@@ -274,12 +307,15 @@ class Redis
|
|
274
307
|
|
275
308
|
def with_socket_timeout(timeout)
|
276
309
|
connect unless connected?
|
310
|
+
original = @options[:read_timeout]
|
277
311
|
|
278
312
|
begin
|
279
313
|
connection.timeout = timeout
|
314
|
+
@options[:read_timeout] = timeout # for reconnection
|
280
315
|
yield
|
281
316
|
ensure
|
282
317
|
connection.timeout = self.timeout if connected?
|
318
|
+
@options[:read_timeout] = original
|
283
319
|
end
|
284
320
|
end
|
285
321
|
|
@@ -287,30 +323,27 @@ class Redis
|
|
287
323
|
with_socket_timeout(0, &blk)
|
288
324
|
end
|
289
325
|
|
290
|
-
def with_reconnect(val=true)
|
291
|
-
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
@reconnect = original
|
296
|
-
end
|
326
|
+
def with_reconnect(val = true)
|
327
|
+
original, @reconnect = @reconnect, val
|
328
|
+
yield
|
329
|
+
ensure
|
330
|
+
@reconnect = original
|
297
331
|
end
|
298
332
|
|
299
333
|
def without_reconnect(&blk)
|
300
334
|
with_reconnect(false, &blk)
|
301
335
|
end
|
302
336
|
|
303
|
-
|
337
|
+
protected
|
304
338
|
|
305
339
|
def logging(commands)
|
306
|
-
return yield unless @logger
|
340
|
+
return yield unless @logger&.debug?
|
307
341
|
|
308
342
|
begin
|
309
343
|
commands.each do |name, *args|
|
310
344
|
logged_args = args.map do |a|
|
311
|
-
|
312
|
-
|
313
|
-
when a.respond_to?(:to_s) then a.to_s
|
345
|
+
if a.respond_to?(:inspect) then a.inspect
|
346
|
+
elsif a.respond_to?(:to_s) then a.to_s
|
314
347
|
else
|
315
348
|
# handle poorly-behaved descendants of BasicObject
|
316
349
|
klass = a.instance_exec { (class << self; self end).superclass }
|
@@ -336,13 +369,17 @@ class Redis
|
|
336
369
|
@connection = @options[:driver].connect(@options)
|
337
370
|
@pending_reads = 0
|
338
371
|
rescue TimeoutError,
|
372
|
+
SocketError,
|
373
|
+
Errno::EADDRNOTAVAIL,
|
339
374
|
Errno::ECONNREFUSED,
|
340
375
|
Errno::EHOSTDOWN,
|
341
376
|
Errno::EHOSTUNREACH,
|
342
377
|
Errno::ENETUNREACH,
|
343
|
-
Errno::
|
378
|
+
Errno::ENOENT,
|
379
|
+
Errno::ETIMEDOUT,
|
380
|
+
Errno::EINVAL => error
|
344
381
|
|
345
|
-
raise CannotConnectError, "Error connecting to Redis on #{location} (#{
|
382
|
+
raise CannotConnectError, "Error connecting to Redis on #{location} (#{error.class})"
|
346
383
|
end
|
347
384
|
|
348
385
|
def ensure_connected
|
@@ -356,9 +393,9 @@ class Redis
|
|
356
393
|
if connected?
|
357
394
|
unless inherit_socket? || Process.pid == @pid
|
358
395
|
raise InheritedError,
|
359
|
-
|
360
|
-
|
361
|
-
|
396
|
+
"Tried to use a connection from a child process without reconnecting. " \
|
397
|
+
"You need to reconnect to Redis after forking " \
|
398
|
+
"or set :inherit_socket to true."
|
362
399
|
end
|
363
400
|
else
|
364
401
|
connect
|
@@ -369,6 +406,10 @@ class Redis
|
|
369
406
|
disconnect
|
370
407
|
|
371
408
|
if attempts <= @options[:reconnect_attempts] && @reconnect
|
409
|
+
sleep_t = [(@options[:reconnect_delay] * 2**(attempts - 1)),
|
410
|
+
@options[:reconnect_delay_max]].min
|
411
|
+
|
412
|
+
Kernel.sleep(sleep_t)
|
372
413
|
retry
|
373
414
|
else
|
374
415
|
raise
|
@@ -387,15 +428,14 @@ class Redis
|
|
387
428
|
|
388
429
|
defaults.keys.each do |key|
|
389
430
|
# Fill in defaults if needed
|
390
|
-
if defaults[key].respond_to?(:call)
|
391
|
-
defaults[key] = defaults[key].call
|
392
|
-
end
|
431
|
+
defaults[key] = defaults[key].call if defaults[key].respond_to?(:call)
|
393
432
|
|
394
433
|
# Symbolize only keys that are needed
|
395
|
-
options[key] = options[key.to_s] if options.
|
434
|
+
options[key] = options[key.to_s] if options.key?(key.to_s)
|
396
435
|
end
|
397
436
|
|
398
|
-
url = options[:url]
|
437
|
+
url = options[:url]
|
438
|
+
url = defaults[:url] if url.nil?
|
399
439
|
|
400
440
|
# Override defaults from URL if given
|
401
441
|
if url
|
@@ -404,12 +444,13 @@ class Redis
|
|
404
444
|
uri = URI(url)
|
405
445
|
|
406
446
|
if uri.scheme == "unix"
|
407
|
-
defaults[:path]
|
447
|
+
defaults[:path] = uri.path
|
408
448
|
elsif uri.scheme == "redis" || uri.scheme == "rediss"
|
409
449
|
defaults[:scheme] = uri.scheme
|
410
450
|
defaults[:host] = uri.host if uri.host
|
411
451
|
defaults[:port] = uri.port if uri.port
|
412
|
-
defaults[:
|
452
|
+
defaults[:username] = CGI.unescape(uri.user) if uri.user && !uri.user.empty?
|
453
|
+
defaults[:password] = CGI.unescape(uri.password) if uri.password && !uri.password.empty?
|
413
454
|
defaults[:db] = uri.path[1..-1].to_i if uri.path
|
414
455
|
defaults[:role] = :master
|
415
456
|
else
|
@@ -435,7 +476,7 @@ class Redis
|
|
435
476
|
options[:port] = options[:port].to_i
|
436
477
|
end
|
437
478
|
|
438
|
-
if options.
|
479
|
+
if options.key?(:timeout)
|
439
480
|
options[:connect_timeout] ||= options[:timeout]
|
440
481
|
options[:read_timeout] ||= options[:timeout]
|
441
482
|
options[:write_timeout] ||= options[:timeout]
|
@@ -445,12 +486,16 @@ class Redis
|
|
445
486
|
options[:read_timeout] = Float(options[:read_timeout])
|
446
487
|
options[:write_timeout] = Float(options[:write_timeout])
|
447
488
|
|
489
|
+
options[:reconnect_attempts] = options[:reconnect_attempts].to_i
|
490
|
+
options[:reconnect_delay] = options[:reconnect_delay].to_f
|
491
|
+
options[:reconnect_delay_max] = options[:reconnect_delay_max].to_f
|
492
|
+
|
448
493
|
options[:db] = options[:db].to_i
|
449
494
|
options[:driver] = _parse_driver(options[:driver]) || Connection.drivers.last
|
450
495
|
|
451
496
|
case options[:tcp_keepalive]
|
452
497
|
when Hash
|
453
|
-
[
|
498
|
+
%i[time intvl probes].each do |key|
|
454
499
|
unless options[:tcp_keepalive][key].is_a?(Integer)
|
455
500
|
raise "Expected the #{key.inspect} key in :tcp_keepalive to be an Integer"
|
456
501
|
end
|
@@ -458,13 +503,13 @@ class Redis
|
|
458
503
|
|
459
504
|
when Integer
|
460
505
|
if options[:tcp_keepalive] >= 60
|
461
|
-
options[:tcp_keepalive] = {:
|
506
|
+
options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 20, intvl: 10, probes: 2 }
|
462
507
|
|
463
508
|
elsif options[:tcp_keepalive] >= 30
|
464
|
-
options[:tcp_keepalive] = {:
|
509
|
+
options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 10, intvl: 5, probes: 2 }
|
465
510
|
|
466
511
|
elsif options[:tcp_keepalive] >= 5
|
467
|
-
options[:tcp_keepalive] = {:
|
512
|
+
options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 2, intvl: 2, probes: 1 }
|
468
513
|
end
|
469
514
|
end
|
470
515
|
|
@@ -476,13 +521,18 @@ class Redis
|
|
476
521
|
def _parse_driver(driver)
|
477
522
|
driver = driver.to_s if driver.is_a?(Symbol)
|
478
523
|
|
479
|
-
if driver.
|
524
|
+
if driver.is_a?(String)
|
480
525
|
begin
|
481
|
-
|
482
|
-
driver = Connection.const_get(driver.capitalize)
|
526
|
+
require_relative "connection/#{driver}"
|
483
527
|
rescue LoadError, NameError
|
484
|
-
|
528
|
+
begin
|
529
|
+
require "redis/connection/#{driver}"
|
530
|
+
rescue LoadError, NameError => error
|
531
|
+
raise "Cannot load driver #{driver.inspect}: #{error.message}"
|
532
|
+
end
|
485
533
|
end
|
534
|
+
|
535
|
+
driver = Connection.const_get(driver.capitalize)
|
486
536
|
end
|
487
537
|
|
488
538
|
driver
|
@@ -497,18 +547,16 @@ class Redis
|
|
497
547
|
@options
|
498
548
|
end
|
499
549
|
|
500
|
-
def check(client)
|
501
|
-
end
|
550
|
+
def check(client); end
|
502
551
|
|
503
552
|
class Sentinel < Connector
|
504
553
|
def initialize(options)
|
505
554
|
super(options)
|
506
555
|
|
507
|
-
@options[:password] = DEFAULTS.fetch(:password)
|
508
556
|
@options[:db] = DEFAULTS.fetch(:db)
|
509
557
|
|
510
558
|
@sentinels = @options.delete(:sentinels).dup
|
511
|
-
@role = @options
|
559
|
+
@role = (@options[:role] || "master").to_s
|
512
560
|
@master = @options[:host]
|
513
561
|
end
|
514
562
|
|
@@ -531,13 +579,13 @@ class Redis
|
|
531
579
|
|
532
580
|
def resolve
|
533
581
|
result = case @role
|
534
|
-
|
535
|
-
|
536
|
-
|
537
|
-
|
538
|
-
|
539
|
-
|
540
|
-
|
582
|
+
when "master"
|
583
|
+
resolve_master
|
584
|
+
when "slave"
|
585
|
+
resolve_slave
|
586
|
+
else
|
587
|
+
raise ArgumentError, "Unknown instance role #{@role}"
|
588
|
+
end
|
541
589
|
|
542
590
|
result || (raise ConnectionError, "Unable to fetch #{@role} via Sentinel.")
|
543
591
|
end
|
@@ -545,10 +593,12 @@ class Redis
|
|
545
593
|
def sentinel_detect
|
546
594
|
@sentinels.each do |sentinel|
|
547
595
|
client = Client.new(@options.merge({
|
548
|
-
|
549
|
-
|
550
|
-
|
551
|
-
|
596
|
+
host: sentinel[:host] || sentinel["host"],
|
597
|
+
port: sentinel[:port] || sentinel["port"],
|
598
|
+
username: sentinel[:username] || sentinel["username"],
|
599
|
+
password: sentinel[:password] || sentinel["password"],
|
600
|
+
reconnect_attempts: 0
|
601
|
+
}))
|
552
602
|
|
553
603
|
begin
|
554
604
|
if result = yield(client)
|
@@ -570,7 +620,7 @@ class Redis
|
|
570
620
|
def resolve_master
|
571
621
|
sentinel_detect do |client|
|
572
622
|
if reply = client.call(["sentinel", "get-master-addr-by-name", @master])
|
573
|
-
{:
|
623
|
+
{ host: reply[0], port: reply[1] }
|
574
624
|
end
|
575
625
|
end
|
576
626
|
end
|
@@ -578,9 +628,19 @@ class Redis
|
|
578
628
|
def resolve_slave
|
579
629
|
sentinel_detect do |client|
|
580
630
|
if reply = client.call(["sentinel", "slaves", @master])
|
581
|
-
|
582
|
-
|
583
|
-
{
|
631
|
+
slaves = reply.map { |s| s.each_slice(2).to_h }
|
632
|
+
slaves.each { |s| s['flags'] = s.fetch('flags').split(',') }
|
633
|
+
slaves.reject! { |s| s.fetch('flags').include?('s_down') }
|
634
|
+
|
635
|
+
if slaves.empty?
|
636
|
+
raise CannotConnectError, 'No slaves available.'
|
637
|
+
else
|
638
|
+
slave = slaves.sample
|
639
|
+
{
|
640
|
+
host: slave.fetch('ip'),
|
641
|
+
port: slave.fetch('port')
|
642
|
+
}
|
643
|
+
end
|
584
644
|
end
|
585
645
|
end
|
586
646
|
end
|