redis 3.2.2 → 4.4.0
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 +175 -13
- data/README.md +223 -76
- data/lib/redis.rb +1360 -445
- data/lib/redis/client.rb +183 -103
- data/lib/redis/cluster.rb +291 -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 +108 -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 +9 -6
- data/lib/redis/connection/registry.rb +2 -1
- data/lib/redis/connection/ruby.rb +168 -63
- data/lib/redis/connection/synchrony.rb +29 -7
- data/lib/redis/distributed.rb +156 -74
- 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 +20 -13
- data/lib/redis/version.rb +3 -1
- metadata +41 -170
- data/.gitignore +0 -16
- data/.travis.yml +0 -59
- 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/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 -250
- 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 -437
- 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 -125
- 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 -254
- 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/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 -119
- 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 -32
- 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,30 +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
|
-
|
27
|
-
|
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
|
28
36
|
|
29
37
|
def scheme
|
30
38
|
@options[:scheme]
|
@@ -42,8 +50,20 @@ class Redis
|
|
42
50
|
@options[:path]
|
43
51
|
end
|
44
52
|
|
53
|
+
def read_timeout
|
54
|
+
@options[:read_timeout]
|
55
|
+
end
|
56
|
+
|
57
|
+
def connect_timeout
|
58
|
+
@options[:connect_timeout]
|
59
|
+
end
|
60
|
+
|
45
61
|
def timeout
|
46
|
-
@options[:
|
62
|
+
@options[:read_timeout]
|
63
|
+
end
|
64
|
+
|
65
|
+
def username
|
66
|
+
@options[:username]
|
47
67
|
end
|
48
68
|
|
49
69
|
def password
|
@@ -79,11 +99,14 @@ class Redis
|
|
79
99
|
|
80
100
|
@pending_reads = 0
|
81
101
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
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
|
87
110
|
end
|
88
111
|
|
89
112
|
def connect
|
@@ -92,7 +115,19 @@ class Redis
|
|
92
115
|
# Don't try to reconnect when the connection is fresh
|
93
116
|
with_reconnect(false) do
|
94
117
|
establish_connection
|
95
|
-
|
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
|
129
|
+
|
130
|
+
call [:readonly] if @options[:readonly]
|
96
131
|
call [:select, db] if db != 0
|
97
132
|
call [:client, :setname, @options[:id]] if @options[:id]
|
98
133
|
@connector.check(self)
|
@@ -109,21 +144,21 @@ class Redis
|
|
109
144
|
path || "#{host}:#{port}"
|
110
145
|
end
|
111
146
|
|
112
|
-
def call(command
|
147
|
+
def call(command)
|
113
148
|
reply = process([command]) { read }
|
114
149
|
raise reply if reply.is_a?(CommandError)
|
115
150
|
|
116
|
-
if
|
117
|
-
|
151
|
+
if block_given? && reply != 'QUEUED'
|
152
|
+
yield reply
|
118
153
|
else
|
119
154
|
reply
|
120
155
|
end
|
121
156
|
end
|
122
157
|
|
123
|
-
def call_loop(command)
|
158
|
+
def call_loop(command, timeout = 0)
|
124
159
|
error = nil
|
125
160
|
|
126
|
-
result =
|
161
|
+
result = with_socket_timeout(timeout) do
|
127
162
|
process([command]) do
|
128
163
|
loop do
|
129
164
|
reply = read
|
@@ -145,13 +180,16 @@ class Redis
|
|
145
180
|
end
|
146
181
|
|
147
182
|
def call_pipeline(pipeline)
|
183
|
+
return [] if pipeline.futures.empty?
|
184
|
+
|
148
185
|
with_reconnect pipeline.with_reconnect? do
|
149
186
|
begin
|
150
|
-
pipeline.finish(call_pipelined(pipeline
|
187
|
+
pipeline.finish(call_pipelined(pipeline)).tap do
|
151
188
|
self.db = pipeline.db if pipeline.db
|
152
189
|
end
|
153
190
|
rescue ConnectionError => e
|
154
191
|
return nil if pipeline.shutdown?
|
192
|
+
|
155
193
|
# Assume the pipeline was sent in one piece, but execution of
|
156
194
|
# SHUTDOWN caused none of the replies for commands that were executed
|
157
195
|
# prior to it from coming back around.
|
@@ -160,8 +198,8 @@ class Redis
|
|
160
198
|
end
|
161
199
|
end
|
162
200
|
|
163
|
-
def call_pipelined(
|
164
|
-
return [] if
|
201
|
+
def call_pipelined(pipeline)
|
202
|
+
return [] if pipeline.futures.empty?
|
165
203
|
|
166
204
|
# The method #ensure_connected (called from #process) reconnects once on
|
167
205
|
# I/O errors. To make an effort in making sure that commands are not
|
@@ -171,19 +209,28 @@ class Redis
|
|
171
209
|
# already successfully executed commands. To circumvent this, don't retry
|
172
210
|
# after the first reply has been read successfully.
|
173
211
|
|
212
|
+
commands = pipeline.commands
|
213
|
+
|
174
214
|
result = Array.new(commands.size)
|
175
215
|
reconnect = @reconnect
|
176
216
|
|
177
217
|
begin
|
178
|
-
|
179
|
-
result[0] = read
|
180
|
-
|
181
|
-
@reconnect = false
|
218
|
+
exception = nil
|
182
219
|
|
183
|
-
|
184
|
-
|
220
|
+
process(commands) do
|
221
|
+
pipeline.timeouts.each_with_index do |timeout, i|
|
222
|
+
reply = if timeout
|
223
|
+
with_socket_timeout(timeout) { read }
|
224
|
+
else
|
225
|
+
read
|
226
|
+
end
|
227
|
+
result[i] = reply
|
228
|
+
@reconnect = false
|
229
|
+
exception = reply if exception.nil? && reply.is_a?(CommandError)
|
185
230
|
end
|
186
231
|
end
|
232
|
+
|
233
|
+
raise exception if exception
|
187
234
|
ensure
|
188
235
|
@reconnect = reconnect
|
189
236
|
end
|
@@ -221,12 +268,13 @@ class Redis
|
|
221
268
|
end
|
222
269
|
|
223
270
|
def connected?
|
224
|
-
!!
|
271
|
+
!!(connection && connection.connected?)
|
225
272
|
end
|
226
273
|
|
227
274
|
def disconnect
|
228
275
|
connection.disconnect if connected?
|
229
276
|
end
|
277
|
+
alias close disconnect
|
230
278
|
|
231
279
|
def reconnect
|
232
280
|
disconnect
|
@@ -261,12 +309,15 @@ class Redis
|
|
261
309
|
|
262
310
|
def with_socket_timeout(timeout)
|
263
311
|
connect unless connected?
|
312
|
+
original = @options[:read_timeout]
|
264
313
|
|
265
314
|
begin
|
266
315
|
connection.timeout = timeout
|
316
|
+
@options[:read_timeout] = timeout # for reconnection
|
267
317
|
yield
|
268
318
|
ensure
|
269
319
|
connection.timeout = self.timeout if connected?
|
320
|
+
@options[:read_timeout] = original
|
270
321
|
end
|
271
322
|
end
|
272
323
|
|
@@ -274,30 +325,27 @@ class Redis
|
|
274
325
|
with_socket_timeout(0, &blk)
|
275
326
|
end
|
276
327
|
|
277
|
-
def with_reconnect(val=true)
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
@reconnect = original
|
283
|
-
end
|
328
|
+
def with_reconnect(val = true)
|
329
|
+
original, @reconnect = @reconnect, val
|
330
|
+
yield
|
331
|
+
ensure
|
332
|
+
@reconnect = original
|
284
333
|
end
|
285
334
|
|
286
335
|
def without_reconnect(&blk)
|
287
336
|
with_reconnect(false, &blk)
|
288
337
|
end
|
289
338
|
|
290
|
-
|
339
|
+
protected
|
291
340
|
|
292
341
|
def logging(commands)
|
293
|
-
return yield unless @logger
|
342
|
+
return yield unless @logger&.debug?
|
294
343
|
|
295
344
|
begin
|
296
345
|
commands.each do |name, *args|
|
297
346
|
logged_args = args.map do |a|
|
298
|
-
|
299
|
-
|
300
|
-
when a.respond_to?(:to_s) then a.to_s
|
347
|
+
if a.respond_to?(:inspect) then a.inspect
|
348
|
+
elsif a.respond_to?(:to_s) then a.to_s
|
301
349
|
else
|
302
350
|
# handle poorly-behaved descendants of BasicObject
|
303
351
|
klass = a.instance_exec { (class << self; self end).superclass }
|
@@ -323,13 +371,17 @@ class Redis
|
|
323
371
|
@connection = @options[:driver].connect(@options)
|
324
372
|
@pending_reads = 0
|
325
373
|
rescue TimeoutError,
|
374
|
+
SocketError,
|
375
|
+
Errno::EADDRNOTAVAIL,
|
326
376
|
Errno::ECONNREFUSED,
|
327
377
|
Errno::EHOSTDOWN,
|
328
378
|
Errno::EHOSTUNREACH,
|
329
379
|
Errno::ENETUNREACH,
|
330
|
-
Errno::
|
380
|
+
Errno::ENOENT,
|
381
|
+
Errno::ETIMEDOUT,
|
382
|
+
Errno::EINVAL => error
|
331
383
|
|
332
|
-
raise CannotConnectError, "Error connecting to Redis on #{location} (#{
|
384
|
+
raise CannotConnectError, "Error connecting to Redis on #{location} (#{error.class})"
|
333
385
|
end
|
334
386
|
|
335
387
|
def ensure_connected
|
@@ -343,9 +395,9 @@ class Redis
|
|
343
395
|
if connected?
|
344
396
|
unless inherit_socket? || Process.pid == @pid
|
345
397
|
raise InheritedError,
|
346
|
-
|
347
|
-
|
348
|
-
|
398
|
+
"Tried to use a connection from a child process without reconnecting. " \
|
399
|
+
"You need to reconnect to Redis after forking " \
|
400
|
+
"or set :inherit_socket to true."
|
349
401
|
end
|
350
402
|
else
|
351
403
|
connect
|
@@ -356,6 +408,10 @@ class Redis
|
|
356
408
|
disconnect
|
357
409
|
|
358
410
|
if attempts <= @options[:reconnect_attempts] && @reconnect
|
411
|
+
sleep_t = [(@options[:reconnect_delay] * 2**(attempts - 1)),
|
412
|
+
@options[:reconnect_delay_max]].min
|
413
|
+
|
414
|
+
Kernel.sleep(sleep_t)
|
359
415
|
retry
|
360
416
|
else
|
361
417
|
raise
|
@@ -374,15 +430,14 @@ class Redis
|
|
374
430
|
|
375
431
|
defaults.keys.each do |key|
|
376
432
|
# Fill in defaults if needed
|
377
|
-
if defaults[key].respond_to?(:call)
|
378
|
-
defaults[key] = defaults[key].call
|
379
|
-
end
|
433
|
+
defaults[key] = defaults[key].call if defaults[key].respond_to?(:call)
|
380
434
|
|
381
435
|
# Symbolize only keys that are needed
|
382
|
-
options[key] = options[key.to_s] if options.
|
436
|
+
options[key] = options[key.to_s] if options.key?(key.to_s)
|
383
437
|
end
|
384
438
|
|
385
|
-
url = options[:url]
|
439
|
+
url = options[:url]
|
440
|
+
url = defaults[:url] if url.nil?
|
386
441
|
|
387
442
|
# Override defaults from URL if given
|
388
443
|
if url
|
@@ -391,17 +446,20 @@ class Redis
|
|
391
446
|
uri = URI(url)
|
392
447
|
|
393
448
|
if uri.scheme == "unix"
|
394
|
-
defaults[:path]
|
395
|
-
elsif uri.scheme == "redis"
|
449
|
+
defaults[:path] = uri.path
|
450
|
+
elsif uri.scheme == "redis" || uri.scheme == "rediss"
|
396
451
|
defaults[:scheme] = uri.scheme
|
397
452
|
defaults[:host] = uri.host if uri.host
|
398
453
|
defaults[:port] = uri.port if uri.port
|
399
|
-
defaults[:
|
454
|
+
defaults[:username] = CGI.unescape(uri.user) if uri.user && !uri.user.empty?
|
455
|
+
defaults[:password] = CGI.unescape(uri.password) if uri.password && !uri.password.empty?
|
400
456
|
defaults[:db] = uri.path[1..-1].to_i if uri.path
|
401
457
|
defaults[:role] = :master
|
402
458
|
else
|
403
459
|
raise ArgumentError, "invalid uri scheme '#{uri.scheme}'"
|
404
460
|
end
|
461
|
+
|
462
|
+
defaults[:ssl] = true if uri.scheme == "rediss"
|
405
463
|
end
|
406
464
|
|
407
465
|
# Use default when option is not specified or nil
|
@@ -420,33 +478,40 @@ class Redis
|
|
420
478
|
options[:port] = options[:port].to_i
|
421
479
|
end
|
422
480
|
|
423
|
-
|
424
|
-
|
425
|
-
options[:
|
426
|
-
|
427
|
-
options[:timeout]
|
481
|
+
if options.key?(:timeout)
|
482
|
+
options[:connect_timeout] ||= options[:timeout]
|
483
|
+
options[:read_timeout] ||= options[:timeout]
|
484
|
+
options[:write_timeout] ||= options[:timeout]
|
428
485
|
end
|
429
486
|
|
487
|
+
options[:connect_timeout] = Float(options[:connect_timeout])
|
488
|
+
options[:read_timeout] = Float(options[:read_timeout])
|
489
|
+
options[:write_timeout] = Float(options[:write_timeout])
|
490
|
+
|
491
|
+
options[:reconnect_attempts] = options[:reconnect_attempts].to_i
|
492
|
+
options[:reconnect_delay] = options[:reconnect_delay].to_f
|
493
|
+
options[:reconnect_delay_max] = options[:reconnect_delay_max].to_f
|
494
|
+
|
430
495
|
options[:db] = options[:db].to_i
|
431
496
|
options[:driver] = _parse_driver(options[:driver]) || Connection.drivers.last
|
432
497
|
|
433
498
|
case options[:tcp_keepalive]
|
434
499
|
when Hash
|
435
|
-
[
|
436
|
-
unless options[:tcp_keepalive][key].is_a?(
|
437
|
-
raise "Expected the #{key.inspect} key in :tcp_keepalive to be
|
500
|
+
%i[time intvl probes].each do |key|
|
501
|
+
unless options[:tcp_keepalive][key].is_a?(Integer)
|
502
|
+
raise "Expected the #{key.inspect} key in :tcp_keepalive to be an Integer"
|
438
503
|
end
|
439
504
|
end
|
440
505
|
|
441
|
-
when
|
506
|
+
when Integer
|
442
507
|
if options[:tcp_keepalive] >= 60
|
443
|
-
options[:tcp_keepalive] = {:
|
508
|
+
options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 20, intvl: 10, probes: 2 }
|
444
509
|
|
445
510
|
elsif options[:tcp_keepalive] >= 30
|
446
|
-
options[:tcp_keepalive] = {:
|
511
|
+
options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 10, intvl: 5, probes: 2 }
|
447
512
|
|
448
513
|
elsif options[:tcp_keepalive] >= 5
|
449
|
-
options[:tcp_keepalive] = {:
|
514
|
+
options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 2, intvl: 2, probes: 1 }
|
450
515
|
end
|
451
516
|
end
|
452
517
|
|
@@ -458,13 +523,18 @@ class Redis
|
|
458
523
|
def _parse_driver(driver)
|
459
524
|
driver = driver.to_s if driver.is_a?(Symbol)
|
460
525
|
|
461
|
-
if driver.
|
526
|
+
if driver.is_a?(String)
|
462
527
|
begin
|
463
|
-
|
464
|
-
driver = Connection.const_get(driver.capitalize)
|
528
|
+
require_relative "connection/#{driver}"
|
465
529
|
rescue LoadError, NameError
|
466
|
-
|
530
|
+
begin
|
531
|
+
require "redis/connection/#{driver}"
|
532
|
+
rescue LoadError, NameError => error
|
533
|
+
raise "Cannot load driver #{driver.inspect}: #{error.message}"
|
534
|
+
end
|
467
535
|
end
|
536
|
+
|
537
|
+
driver = Connection.const_get(driver.capitalize)
|
468
538
|
end
|
469
539
|
|
470
540
|
driver
|
@@ -479,18 +549,16 @@ class Redis
|
|
479
549
|
@options
|
480
550
|
end
|
481
551
|
|
482
|
-
def check(client)
|
483
|
-
end
|
552
|
+
def check(client); end
|
484
553
|
|
485
554
|
class Sentinel < Connector
|
486
555
|
def initialize(options)
|
487
556
|
super(options)
|
488
557
|
|
489
|
-
@options[:password] = DEFAULTS.fetch(:password)
|
490
558
|
@options[:db] = DEFAULTS.fetch(:db)
|
491
559
|
|
492
560
|
@sentinels = @options.delete(:sentinels).dup
|
493
|
-
@role = @options
|
561
|
+
@role = (@options[:role] || "master").to_s
|
494
562
|
@master = @options[:host]
|
495
563
|
end
|
496
564
|
|
@@ -513,13 +581,13 @@ class Redis
|
|
513
581
|
|
514
582
|
def resolve
|
515
583
|
result = case @role
|
516
|
-
|
517
|
-
|
518
|
-
|
519
|
-
|
520
|
-
|
521
|
-
|
522
|
-
|
584
|
+
when "master"
|
585
|
+
resolve_master
|
586
|
+
when "slave"
|
587
|
+
resolve_slave
|
588
|
+
else
|
589
|
+
raise ArgumentError, "Unknown instance role #{@role}"
|
590
|
+
end
|
523
591
|
|
524
592
|
result || (raise ConnectionError, "Unable to fetch #{@role} via Sentinel.")
|
525
593
|
end
|
@@ -527,10 +595,12 @@ class Redis
|
|
527
595
|
def sentinel_detect
|
528
596
|
@sentinels.each do |sentinel|
|
529
597
|
client = Client.new(@options.merge({
|
530
|
-
|
531
|
-
|
532
|
-
|
533
|
-
|
598
|
+
host: sentinel[:host] || sentinel["host"],
|
599
|
+
port: sentinel[:port] || sentinel["port"],
|
600
|
+
username: sentinel[:username] || sentinel["username"],
|
601
|
+
password: sentinel[:password] || sentinel["password"],
|
602
|
+
reconnect_attempts: 0
|
603
|
+
}))
|
534
604
|
|
535
605
|
begin
|
536
606
|
if result = yield(client)
|
@@ -552,7 +622,7 @@ class Redis
|
|
552
622
|
def resolve_master
|
553
623
|
sentinel_detect do |client|
|
554
624
|
if reply = client.call(["sentinel", "get-master-addr-by-name", @master])
|
555
|
-
{:
|
625
|
+
{ host: reply[0], port: reply[1] }
|
556
626
|
end
|
557
627
|
end
|
558
628
|
end
|
@@ -560,9 +630,19 @@ class Redis
|
|
560
630
|
def resolve_slave
|
561
631
|
sentinel_detect do |client|
|
562
632
|
if reply = client.call(["sentinel", "slaves", @master])
|
563
|
-
|
564
|
-
|
565
|
-
{
|
633
|
+
slaves = reply.map { |s| s.each_slice(2).to_h }
|
634
|
+
slaves.each { |s| s['flags'] = s.fetch('flags').split(',') }
|
635
|
+
slaves.reject! { |s| s.fetch('flags').include?('s_down') }
|
636
|
+
|
637
|
+
if slaves.empty?
|
638
|
+
raise CannotConnectError, 'No slaves available.'
|
639
|
+
else
|
640
|
+
slave = slaves.sample
|
641
|
+
{
|
642
|
+
host: slave.fetch('ip'),
|
643
|
+
port: slave.fetch('port')
|
644
|
+
}
|
645
|
+
end
|
566
646
|
end
|
567
647
|
end
|
568
648
|
end
|