redis 4.1.1 → 4.2.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +44 -0
- data/README.md +23 -5
- data/lib/redis/client.rb +66 -73
- data/lib/redis/cluster/node.rb +3 -0
- data/lib/redis/cluster/node_key.rb +3 -7
- data/lib/redis/cluster/option.rb +27 -14
- data/lib/redis/cluster/slot.rb +30 -13
- data/lib/redis/cluster/slot_loader.rb +4 -4
- data/lib/redis/cluster.rb +13 -4
- data/lib/redis/connection/command_helper.rb +3 -2
- data/lib/redis/connection/hiredis.rb +4 -3
- data/lib/redis/connection/registry.rb +2 -1
- data/lib/redis/connection/ruby.rb +69 -59
- data/lib/redis/connection/synchrony.rb +9 -4
- data/lib/redis/connection.rb +2 -0
- data/lib/redis/distributed.rb +81 -55
- data/lib/redis/errors.rb +2 -0
- data/lib/redis/hash_ring.rb +15 -14
- data/lib/redis/pipeline.rb +16 -3
- data/lib/redis/subscribe.rb +11 -12
- data/lib/redis/version.rb +3 -1
- data/lib/redis.rb +362 -343
- metadata +6 -21
data/lib/redis/distributed.rb
CHANGED
@@ -1,15 +1,17 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative "hash_ring"
|
2
4
|
|
3
5
|
class Redis
|
4
6
|
class Distributed
|
5
|
-
|
6
7
|
class CannotDistribute < RuntimeError
|
7
8
|
def initialize(command)
|
8
9
|
@command = command
|
9
10
|
end
|
10
11
|
|
11
12
|
def message
|
12
|
-
"#{@command.to_s.upcase} cannot be used in Redis::Distributed because the keys involved need
|
13
|
+
"#{@command.to_s.upcase} cannot be used in Redis::Distributed because the keys involved need " \
|
14
|
+
"to be on the same server or because we cannot guarantee that the operation will be atomic."
|
13
15
|
end
|
14
16
|
end
|
15
17
|
|
@@ -33,9 +35,9 @@ class Redis
|
|
33
35
|
end
|
34
36
|
|
35
37
|
def add_node(options)
|
36
|
-
options = { :
|
38
|
+
options = { url: options } if options.is_a?(String)
|
37
39
|
options = @default_options.merge(options)
|
38
|
-
@ring.add_node Redis.new(
|
40
|
+
@ring.add_node Redis.new(options)
|
39
41
|
end
|
40
42
|
|
41
43
|
# Change the selected database for the current connection.
|
@@ -144,12 +146,12 @@ class Redis
|
|
144
146
|
end
|
145
147
|
|
146
148
|
# Create a key using the serialized value, previously obtained using DUMP.
|
147
|
-
def restore(key, ttl, serialized_value, options
|
148
|
-
node_for(key).restore(key, ttl, serialized_value, options)
|
149
|
+
def restore(key, ttl, serialized_value, **options)
|
150
|
+
node_for(key).restore(key, ttl, serialized_value, **options)
|
149
151
|
end
|
150
152
|
|
151
153
|
# Transfer a key from the connected instance to another instance.
|
152
|
-
def migrate(
|
154
|
+
def migrate(_key, _options)
|
153
155
|
raise CannotDistribute, :migrate
|
154
156
|
end
|
155
157
|
|
@@ -170,8 +172,33 @@ class Redis
|
|
170
172
|
end
|
171
173
|
|
172
174
|
# Determine if a key exists.
|
173
|
-
def exists(
|
174
|
-
|
175
|
+
def exists(*args)
|
176
|
+
if !Redis.exists_returns_integer && args.size == 1
|
177
|
+
message = "`Redis#exists(key)` will return an Integer in redis-rb 4.3, if you want to keep the old behavior, " \
|
178
|
+
"use `exists?` instead. To opt-in to the new behavior now you can set Redis.exists_returns_integer = true. " \
|
179
|
+
"(#{::Kernel.caller(1, 1).first})\n"
|
180
|
+
|
181
|
+
if defined?(::Warning)
|
182
|
+
::Warning.warn(message)
|
183
|
+
else
|
184
|
+
warn(message)
|
185
|
+
end
|
186
|
+
exists?(*args)
|
187
|
+
else
|
188
|
+
keys_per_node = args.group_by { |key| node_for(key) }
|
189
|
+
keys_per_node.inject(0) do |sum, (node, keys)|
|
190
|
+
sum + node._exists(*keys)
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# Determine if any of the keys exists.
|
196
|
+
def exists?(*args)
|
197
|
+
keys_per_node = args.group_by { |key| node_for(key) }
|
198
|
+
keys_per_node.each do |node, keys|
|
199
|
+
return true if node.exists?(*keys)
|
200
|
+
end
|
201
|
+
false
|
175
202
|
end
|
176
203
|
|
177
204
|
# Find all keys matching the given pattern.
|
@@ -204,11 +231,11 @@ class Redis
|
|
204
231
|
end
|
205
232
|
|
206
233
|
# Sort the elements in a list, set or sorted set.
|
207
|
-
def sort(key, options
|
234
|
+
def sort(key, **options)
|
208
235
|
keys = [key, options[:by], options[:store], *Array(options[:get])].compact
|
209
236
|
|
210
237
|
ensure_same_node(:sort, keys) do |node|
|
211
|
-
node.sort(key, options)
|
238
|
+
node.sort(key, **options)
|
212
239
|
end
|
213
240
|
end
|
214
241
|
|
@@ -243,8 +270,8 @@ class Redis
|
|
243
270
|
end
|
244
271
|
|
245
272
|
# Set the string value of a key.
|
246
|
-
def set(key, value, options
|
247
|
-
node_for(key).set(key, value, options)
|
273
|
+
def set(key, value, **options)
|
274
|
+
node_for(key).set(key, value, **options)
|
248
275
|
end
|
249
276
|
|
250
277
|
# Set the time to live in seconds of a key.
|
@@ -263,20 +290,20 @@ class Redis
|
|
263
290
|
end
|
264
291
|
|
265
292
|
# Set multiple keys to multiple values.
|
266
|
-
def mset(*
|
293
|
+
def mset(*_args)
|
267
294
|
raise CannotDistribute, :mset
|
268
295
|
end
|
269
296
|
|
270
|
-
def mapped_mset(
|
297
|
+
def mapped_mset(_hash)
|
271
298
|
raise CannotDistribute, :mapped_mset
|
272
299
|
end
|
273
300
|
|
274
301
|
# Set multiple keys to multiple values, only if none of the keys exist.
|
275
|
-
def msetnx(*
|
302
|
+
def msetnx(*_args)
|
276
303
|
raise CannotDistribute, :msetnx
|
277
304
|
end
|
278
305
|
|
279
|
-
def mapped_msetnx(
|
306
|
+
def mapped_msetnx(_hash)
|
280
307
|
raise CannotDistribute, :mapped_msetnx
|
281
308
|
end
|
282
309
|
|
@@ -335,7 +362,7 @@ class Redis
|
|
335
362
|
end
|
336
363
|
|
337
364
|
# Return the position of the first bit set to 1 or 0 in a string.
|
338
|
-
def bitpos(key, bit, start=nil, stop=nil)
|
365
|
+
def bitpos(key, bit, start = nil, stop = nil)
|
339
366
|
node_for(key).bitpos(key, bit, start, stop)
|
340
367
|
end
|
341
368
|
|
@@ -353,7 +380,7 @@ class Redis
|
|
353
380
|
get(key)
|
354
381
|
end
|
355
382
|
|
356
|
-
def []=(key,value)
|
383
|
+
def []=(key, value)
|
357
384
|
set(key, value)
|
358
385
|
end
|
359
386
|
|
@@ -401,13 +428,12 @@ class Redis
|
|
401
428
|
end
|
402
429
|
|
403
430
|
def _bpop(cmd, args)
|
404
|
-
|
405
|
-
|
406
|
-
if args.last.is_a?(Hash)
|
431
|
+
timeout = if args.last.is_a?(Hash)
|
407
432
|
options = args.pop
|
433
|
+
options[:timeout]
|
408
434
|
elsif args.last.respond_to?(:to_int)
|
409
435
|
# Issue deprecation notice in obnoxious mode...
|
410
|
-
|
436
|
+
args.pop.to_int
|
411
437
|
end
|
412
438
|
|
413
439
|
if args.size > 1
|
@@ -417,7 +443,11 @@ class Redis
|
|
417
443
|
keys = args.flatten
|
418
444
|
|
419
445
|
ensure_same_node(cmd, keys) do |node|
|
420
|
-
|
446
|
+
if timeout
|
447
|
+
node.__send__(cmd, keys, timeout: timeout)
|
448
|
+
else
|
449
|
+
node.__send__(cmd, keys)
|
450
|
+
end
|
421
451
|
end
|
422
452
|
end
|
423
453
|
|
@@ -435,15 +465,9 @@ class Redis
|
|
435
465
|
|
436
466
|
# Pop a value from a list, push it to another list and return it; or block
|
437
467
|
# until one is available.
|
438
|
-
def brpoplpush(source, destination,
|
439
|
-
case options
|
440
|
-
when Integer
|
441
|
-
# Issue deprecation notice in obnoxious mode...
|
442
|
-
options = { :timeout => options }
|
443
|
-
end
|
444
|
-
|
468
|
+
def brpoplpush(source, destination, deprecated_timeout = 0, **options)
|
445
469
|
ensure_same_node(:brpoplpush, [source, destination]) do |node|
|
446
|
-
node.brpoplpush(source, destination, options)
|
470
|
+
node.brpoplpush(source, destination, deprecated_timeout, **options)
|
447
471
|
end
|
448
472
|
end
|
449
473
|
|
@@ -520,13 +544,13 @@ class Redis
|
|
520
544
|
end
|
521
545
|
|
522
546
|
# Scan a set
|
523
|
-
def sscan(key, cursor, options
|
524
|
-
node_for(key).sscan(key, cursor, options)
|
547
|
+
def sscan(key, cursor, **options)
|
548
|
+
node_for(key).sscan(key, cursor, **options)
|
525
549
|
end
|
526
550
|
|
527
551
|
# Scan a set and return an enumerator
|
528
|
-
def sscan_each(key, options
|
529
|
-
node_for(key).sscan_each(key, options, &block)
|
552
|
+
def sscan_each(key, **options, &block)
|
553
|
+
node_for(key).sscan_each(key, **options, &block)
|
530
554
|
end
|
531
555
|
|
532
556
|
# Subtract multiple sets.
|
@@ -581,6 +605,7 @@ class Redis
|
|
581
605
|
def zadd(key, *args)
|
582
606
|
node_for(key).zadd(key, *args)
|
583
607
|
end
|
608
|
+
ruby2_keywords(:zadd) if respond_to?(:ruby2_keywords, true)
|
584
609
|
|
585
610
|
# Increment the score of a member in a sorted set.
|
586
611
|
def zincrby(key, increment, member)
|
@@ -598,14 +623,14 @@ class Redis
|
|
598
623
|
end
|
599
624
|
|
600
625
|
# Return a range of members in a sorted set, by index.
|
601
|
-
def zrange(key, start, stop, options
|
602
|
-
node_for(key).zrange(key, start, stop, options)
|
626
|
+
def zrange(key, start, stop, **options)
|
627
|
+
node_for(key).zrange(key, start, stop, **options)
|
603
628
|
end
|
604
629
|
|
605
630
|
# Return a range of members in a sorted set, by index, with scores ordered
|
606
631
|
# from high to low.
|
607
|
-
def zrevrange(key, start, stop, options
|
608
|
-
node_for(key).zrevrange(key, start, stop, options)
|
632
|
+
def zrevrange(key, start, stop, **options)
|
633
|
+
node_for(key).zrevrange(key, start, stop, **options)
|
609
634
|
end
|
610
635
|
|
611
636
|
# Determine the index of a member in a sorted set.
|
@@ -625,14 +650,14 @@ class Redis
|
|
625
650
|
end
|
626
651
|
|
627
652
|
# Return a range of members in a sorted set, by score.
|
628
|
-
def zrangebyscore(key, min, max, options
|
629
|
-
node_for(key).zrangebyscore(key, min, max, options)
|
653
|
+
def zrangebyscore(key, min, max, **options)
|
654
|
+
node_for(key).zrangebyscore(key, min, max, **options)
|
630
655
|
end
|
631
656
|
|
632
657
|
# Return a range of members in a sorted set, by score, with scores ordered
|
633
658
|
# from high to low.
|
634
|
-
def zrevrangebyscore(key, max, min, options
|
635
|
-
node_for(key).zrevrangebyscore(key, max, min, options)
|
659
|
+
def zrevrangebyscore(key, max, min, **options)
|
660
|
+
node_for(key).zrevrangebyscore(key, max, min, **options)
|
636
661
|
end
|
637
662
|
|
638
663
|
# Remove all members in a sorted set within the given scores.
|
@@ -647,16 +672,16 @@ class Redis
|
|
647
672
|
|
648
673
|
# Intersect multiple sorted sets and store the resulting sorted set in a new
|
649
674
|
# key.
|
650
|
-
def zinterstore(destination, keys, options
|
675
|
+
def zinterstore(destination, keys, **options)
|
651
676
|
ensure_same_node(:zinterstore, [destination] + keys) do |node|
|
652
|
-
node.zinterstore(destination, keys, options)
|
677
|
+
node.zinterstore(destination, keys, **options)
|
653
678
|
end
|
654
679
|
end
|
655
680
|
|
656
681
|
# Add multiple sorted sets and store the resulting sorted set in a new key.
|
657
|
-
def zunionstore(destination, keys, options
|
682
|
+
def zunionstore(destination, keys, **options)
|
658
683
|
ensure_same_node(:zunionstore, [destination] + keys) do |node|
|
659
|
-
node.zunionstore(destination, keys, options)
|
684
|
+
node.zunionstore(destination, keys, **options)
|
660
685
|
end
|
661
686
|
end
|
662
687
|
|
@@ -665,9 +690,9 @@ class Redis
|
|
665
690
|
node_for(key).hlen(key)
|
666
691
|
end
|
667
692
|
|
668
|
-
# Set
|
669
|
-
def hset(key,
|
670
|
-
node_for(key).hset(key,
|
693
|
+
# Set multiple hash fields to multiple values.
|
694
|
+
def hset(key, *attrs)
|
695
|
+
node_for(key).hset(key, *attrs)
|
671
696
|
end
|
672
697
|
|
673
698
|
# Set the value of a hash field, only if the field does not exist.
|
@@ -739,7 +764,7 @@ class Redis
|
|
739
764
|
end
|
740
765
|
|
741
766
|
def subscribed?
|
742
|
-
|
767
|
+
!!@subscribed_node
|
743
768
|
end
|
744
769
|
|
745
770
|
# Listen for messages published to the given channels.
|
@@ -757,7 +782,8 @@ class Redis
|
|
757
782
|
|
758
783
|
# Stop listening for messages posted to the given channels.
|
759
784
|
def unsubscribe(*channels)
|
760
|
-
raise
|
785
|
+
raise "Can't unsubscribe if not subscribed." unless subscribed?
|
786
|
+
|
761
787
|
@subscribed_node.unsubscribe(*channels)
|
762
788
|
end
|
763
789
|
|
@@ -773,7 +799,7 @@ class Redis
|
|
773
799
|
end
|
774
800
|
|
775
801
|
# Watch the given keys to determine execution of the MULTI/EXEC block.
|
776
|
-
def watch(*
|
802
|
+
def watch(*_keys)
|
777
803
|
raise CannotDistribute, :watch
|
778
804
|
end
|
779
805
|
|
@@ -857,7 +883,7 @@ class Redis
|
|
857
883
|
self.class.new(@node_configs, @default_options)
|
858
884
|
end
|
859
885
|
|
860
|
-
|
886
|
+
protected
|
861
887
|
|
862
888
|
def on_each_node(command, *args)
|
863
889
|
nodes.map do |node|
|
data/lib/redis/errors.rb
CHANGED
data/lib/redis/hash_ring.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'zlib'
|
2
4
|
|
3
5
|
class Redis
|
4
6
|
class HashRing
|
5
|
-
|
6
7
|
POINTS_PER_SERVER = 160 # this is the default in libmemcached
|
7
8
|
|
8
9
|
attr_reader :ring, :sorted_keys, :replicas, :nodes
|
@@ -10,7 +11,7 @@ class Redis
|
|
10
11
|
# nodes is a list of objects that have a proper to_s representation.
|
11
12
|
# replicas indicates how many virtual points should be used pr. node,
|
12
13
|
# replicas are required to improve the distribution.
|
13
|
-
def initialize(nodes=[], replicas=POINTS_PER_SERVER)
|
14
|
+
def initialize(nodes = [], replicas = POINTS_PER_SERVER)
|
14
15
|
@replicas = replicas
|
15
16
|
@ring = {}
|
16
17
|
@nodes = []
|
@@ -32,11 +33,11 @@ class Redis
|
|
32
33
|
end
|
33
34
|
|
34
35
|
def remove_node(node)
|
35
|
-
@nodes.reject!{|n| n.id == node.id}
|
36
|
+
@nodes.reject! { |n| n.id == node.id }
|
36
37
|
@replicas.times do |i|
|
37
38
|
key = Zlib.crc32("#{node.id}:#{i}")
|
38
39
|
@ring.delete(key)
|
39
|
-
@sorted_keys.reject! {|k| k == key}
|
40
|
+
@sorted_keys.reject! { |k| k == key }
|
40
41
|
end
|
41
42
|
end
|
42
43
|
|
@@ -46,27 +47,29 @@ class Redis
|
|
46
47
|
end
|
47
48
|
|
48
49
|
def get_node_pos(key)
|
49
|
-
return [nil,nil] if @ring.
|
50
|
+
return [nil, nil] if @ring.empty?
|
51
|
+
|
50
52
|
crc = Zlib.crc32(key)
|
51
53
|
idx = HashRing.binary_search(@sorted_keys, crc)
|
52
|
-
|
54
|
+
[@ring[@sorted_keys[idx]], idx]
|
53
55
|
end
|
54
56
|
|
55
57
|
def iter_nodes(key)
|
56
|
-
return [nil,nil] if @ring.
|
58
|
+
return [nil, nil] if @ring.empty?
|
59
|
+
|
57
60
|
_, pos = get_node_pos(key)
|
58
61
|
@ring.size.times do |n|
|
59
|
-
yield @ring[@sorted_keys[(pos+n) % @ring.size]]
|
62
|
+
yield @ring[@sorted_keys[(pos + n) % @ring.size]]
|
60
63
|
end
|
61
64
|
end
|
62
65
|
|
63
66
|
# Find the closest index in HashRing with value <= the given value
|
64
|
-
def self.binary_search(ary, value
|
67
|
+
def self.binary_search(ary, value)
|
65
68
|
upper = ary.size - 1
|
66
69
|
lower = 0
|
67
70
|
idx = 0
|
68
71
|
|
69
|
-
while
|
72
|
+
while lower <= upper
|
70
73
|
idx = (lower + upper) / 2
|
71
74
|
comp = ary[idx] <=> value
|
72
75
|
|
@@ -79,10 +82,8 @@ class Redis
|
|
79
82
|
end
|
80
83
|
end
|
81
84
|
|
82
|
-
if upper < 0
|
83
|
-
|
84
|
-
end
|
85
|
-
return upper
|
85
|
+
upper = ary.size - 1 if upper < 0
|
86
|
+
upper
|
86
87
|
end
|
87
88
|
end
|
88
89
|
end
|
data/lib/redis/pipeline.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Redis
|
2
4
|
class Pipeline
|
3
5
|
attr_accessor :db
|
@@ -60,7 +62,7 @@ class Redis
|
|
60
62
|
@futures.map(&:timeout)
|
61
63
|
end
|
62
64
|
|
63
|
-
def with_reconnect(val=true)
|
65
|
+
def with_reconnect(val = true)
|
64
66
|
@with_reconnect = false unless val
|
65
67
|
yield
|
66
68
|
end
|
@@ -92,7 +94,8 @@ class Redis
|
|
92
94
|
|
93
95
|
if exec.size < futures.size
|
94
96
|
# Some command wasn't recognized by Redis.
|
95
|
-
|
97
|
+
command_error = replies.detect { |r| r.is_a?(CommandError) }
|
98
|
+
raise command_error
|
96
99
|
end
|
97
100
|
|
98
101
|
super(exec) do |reply|
|
@@ -139,6 +142,16 @@ class Redis
|
|
139
142
|
@object = FutureNotReady
|
140
143
|
end
|
141
144
|
|
145
|
+
def ==(_other)
|
146
|
+
message = +"The methods == and != are deprecated for Redis::Future and will be removed in 4.2.0"
|
147
|
+
message << " - You probably meant to call .value == or .value !="
|
148
|
+
message << " (#{::Kernel.caller(1, 1).first})\n"
|
149
|
+
|
150
|
+
::Kernel.warn(message)
|
151
|
+
|
152
|
+
super
|
153
|
+
end
|
154
|
+
|
142
155
|
def inspect
|
143
156
|
"<Redis::Future #{@command.inspect}>"
|
144
157
|
end
|
@@ -153,7 +166,7 @@ class Redis
|
|
153
166
|
end
|
154
167
|
|
155
168
|
def value
|
156
|
-
::Kernel.raise(@object) if @object.
|
169
|
+
::Kernel.raise(@object) if @object.is_a?(::RuntimeError)
|
157
170
|
@object
|
158
171
|
end
|
159
172
|
|
data/lib/redis/subscribe.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Redis
|
2
4
|
class SubscribedClient
|
3
5
|
def initialize(client)
|
@@ -32,24 +34,21 @@ class Redis
|
|
32
34
|
call([:punsubscribe, *channels])
|
33
35
|
end
|
34
36
|
|
35
|
-
|
37
|
+
protected
|
36
38
|
|
37
39
|
def subscription(start, stop, channels, block, timeout = 0)
|
38
40
|
sub = Subscription.new(&block)
|
39
41
|
|
40
42
|
unsubscribed = false
|
41
43
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
break if unsubscribed
|
48
|
-
end
|
49
|
-
ensure
|
50
|
-
# No need to unsubscribe here. The real client closes the connection
|
51
|
-
# whenever an exception is raised (see #ensure_connected).
|
44
|
+
@client.call_loop([start, *channels], timeout) do |line|
|
45
|
+
type, *rest = line
|
46
|
+
sub.callbacks[type].call(*rest)
|
47
|
+
unsubscribed = type == stop && rest.last == 0
|
48
|
+
break if unsubscribed
|
52
49
|
end
|
50
|
+
# No need to unsubscribe here. The real client closes the connection
|
51
|
+
# whenever an exception is raised (see #ensure_connected).
|
53
52
|
end
|
54
53
|
end
|
55
54
|
|
@@ -58,7 +57,7 @@ class Redis
|
|
58
57
|
|
59
58
|
def initialize
|
60
59
|
@callbacks = Hash.new do |hash, key|
|
61
|
-
hash[key] =
|
60
|
+
hash[key] = ->(*_) {}
|
62
61
|
end
|
63
62
|
|
64
63
|
yield(self)
|
data/lib/redis/version.rb
CHANGED