redis 4.1.1 → 4.2.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 +49 -0
- data/README.md +23 -5
- data/lib/redis.rb +382 -343
- data/lib/redis/client.rb +66 -73
- data/lib/redis/cluster.rb +13 -4
- 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/connection.rb +2 -0
- 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/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
- metadata +12 -22
@@ -1,9 +1,16 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative "command_helper"
|
2
4
|
require_relative "registry"
|
3
5
|
require_relative "../errors"
|
4
6
|
require "em-synchrony"
|
5
7
|
require "hiredis/reader"
|
6
8
|
|
9
|
+
Kernel.warn(
|
10
|
+
"The redis synchrony driver is deprecated and will be removed in redis-rb 5.0. " \
|
11
|
+
"We're looking for people to maintain it as a separate gem, see https://github.com/redis/redis-rb/issues/915"
|
12
|
+
)
|
13
|
+
|
7
14
|
class Redis
|
8
15
|
module Connection
|
9
16
|
class RedisClient < EventMachine::Connection
|
@@ -46,9 +53,7 @@ class Redis
|
|
46
53
|
|
47
54
|
def read
|
48
55
|
@req = EventMachine::DefaultDeferrable.new
|
49
|
-
if @timeout > 0
|
50
|
-
@req.timeout(@timeout, :timeout)
|
51
|
-
end
|
56
|
+
@req.timeout(@timeout, :timeout) if @timeout > 0
|
52
57
|
EventMachine::Synchrony.sync @req
|
53
58
|
end
|
54
59
|
|
@@ -105,7 +110,7 @@ class Redis
|
|
105
110
|
end
|
106
111
|
|
107
112
|
def connected?
|
108
|
-
@connection
|
113
|
+
@connection&.connected?
|
109
114
|
end
|
110
115
|
|
111
116
|
def timeout=(timeout)
|
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
|
|