redis 4.1.4 → 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 +4 -4
- data/CHANGELOG.md +56 -0
- data/README.md +27 -17
- data/lib/redis.rb +405 -260
- data/lib/redis/client.rb +94 -74
- data/lib/redis/cluster.rb +13 -13
- data/lib/redis/cluster/node.rb +5 -1
- data/lib/redis/cluster/option.rb +9 -3
- data/lib/redis/cluster/slot.rb +28 -14
- data/lib/redis/cluster/slot_loader.rb +2 -3
- data/lib/redis/connection.rb +1 -0
- data/lib/redis/connection/command_helper.rb +2 -2
- data/lib/redis/connection/hiredis.rb +3 -3
- data/lib/redis/connection/registry.rb +1 -1
- data/lib/redis/connection/ruby.rb +89 -107
- data/lib/redis/connection/synchrony.rb +8 -4
- data/lib/redis/distributed.rb +121 -63
- data/lib/redis/errors.rb +1 -0
- data/lib/redis/hash_ring.rb +14 -14
- data/lib/redis/pipeline.rb +6 -8
- data/lib/redis/subscribe.rb +10 -12
- data/lib/redis/version.rb +2 -1
- metadata +14 -9
data/lib/redis/client.rb
CHANGED
@@ -6,24 +6,31 @@ require "cgi"
|
|
6
6
|
|
7
7
|
class Redis
|
8
8
|
class Client
|
9
|
-
|
9
|
+
# Defaults are also used for converting string keys to symbols.
|
10
10
|
DEFAULTS = {
|
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
|
27
34
|
|
28
35
|
attr_reader :options
|
29
36
|
|
@@ -55,6 +62,10 @@ class Redis
|
|
55
62
|
@options[:read_timeout]
|
56
63
|
end
|
57
64
|
|
65
|
+
def username
|
66
|
+
@options[:username]
|
67
|
+
end
|
68
|
+
|
58
69
|
def password
|
59
70
|
@options[:password]
|
60
71
|
end
|
@@ -89,7 +100,7 @@ class Redis
|
|
89
100
|
@pending_reads = 0
|
90
101
|
|
91
102
|
@connector =
|
92
|
-
if options.
|
103
|
+
if !@options[:sentinels].nil?
|
93
104
|
Connector::Sentinel.new(@options)
|
94
105
|
elsif options.include?(:connector) && options[:connector].respond_to?(:new)
|
95
106
|
options.delete(:connector).new(@options)
|
@@ -104,7 +115,19 @@ class Redis
|
|
104
115
|
# Don't try to reconnect when the connection is fresh
|
105
116
|
with_reconnect(false) do
|
106
117
|
establish_connection
|
107
|
-
|
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]
|
108
131
|
call [:select, db] if db != 0
|
109
132
|
call [:client, :setname, @options[:id]] if @options[:id]
|
110
133
|
@connector.check(self)
|
@@ -125,7 +148,7 @@ class Redis
|
|
125
148
|
reply = process([command]) { read }
|
126
149
|
raise reply if reply.is_a?(CommandError)
|
127
150
|
|
128
|
-
if block_given?
|
151
|
+
if block_given? && reply != 'QUEUED'
|
129
152
|
yield reply
|
130
153
|
else
|
131
154
|
reply
|
@@ -166,6 +189,7 @@ class Redis
|
|
166
189
|
end
|
167
190
|
rescue ConnectionError => e
|
168
191
|
return nil if pipeline.shutdown?
|
192
|
+
|
169
193
|
# Assume the pipeline was sent in one piece, but execution of
|
170
194
|
# SHUTDOWN caused none of the replies for commands that were executed
|
171
195
|
# prior to it from coming back around.
|
@@ -244,13 +268,13 @@ class Redis
|
|
244
268
|
end
|
245
269
|
|
246
270
|
def connected?
|
247
|
-
!!
|
271
|
+
!!(connection && connection.connected?)
|
248
272
|
end
|
249
273
|
|
250
274
|
def disconnect
|
251
275
|
connection.disconnect if connected?
|
252
276
|
end
|
253
|
-
|
277
|
+
alias close disconnect
|
254
278
|
|
255
279
|
def reconnect
|
256
280
|
disconnect
|
@@ -301,30 +325,27 @@ class Redis
|
|
301
325
|
with_socket_timeout(0, &blk)
|
302
326
|
end
|
303
327
|
|
304
|
-
def with_reconnect(val=true)
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
@reconnect = original
|
310
|
-
end
|
328
|
+
def with_reconnect(val = true)
|
329
|
+
original, @reconnect = @reconnect, val
|
330
|
+
yield
|
331
|
+
ensure
|
332
|
+
@reconnect = original
|
311
333
|
end
|
312
334
|
|
313
335
|
def without_reconnect(&blk)
|
314
336
|
with_reconnect(false, &blk)
|
315
337
|
end
|
316
338
|
|
317
|
-
|
339
|
+
protected
|
318
340
|
|
319
341
|
def logging(commands)
|
320
|
-
return yield unless @logger
|
342
|
+
return yield unless @logger&.debug?
|
321
343
|
|
322
344
|
begin
|
323
345
|
commands.each do |name, *args|
|
324
346
|
logged_args = args.map do |a|
|
325
|
-
|
326
|
-
|
327
|
-
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
|
328
349
|
else
|
329
350
|
# handle poorly-behaved descendants of BasicObject
|
330
351
|
klass = a.instance_exec { (class << self; self end).superclass }
|
@@ -358,9 +379,9 @@ class Redis
|
|
358
379
|
Errno::ENETUNREACH,
|
359
380
|
Errno::ENOENT,
|
360
381
|
Errno::ETIMEDOUT,
|
361
|
-
Errno::EINVAL
|
382
|
+
Errno::EINVAL => error
|
362
383
|
|
363
|
-
raise CannotConnectError, "Error connecting to Redis on #{location} (#{
|
384
|
+
raise CannotConnectError, "Error connecting to Redis on #{location} (#{error.class})"
|
364
385
|
end
|
365
386
|
|
366
387
|
def ensure_connected
|
@@ -374,9 +395,9 @@ class Redis
|
|
374
395
|
if connected?
|
375
396
|
unless inherit_socket? || Process.pid == @pid
|
376
397
|
raise InheritedError,
|
377
|
-
|
378
|
-
|
379
|
-
|
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."
|
380
401
|
end
|
381
402
|
else
|
382
403
|
connect
|
@@ -387,7 +408,7 @@ class Redis
|
|
387
408
|
disconnect
|
388
409
|
|
389
410
|
if attempts <= @options[:reconnect_attempts] && @reconnect
|
390
|
-
sleep_t = [(@options[:reconnect_delay] * 2**(attempts-1)),
|
411
|
+
sleep_t = [(@options[:reconnect_delay] * 2**(attempts - 1)),
|
391
412
|
@options[:reconnect_delay_max]].min
|
392
413
|
|
393
414
|
Kernel.sleep(sleep_t)
|
@@ -409,16 +430,14 @@ class Redis
|
|
409
430
|
|
410
431
|
defaults.keys.each do |key|
|
411
432
|
# Fill in defaults if needed
|
412
|
-
if defaults[key].respond_to?(:call)
|
413
|
-
defaults[key] = defaults[key].call
|
414
|
-
end
|
433
|
+
defaults[key] = defaults[key].call if defaults[key].respond_to?(:call)
|
415
434
|
|
416
435
|
# Symbolize only keys that are needed
|
417
|
-
options[key] = options[key.to_s] if options.
|
436
|
+
options[key] = options[key.to_s] if options.key?(key.to_s)
|
418
437
|
end
|
419
438
|
|
420
439
|
url = options[:url]
|
421
|
-
url = defaults[:url] if url
|
440
|
+
url = defaults[:url] if url.nil?
|
422
441
|
|
423
442
|
# Override defaults from URL if given
|
424
443
|
if url
|
@@ -427,12 +446,13 @@ class Redis
|
|
427
446
|
uri = URI(url)
|
428
447
|
|
429
448
|
if uri.scheme == "unix"
|
430
|
-
defaults[:path]
|
449
|
+
defaults[:path] = uri.path
|
431
450
|
elsif uri.scheme == "redis" || uri.scheme == "rediss"
|
432
451
|
defaults[:scheme] = uri.scheme
|
433
452
|
defaults[:host] = uri.host if uri.host
|
434
453
|
defaults[:port] = uri.port if uri.port
|
435
|
-
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?
|
436
456
|
defaults[:db] = uri.path[1..-1].to_i if uri.path
|
437
457
|
defaults[:role] = :master
|
438
458
|
else
|
@@ -458,7 +478,7 @@ class Redis
|
|
458
478
|
options[:port] = options[:port].to_i
|
459
479
|
end
|
460
480
|
|
461
|
-
if options.
|
481
|
+
if options.key?(:timeout)
|
462
482
|
options[:connect_timeout] ||= options[:timeout]
|
463
483
|
options[:read_timeout] ||= options[:timeout]
|
464
484
|
options[:write_timeout] ||= options[:timeout]
|
@@ -477,7 +497,7 @@ class Redis
|
|
477
497
|
|
478
498
|
case options[:tcp_keepalive]
|
479
499
|
when Hash
|
480
|
-
[
|
500
|
+
%i[time intvl probes].each do |key|
|
481
501
|
unless options[:tcp_keepalive][key].is_a?(Integer)
|
482
502
|
raise "Expected the #{key.inspect} key in :tcp_keepalive to be an Integer"
|
483
503
|
end
|
@@ -485,13 +505,13 @@ class Redis
|
|
485
505
|
|
486
506
|
when Integer
|
487
507
|
if options[:tcp_keepalive] >= 60
|
488
|
-
options[:tcp_keepalive] = {:
|
508
|
+
options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 20, intvl: 10, probes: 2 }
|
489
509
|
|
490
510
|
elsif options[:tcp_keepalive] >= 30
|
491
|
-
options[:tcp_keepalive] = {:
|
511
|
+
options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 10, intvl: 5, probes: 2 }
|
492
512
|
|
493
513
|
elsif options[:tcp_keepalive] >= 5
|
494
|
-
options[:tcp_keepalive] = {:
|
514
|
+
options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 2, intvl: 2, probes: 1 }
|
495
515
|
end
|
496
516
|
end
|
497
517
|
|
@@ -503,14 +523,14 @@ class Redis
|
|
503
523
|
def _parse_driver(driver)
|
504
524
|
driver = driver.to_s if driver.is_a?(Symbol)
|
505
525
|
|
506
|
-
if driver.
|
526
|
+
if driver.is_a?(String)
|
507
527
|
begin
|
508
528
|
require_relative "connection/#{driver}"
|
509
|
-
rescue LoadError, NameError
|
529
|
+
rescue LoadError, NameError
|
510
530
|
begin
|
511
|
-
require "connection/#{driver}"
|
512
|
-
rescue LoadError, NameError =>
|
513
|
-
raise
|
531
|
+
require "redis/connection/#{driver}"
|
532
|
+
rescue LoadError, NameError => error
|
533
|
+
raise "Cannot load driver #{driver.inspect}: #{error.message}"
|
514
534
|
end
|
515
535
|
end
|
516
536
|
|
@@ -529,8 +549,7 @@ class Redis
|
|
529
549
|
@options
|
530
550
|
end
|
531
551
|
|
532
|
-
def check(client)
|
533
|
-
end
|
552
|
+
def check(client); end
|
534
553
|
|
535
554
|
class Sentinel < Connector
|
536
555
|
def initialize(options)
|
@@ -539,7 +558,7 @@ class Redis
|
|
539
558
|
@options[:db] = DEFAULTS.fetch(:db)
|
540
559
|
|
541
560
|
@sentinels = @options.delete(:sentinels).dup
|
542
|
-
@role = @options
|
561
|
+
@role = (@options[:role] || "master").to_s
|
543
562
|
@master = @options[:host]
|
544
563
|
end
|
545
564
|
|
@@ -562,13 +581,13 @@ class Redis
|
|
562
581
|
|
563
582
|
def resolve
|
564
583
|
result = case @role
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
584
|
+
when "master"
|
585
|
+
resolve_master
|
586
|
+
when "slave"
|
587
|
+
resolve_slave
|
588
|
+
else
|
589
|
+
raise ArgumentError, "Unknown instance role #{@role}"
|
590
|
+
end
|
572
591
|
|
573
592
|
result || (raise ConnectionError, "Unable to fetch #{@role} via Sentinel.")
|
574
593
|
end
|
@@ -576,11 +595,12 @@ class Redis
|
|
576
595
|
def sentinel_detect
|
577
596
|
@sentinels.each do |sentinel|
|
578
597
|
client = Client.new(@options.merge({
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
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
|
+
}))
|
584
604
|
|
585
605
|
begin
|
586
606
|
if result = yield(client)
|
@@ -602,7 +622,7 @@ class Redis
|
|
602
622
|
def resolve_master
|
603
623
|
sentinel_detect do |client|
|
604
624
|
if reply = client.call(["sentinel", "get-master-addr-by-name", @master])
|
605
|
-
{:
|
625
|
+
{ host: reply[0], port: reply[1] }
|
606
626
|
end
|
607
627
|
end
|
608
628
|
end
|
@@ -620,7 +640,7 @@ class Redis
|
|
620
640
|
slave = slaves.sample
|
621
641
|
{
|
622
642
|
host: slave.fetch('ip'),
|
623
|
-
port: slave.fetch('port')
|
643
|
+
port: slave.fetch('port')
|
624
644
|
}
|
625
645
|
end
|
626
646
|
end
|
data/lib/redis/cluster.rb
CHANGED
@@ -78,10 +78,13 @@ class Redis
|
|
78
78
|
end
|
79
79
|
|
80
80
|
def call_pipeline(pipeline)
|
81
|
-
node_keys
|
82
|
-
|
83
|
-
|
84
|
-
|
81
|
+
node_keys = pipeline.commands.map { |cmd| find_node_key(cmd, primary_only: true) }.compact.uniq
|
82
|
+
if node_keys.size > 1
|
83
|
+
raise(CrossSlotPipeliningError,
|
84
|
+
pipeline.commands.map { |cmd| @command.extract_first_key(cmd) }.reject(&:empty?).uniq)
|
85
|
+
end
|
86
|
+
|
87
|
+
try_send(find_node(node_keys.first), :call_pipeline, pipeline)
|
85
88
|
end
|
86
89
|
|
87
90
|
def call_with_timeout(command, timeout, &block)
|
@@ -127,7 +130,7 @@ class Redis
|
|
127
130
|
def send_command(command, &block)
|
128
131
|
cmd = command.first.to_s.downcase
|
129
132
|
case cmd
|
130
|
-
when 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save'
|
133
|
+
when 'acl', 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save'
|
131
134
|
@node.call_all(command, &block).first
|
132
135
|
when 'flushall', 'flushdb'
|
133
136
|
@node.call_master(command, &block).first
|
@@ -216,11 +219,13 @@ class Redis
|
|
216
219
|
rescue CommandError => err
|
217
220
|
if err.message.start_with?('MOVED')
|
218
221
|
raise if retry_count <= 0
|
222
|
+
|
219
223
|
node = assign_redirection_node(err.message)
|
220
224
|
retry_count -= 1
|
221
225
|
retry
|
222
226
|
elsif err.message.start_with?('ASK')
|
223
227
|
raise if retry_count <= 0
|
228
|
+
|
224
229
|
node = assign_asking_node(err.message)
|
225
230
|
node.call(%i[asking])
|
226
231
|
retry_count -= 1
|
@@ -250,14 +255,14 @@ class Redis
|
|
250
255
|
find_node(node_key)
|
251
256
|
end
|
252
257
|
|
253
|
-
def find_node_key(command)
|
258
|
+
def find_node_key(command, primary_only: false)
|
254
259
|
key = @command.extract_first_key(command)
|
255
260
|
return if key.empty?
|
256
261
|
|
257
262
|
slot = KeySlotConverter.convert(key)
|
258
263
|
return unless @slot.exists?(slot)
|
259
264
|
|
260
|
-
if @command.should_send_to_master?(command)
|
265
|
+
if @command.should_send_to_master?(command) || primary_only
|
261
266
|
@slot.find_node_key_of_master(slot)
|
262
267
|
else
|
263
268
|
@slot.find_node_key_of_slave(slot)
|
@@ -266,6 +271,7 @@ class Redis
|
|
266
271
|
|
267
272
|
def find_node(node_key)
|
268
273
|
return @node.sample if node_key.nil?
|
274
|
+
|
269
275
|
@node.find_by(node_key)
|
270
276
|
rescue Node::ReloadNeeded
|
271
277
|
update_cluster_info!(node_key)
|
@@ -281,11 +287,5 @@ class Redis
|
|
281
287
|
@node.map(&:disconnect)
|
282
288
|
@node, @slot = fetch_cluster_info!(@option)
|
283
289
|
end
|
284
|
-
|
285
|
-
def extract_keys_in_pipeline(pipeline)
|
286
|
-
node_keys = pipeline.commands.map { |cmd| find_node_key(cmd) }.compact.uniq
|
287
|
-
command_keys = pipeline.commands.map { |cmd| @command.extract_first_key(cmd) }.reject(&:empty?)
|
288
|
-
[node_keys, command_keys]
|
289
|
-
end
|
290
290
|
end
|
291
291
|
end
|
data/lib/redis/cluster/node.rb
CHANGED
@@ -39,6 +39,7 @@ class Redis
|
|
39
39
|
def call_master(command, &block)
|
40
40
|
try_map do |node_key, client|
|
41
41
|
next if slave?(node_key)
|
42
|
+
|
42
43
|
client.call(command, &block)
|
43
44
|
end.values
|
44
45
|
end
|
@@ -48,6 +49,7 @@ class Redis
|
|
48
49
|
|
49
50
|
try_map do |node_key, client|
|
50
51
|
next if master?(node_key)
|
52
|
+
|
51
53
|
client.call(command, &block)
|
52
54
|
end.values
|
53
55
|
end
|
@@ -74,8 +76,9 @@ class Redis
|
|
74
76
|
clients = options.map do |node_key, option|
|
75
77
|
next if replica_disabled? && slave?(node_key)
|
76
78
|
|
79
|
+
option = option.merge(readonly: true) if slave?(node_key)
|
80
|
+
|
77
81
|
client = Client.new(option)
|
78
|
-
client.call(%i[readonly]) if slave?(node_key)
|
79
82
|
[node_key, client]
|
80
83
|
end
|
81
84
|
|
@@ -97,6 +100,7 @@ class Redis
|
|
97
100
|
end
|
98
101
|
|
99
102
|
return results if errors.empty?
|
103
|
+
|
100
104
|
raise CommandErrorCollection, errors
|
101
105
|
end
|
102
106
|
end
|