redis 3.3.5 → 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 +140 -2
- data/README.md +144 -79
- data/lib/redis.rb +1260 -403
- data/lib/redis/client.rb +152 -90
- 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 +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 +154 -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,19 @@ 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
|
|
129
|
+
|
|
130
|
+
call [:readonly] if @options[:readonly]
|
|
103
131
|
call [:select, db] if db != 0
|
|
104
132
|
call [:client, :setname, @options[:id]] if @options[:id]
|
|
105
133
|
@connector.check(self)
|
|
@@ -120,7 +148,7 @@ class Redis
|
|
|
120
148
|
reply = process([command]) { read }
|
|
121
149
|
raise reply if reply.is_a?(CommandError)
|
|
122
150
|
|
|
123
|
-
if block_given?
|
|
151
|
+
if block_given? && reply != 'QUEUED'
|
|
124
152
|
yield reply
|
|
125
153
|
else
|
|
126
154
|
reply
|
|
@@ -152,13 +180,16 @@ class Redis
|
|
|
152
180
|
end
|
|
153
181
|
|
|
154
182
|
def call_pipeline(pipeline)
|
|
183
|
+
return [] if pipeline.futures.empty?
|
|
184
|
+
|
|
155
185
|
with_reconnect pipeline.with_reconnect? do
|
|
156
186
|
begin
|
|
157
|
-
pipeline.finish(call_pipelined(pipeline
|
|
187
|
+
pipeline.finish(call_pipelined(pipeline)).tap do
|
|
158
188
|
self.db = pipeline.db if pipeline.db
|
|
159
189
|
end
|
|
160
190
|
rescue ConnectionError => e
|
|
161
191
|
return nil if pipeline.shutdown?
|
|
192
|
+
|
|
162
193
|
# Assume the pipeline was sent in one piece, but execution of
|
|
163
194
|
# SHUTDOWN caused none of the replies for commands that were executed
|
|
164
195
|
# prior to it from coming back around.
|
|
@@ -167,8 +198,8 @@ class Redis
|
|
|
167
198
|
end
|
|
168
199
|
end
|
|
169
200
|
|
|
170
|
-
def call_pipelined(
|
|
171
|
-
return [] if
|
|
201
|
+
def call_pipelined(pipeline)
|
|
202
|
+
return [] if pipeline.futures.empty?
|
|
172
203
|
|
|
173
204
|
# The method #ensure_connected (called from #process) reconnects once on
|
|
174
205
|
# I/O errors. To make an effort in making sure that commands are not
|
|
@@ -178,6 +209,8 @@ class Redis
|
|
|
178
209
|
# already successfully executed commands. To circumvent this, don't retry
|
|
179
210
|
# after the first reply has been read successfully.
|
|
180
211
|
|
|
212
|
+
commands = pipeline.commands
|
|
213
|
+
|
|
181
214
|
result = Array.new(commands.size)
|
|
182
215
|
reconnect = @reconnect
|
|
183
216
|
|
|
@@ -185,13 +218,14 @@ class Redis
|
|
|
185
218
|
exception = nil
|
|
186
219
|
|
|
187
220
|
process(commands) do
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
result[i
|
|
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
|
|
195
229
|
exception = reply if exception.nil? && reply.is_a?(CommandError)
|
|
196
230
|
end
|
|
197
231
|
end
|
|
@@ -234,12 +268,13 @@ class Redis
|
|
|
234
268
|
end
|
|
235
269
|
|
|
236
270
|
def connected?
|
|
237
|
-
!!
|
|
271
|
+
!!(connection && connection.connected?)
|
|
238
272
|
end
|
|
239
273
|
|
|
240
274
|
def disconnect
|
|
241
275
|
connection.disconnect if connected?
|
|
242
276
|
end
|
|
277
|
+
alias close disconnect
|
|
243
278
|
|
|
244
279
|
def reconnect
|
|
245
280
|
disconnect
|
|
@@ -274,12 +309,15 @@ class Redis
|
|
|
274
309
|
|
|
275
310
|
def with_socket_timeout(timeout)
|
|
276
311
|
connect unless connected?
|
|
312
|
+
original = @options[:read_timeout]
|
|
277
313
|
|
|
278
314
|
begin
|
|
279
315
|
connection.timeout = timeout
|
|
316
|
+
@options[:read_timeout] = timeout # for reconnection
|
|
280
317
|
yield
|
|
281
318
|
ensure
|
|
282
319
|
connection.timeout = self.timeout if connected?
|
|
320
|
+
@options[:read_timeout] = original
|
|
283
321
|
end
|
|
284
322
|
end
|
|
285
323
|
|
|
@@ -287,30 +325,27 @@ class Redis
|
|
|
287
325
|
with_socket_timeout(0, &blk)
|
|
288
326
|
end
|
|
289
327
|
|
|
290
|
-
def with_reconnect(val=true)
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
@reconnect = original
|
|
296
|
-
end
|
|
328
|
+
def with_reconnect(val = true)
|
|
329
|
+
original, @reconnect = @reconnect, val
|
|
330
|
+
yield
|
|
331
|
+
ensure
|
|
332
|
+
@reconnect = original
|
|
297
333
|
end
|
|
298
334
|
|
|
299
335
|
def without_reconnect(&blk)
|
|
300
336
|
with_reconnect(false, &blk)
|
|
301
337
|
end
|
|
302
338
|
|
|
303
|
-
|
|
339
|
+
protected
|
|
304
340
|
|
|
305
341
|
def logging(commands)
|
|
306
|
-
return yield unless @logger
|
|
342
|
+
return yield unless @logger&.debug?
|
|
307
343
|
|
|
308
344
|
begin
|
|
309
345
|
commands.each do |name, *args|
|
|
310
346
|
logged_args = args.map do |a|
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
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
|
|
314
349
|
else
|
|
315
350
|
# handle poorly-behaved descendants of BasicObject
|
|
316
351
|
klass = a.instance_exec { (class << self; self end).superclass }
|
|
@@ -336,13 +371,17 @@ class Redis
|
|
|
336
371
|
@connection = @options[:driver].connect(@options)
|
|
337
372
|
@pending_reads = 0
|
|
338
373
|
rescue TimeoutError,
|
|
374
|
+
SocketError,
|
|
375
|
+
Errno::EADDRNOTAVAIL,
|
|
339
376
|
Errno::ECONNREFUSED,
|
|
340
377
|
Errno::EHOSTDOWN,
|
|
341
378
|
Errno::EHOSTUNREACH,
|
|
342
379
|
Errno::ENETUNREACH,
|
|
343
|
-
Errno::
|
|
380
|
+
Errno::ENOENT,
|
|
381
|
+
Errno::ETIMEDOUT,
|
|
382
|
+
Errno::EINVAL => error
|
|
344
383
|
|
|
345
|
-
raise CannotConnectError, "Error connecting to Redis on #{location} (#{
|
|
384
|
+
raise CannotConnectError, "Error connecting to Redis on #{location} (#{error.class})"
|
|
346
385
|
end
|
|
347
386
|
|
|
348
387
|
def ensure_connected
|
|
@@ -356,9 +395,9 @@ class Redis
|
|
|
356
395
|
if connected?
|
|
357
396
|
unless inherit_socket? || Process.pid == @pid
|
|
358
397
|
raise InheritedError,
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
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."
|
|
362
401
|
end
|
|
363
402
|
else
|
|
364
403
|
connect
|
|
@@ -369,6 +408,10 @@ class Redis
|
|
|
369
408
|
disconnect
|
|
370
409
|
|
|
371
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)
|
|
372
415
|
retry
|
|
373
416
|
else
|
|
374
417
|
raise
|
|
@@ -387,15 +430,14 @@ class Redis
|
|
|
387
430
|
|
|
388
431
|
defaults.keys.each do |key|
|
|
389
432
|
# Fill in defaults if needed
|
|
390
|
-
if defaults[key].respond_to?(:call)
|
|
391
|
-
defaults[key] = defaults[key].call
|
|
392
|
-
end
|
|
433
|
+
defaults[key] = defaults[key].call if defaults[key].respond_to?(:call)
|
|
393
434
|
|
|
394
435
|
# Symbolize only keys that are needed
|
|
395
|
-
options[key] = options[key.to_s] if options.
|
|
436
|
+
options[key] = options[key.to_s] if options.key?(key.to_s)
|
|
396
437
|
end
|
|
397
438
|
|
|
398
|
-
url = options[:url]
|
|
439
|
+
url = options[:url]
|
|
440
|
+
url = defaults[:url] if url.nil?
|
|
399
441
|
|
|
400
442
|
# Override defaults from URL if given
|
|
401
443
|
if url
|
|
@@ -404,12 +446,13 @@ class Redis
|
|
|
404
446
|
uri = URI(url)
|
|
405
447
|
|
|
406
448
|
if uri.scheme == "unix"
|
|
407
|
-
defaults[:path]
|
|
449
|
+
defaults[:path] = uri.path
|
|
408
450
|
elsif uri.scheme == "redis" || uri.scheme == "rediss"
|
|
409
451
|
defaults[:scheme] = uri.scheme
|
|
410
452
|
defaults[:host] = uri.host if uri.host
|
|
411
453
|
defaults[:port] = uri.port if uri.port
|
|
412
|
-
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?
|
|
413
456
|
defaults[:db] = uri.path[1..-1].to_i if uri.path
|
|
414
457
|
defaults[:role] = :master
|
|
415
458
|
else
|
|
@@ -435,7 +478,7 @@ class Redis
|
|
|
435
478
|
options[:port] = options[:port].to_i
|
|
436
479
|
end
|
|
437
480
|
|
|
438
|
-
if options.
|
|
481
|
+
if options.key?(:timeout)
|
|
439
482
|
options[:connect_timeout] ||= options[:timeout]
|
|
440
483
|
options[:read_timeout] ||= options[:timeout]
|
|
441
484
|
options[:write_timeout] ||= options[:timeout]
|
|
@@ -445,12 +488,16 @@ class Redis
|
|
|
445
488
|
options[:read_timeout] = Float(options[:read_timeout])
|
|
446
489
|
options[:write_timeout] = Float(options[:write_timeout])
|
|
447
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
|
+
|
|
448
495
|
options[:db] = options[:db].to_i
|
|
449
496
|
options[:driver] = _parse_driver(options[:driver]) || Connection.drivers.last
|
|
450
497
|
|
|
451
498
|
case options[:tcp_keepalive]
|
|
452
499
|
when Hash
|
|
453
|
-
[
|
|
500
|
+
%i[time intvl probes].each do |key|
|
|
454
501
|
unless options[:tcp_keepalive][key].is_a?(Integer)
|
|
455
502
|
raise "Expected the #{key.inspect} key in :tcp_keepalive to be an Integer"
|
|
456
503
|
end
|
|
@@ -458,13 +505,13 @@ class Redis
|
|
|
458
505
|
|
|
459
506
|
when Integer
|
|
460
507
|
if options[:tcp_keepalive] >= 60
|
|
461
|
-
options[:tcp_keepalive] = {:
|
|
508
|
+
options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 20, intvl: 10, probes: 2 }
|
|
462
509
|
|
|
463
510
|
elsif options[:tcp_keepalive] >= 30
|
|
464
|
-
options[:tcp_keepalive] = {:
|
|
511
|
+
options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 10, intvl: 5, probes: 2 }
|
|
465
512
|
|
|
466
513
|
elsif options[:tcp_keepalive] >= 5
|
|
467
|
-
options[:tcp_keepalive] = {:
|
|
514
|
+
options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 2, intvl: 2, probes: 1 }
|
|
468
515
|
end
|
|
469
516
|
end
|
|
470
517
|
|
|
@@ -476,13 +523,18 @@ class Redis
|
|
|
476
523
|
def _parse_driver(driver)
|
|
477
524
|
driver = driver.to_s if driver.is_a?(Symbol)
|
|
478
525
|
|
|
479
|
-
if driver.
|
|
526
|
+
if driver.is_a?(String)
|
|
480
527
|
begin
|
|
481
|
-
|
|
482
|
-
driver = Connection.const_get(driver.capitalize)
|
|
528
|
+
require_relative "connection/#{driver}"
|
|
483
529
|
rescue LoadError, NameError
|
|
484
|
-
|
|
530
|
+
begin
|
|
531
|
+
require "redis/connection/#{driver}"
|
|
532
|
+
rescue LoadError, NameError => error
|
|
533
|
+
raise "Cannot load driver #{driver.inspect}: #{error.message}"
|
|
534
|
+
end
|
|
485
535
|
end
|
|
536
|
+
|
|
537
|
+
driver = Connection.const_get(driver.capitalize)
|
|
486
538
|
end
|
|
487
539
|
|
|
488
540
|
driver
|
|
@@ -497,18 +549,16 @@ class Redis
|
|
|
497
549
|
@options
|
|
498
550
|
end
|
|
499
551
|
|
|
500
|
-
def check(client)
|
|
501
|
-
end
|
|
552
|
+
def check(client); end
|
|
502
553
|
|
|
503
554
|
class Sentinel < Connector
|
|
504
555
|
def initialize(options)
|
|
505
556
|
super(options)
|
|
506
557
|
|
|
507
|
-
@options[:password] = DEFAULTS.fetch(:password)
|
|
508
558
|
@options[:db] = DEFAULTS.fetch(:db)
|
|
509
559
|
|
|
510
560
|
@sentinels = @options.delete(:sentinels).dup
|
|
511
|
-
@role = @options
|
|
561
|
+
@role = (@options[:role] || "master").to_s
|
|
512
562
|
@master = @options[:host]
|
|
513
563
|
end
|
|
514
564
|
|
|
@@ -531,13 +581,13 @@ class Redis
|
|
|
531
581
|
|
|
532
582
|
def resolve
|
|
533
583
|
result = case @role
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
584
|
+
when "master"
|
|
585
|
+
resolve_master
|
|
586
|
+
when "slave"
|
|
587
|
+
resolve_slave
|
|
588
|
+
else
|
|
589
|
+
raise ArgumentError, "Unknown instance role #{@role}"
|
|
590
|
+
end
|
|
541
591
|
|
|
542
592
|
result || (raise ConnectionError, "Unable to fetch #{@role} via Sentinel.")
|
|
543
593
|
end
|
|
@@ -545,10 +595,12 @@ class Redis
|
|
|
545
595
|
def sentinel_detect
|
|
546
596
|
@sentinels.each do |sentinel|
|
|
547
597
|
client = Client.new(@options.merge({
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
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
|
+
}))
|
|
552
604
|
|
|
553
605
|
begin
|
|
554
606
|
if result = yield(client)
|
|
@@ -570,7 +622,7 @@ class Redis
|
|
|
570
622
|
def resolve_master
|
|
571
623
|
sentinel_detect do |client|
|
|
572
624
|
if reply = client.call(["sentinel", "get-master-addr-by-name", @master])
|
|
573
|
-
{:
|
|
625
|
+
{ host: reply[0], port: reply[1] }
|
|
574
626
|
end
|
|
575
627
|
end
|
|
576
628
|
end
|
|
@@ -578,9 +630,19 @@ class Redis
|
|
|
578
630
|
def resolve_slave
|
|
579
631
|
sentinel_detect do |client|
|
|
580
632
|
if reply = client.call(["sentinel", "slaves", @master])
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
{
|
|
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
|
|
584
646
|
end
|
|
585
647
|
end
|
|
586
648
|
end
|