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
@@ -1,10 +1,16 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require_relative "command_helper"
|
3
4
|
require_relative "registry"
|
4
5
|
require_relative "../errors"
|
5
6
|
require "em-synchrony"
|
6
7
|
require "hiredis/reader"
|
7
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
|
+
|
8
14
|
class Redis
|
9
15
|
module Connection
|
10
16
|
class RedisClient < EventMachine::Connection
|
@@ -47,9 +53,7 @@ class Redis
|
|
47
53
|
|
48
54
|
def read
|
49
55
|
@req = EventMachine::DefaultDeferrable.new
|
50
|
-
if @timeout > 0
|
51
|
-
@req.timeout(@timeout, :timeout)
|
52
|
-
end
|
56
|
+
@req.timeout(@timeout, :timeout) if @timeout > 0
|
53
57
|
EventMachine::Synchrony.sync @req
|
54
58
|
end
|
55
59
|
|
@@ -106,7 +110,7 @@ class Redis
|
|
106
110
|
end
|
107
111
|
|
108
112
|
def connected?
|
109
|
-
@connection
|
113
|
+
@connection&.connected?
|
110
114
|
end
|
111
115
|
|
112
116
|
def timeout=(timeout)
|
data/lib/redis/distributed.rb
CHANGED
@@ -1,16 +1,17 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require_relative "hash_ring"
|
3
4
|
|
4
5
|
class Redis
|
5
6
|
class Distributed
|
6
|
-
|
7
7
|
class CannotDistribute < RuntimeError
|
8
8
|
def initialize(command)
|
9
9
|
@command = command
|
10
10
|
end
|
11
11
|
|
12
12
|
def message
|
13
|
-
"#{@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."
|
14
15
|
end
|
15
16
|
end
|
16
17
|
|
@@ -23,10 +24,14 @@ class Redis
|
|
23
24
|
@default_options = options.dup
|
24
25
|
node_configs.each { |node_config| add_node(node_config) }
|
25
26
|
@subscribed_node = nil
|
27
|
+
@watch_key = nil
|
26
28
|
end
|
27
29
|
|
28
30
|
def node_for(key)
|
29
|
-
|
31
|
+
key = key_tag(key.to_s) || key.to_s
|
32
|
+
raise CannotDistribute, :watch if @watch_key && @watch_key != key
|
33
|
+
|
34
|
+
@ring.get_node(key)
|
30
35
|
end
|
31
36
|
|
32
37
|
def nodes
|
@@ -34,9 +39,9 @@ class Redis
|
|
34
39
|
end
|
35
40
|
|
36
41
|
def add_node(options)
|
37
|
-
options = { :
|
42
|
+
options = { url: options } if options.is_a?(String)
|
38
43
|
options = @default_options.merge(options)
|
39
|
-
@ring.add_node Redis.new(
|
44
|
+
@ring.add_node Redis.new(options)
|
40
45
|
end
|
41
46
|
|
42
47
|
# Change the selected database for the current connection.
|
@@ -145,12 +150,12 @@ class Redis
|
|
145
150
|
end
|
146
151
|
|
147
152
|
# Create a key using the serialized value, previously obtained using DUMP.
|
148
|
-
def restore(key, ttl, serialized_value, options
|
149
|
-
node_for(key).restore(key, ttl, serialized_value, options)
|
153
|
+
def restore(key, ttl, serialized_value, **options)
|
154
|
+
node_for(key).restore(key, ttl, serialized_value, **options)
|
150
155
|
end
|
151
156
|
|
152
157
|
# Transfer a key from the connected instance to another instance.
|
153
|
-
def migrate(
|
158
|
+
def migrate(_key, _options)
|
154
159
|
raise CannotDistribute, :migrate
|
155
160
|
end
|
156
161
|
|
@@ -171,8 +176,33 @@ class Redis
|
|
171
176
|
end
|
172
177
|
|
173
178
|
# Determine if a key exists.
|
174
|
-
def exists(
|
175
|
-
|
179
|
+
def exists(*args)
|
180
|
+
if !Redis.exists_returns_integer && args.size == 1
|
181
|
+
message = "`Redis#exists(key)` will return an Integer in redis-rb 4.3, if you want to keep the old behavior, " \
|
182
|
+
"use `exists?` instead. To opt-in to the new behavior now you can set Redis.exists_returns_integer = true. " \
|
183
|
+
"(#{::Kernel.caller(1, 1).first})\n"
|
184
|
+
|
185
|
+
if defined?(::Warning)
|
186
|
+
::Warning.warn(message)
|
187
|
+
else
|
188
|
+
warn(message)
|
189
|
+
end
|
190
|
+
exists?(*args)
|
191
|
+
else
|
192
|
+
keys_per_node = args.group_by { |key| node_for(key) }
|
193
|
+
keys_per_node.inject(0) do |sum, (node, keys)|
|
194
|
+
sum + node._exists(*keys)
|
195
|
+
end
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
# Determine if any of the keys exists.
|
200
|
+
def exists?(*args)
|
201
|
+
keys_per_node = args.group_by { |key| node_for(key) }
|
202
|
+
keys_per_node.each do |node, keys|
|
203
|
+
return true if node.exists?(*keys)
|
204
|
+
end
|
205
|
+
false
|
176
206
|
end
|
177
207
|
|
178
208
|
# Find all keys matching the given pattern.
|
@@ -205,11 +235,11 @@ class Redis
|
|
205
235
|
end
|
206
236
|
|
207
237
|
# Sort the elements in a list, set or sorted set.
|
208
|
-
def sort(key, options
|
238
|
+
def sort(key, **options)
|
209
239
|
keys = [key, options[:by], options[:store], *Array(options[:get])].compact
|
210
240
|
|
211
241
|
ensure_same_node(:sort, keys) do |node|
|
212
|
-
node.sort(key, options)
|
242
|
+
node.sort(key, **options)
|
213
243
|
end
|
214
244
|
end
|
215
245
|
|
@@ -244,8 +274,8 @@ class Redis
|
|
244
274
|
end
|
245
275
|
|
246
276
|
# Set the string value of a key.
|
247
|
-
def set(key, value, options
|
248
|
-
node_for(key).set(key, value, options)
|
277
|
+
def set(key, value, **options)
|
278
|
+
node_for(key).set(key, value, **options)
|
249
279
|
end
|
250
280
|
|
251
281
|
# Set the time to live in seconds of a key.
|
@@ -264,20 +294,20 @@ class Redis
|
|
264
294
|
end
|
265
295
|
|
266
296
|
# Set multiple keys to multiple values.
|
267
|
-
def mset(*
|
297
|
+
def mset(*_args)
|
268
298
|
raise CannotDistribute, :mset
|
269
299
|
end
|
270
300
|
|
271
|
-
def mapped_mset(
|
301
|
+
def mapped_mset(_hash)
|
272
302
|
raise CannotDistribute, :mapped_mset
|
273
303
|
end
|
274
304
|
|
275
305
|
# Set multiple keys to multiple values, only if none of the keys exist.
|
276
|
-
def msetnx(*
|
306
|
+
def msetnx(*_args)
|
277
307
|
raise CannotDistribute, :msetnx
|
278
308
|
end
|
279
309
|
|
280
|
-
def mapped_msetnx(
|
310
|
+
def mapped_msetnx(_hash)
|
281
311
|
raise CannotDistribute, :mapped_msetnx
|
282
312
|
end
|
283
313
|
|
@@ -336,7 +366,7 @@ class Redis
|
|
336
366
|
end
|
337
367
|
|
338
368
|
# Return the position of the first bit set to 1 or 0 in a string.
|
339
|
-
def bitpos(key, bit, start=nil, stop=nil)
|
369
|
+
def bitpos(key, bit, start = nil, stop = nil)
|
340
370
|
node_for(key).bitpos(key, bit, start, stop)
|
341
371
|
end
|
342
372
|
|
@@ -354,7 +384,7 @@ class Redis
|
|
354
384
|
get(key)
|
355
385
|
end
|
356
386
|
|
357
|
-
def []=(key,value)
|
387
|
+
def []=(key, value)
|
358
388
|
set(key, value)
|
359
389
|
end
|
360
390
|
|
@@ -383,14 +413,14 @@ class Redis
|
|
383
413
|
node_for(key).rpushx(key, value)
|
384
414
|
end
|
385
415
|
|
386
|
-
# Remove and get the first
|
387
|
-
def lpop(key)
|
388
|
-
node_for(key).lpop(key)
|
416
|
+
# Remove and get the first elements in a list.
|
417
|
+
def lpop(key, count = nil)
|
418
|
+
node_for(key).lpop(key, count)
|
389
419
|
end
|
390
420
|
|
391
|
-
# Remove and get the last
|
392
|
-
def rpop(key)
|
393
|
-
node_for(key).rpop(key)
|
421
|
+
# Remove and get the last elements in a list.
|
422
|
+
def rpop(key, count = nil)
|
423
|
+
node_for(key).rpop(key, count)
|
394
424
|
end
|
395
425
|
|
396
426
|
# Remove the last element in a list, append it to another list and return
|
@@ -439,15 +469,9 @@ class Redis
|
|
439
469
|
|
440
470
|
# Pop a value from a list, push it to another list and return it; or block
|
441
471
|
# until one is available.
|
442
|
-
def brpoplpush(source, destination,
|
443
|
-
case options
|
444
|
-
when Integer
|
445
|
-
# Issue deprecation notice in obnoxious mode...
|
446
|
-
options = { :timeout => options }
|
447
|
-
end
|
448
|
-
|
472
|
+
def brpoplpush(source, destination, deprecated_timeout = 0, **options)
|
449
473
|
ensure_same_node(:brpoplpush, [source, destination]) do |node|
|
450
|
-
node.brpoplpush(source, destination, options)
|
474
|
+
node.brpoplpush(source, destination, deprecated_timeout, **options)
|
451
475
|
end
|
452
476
|
end
|
453
477
|
|
@@ -524,13 +548,13 @@ class Redis
|
|
524
548
|
end
|
525
549
|
|
526
550
|
# Scan a set
|
527
|
-
def sscan(key, cursor, options
|
528
|
-
node_for(key).sscan(key, cursor, options)
|
551
|
+
def sscan(key, cursor, **options)
|
552
|
+
node_for(key).sscan(key, cursor, **options)
|
529
553
|
end
|
530
554
|
|
531
555
|
# Scan a set and return an enumerator
|
532
|
-
def sscan_each(key, options
|
533
|
-
node_for(key).sscan_each(key, options, &block)
|
556
|
+
def sscan_each(key, **options, &block)
|
557
|
+
node_for(key).sscan_each(key, **options, &block)
|
534
558
|
end
|
535
559
|
|
536
560
|
# Subtract multiple sets.
|
@@ -585,6 +609,7 @@ class Redis
|
|
585
609
|
def zadd(key, *args)
|
586
610
|
node_for(key).zadd(key, *args)
|
587
611
|
end
|
612
|
+
ruby2_keywords(:zadd) if respond_to?(:ruby2_keywords, true)
|
588
613
|
|
589
614
|
# Increment the score of a member in a sorted set.
|
590
615
|
def zincrby(key, increment, member)
|
@@ -602,14 +627,14 @@ class Redis
|
|
602
627
|
end
|
603
628
|
|
604
629
|
# Return a range of members in a sorted set, by index.
|
605
|
-
def zrange(key, start, stop, options
|
606
|
-
node_for(key).zrange(key, start, stop, options)
|
630
|
+
def zrange(key, start, stop, **options)
|
631
|
+
node_for(key).zrange(key, start, stop, **options)
|
607
632
|
end
|
608
633
|
|
609
634
|
# Return a range of members in a sorted set, by index, with scores ordered
|
610
635
|
# from high to low.
|
611
|
-
def zrevrange(key, start, stop, options
|
612
|
-
node_for(key).zrevrange(key, start, stop, options)
|
636
|
+
def zrevrange(key, start, stop, **options)
|
637
|
+
node_for(key).zrevrange(key, start, stop, **options)
|
613
638
|
end
|
614
639
|
|
615
640
|
# Determine the index of a member in a sorted set.
|
@@ -629,14 +654,14 @@ class Redis
|
|
629
654
|
end
|
630
655
|
|
631
656
|
# Return a range of members in a sorted set, by score.
|
632
|
-
def zrangebyscore(key, min, max, options
|
633
|
-
node_for(key).zrangebyscore(key, min, max, options)
|
657
|
+
def zrangebyscore(key, min, max, **options)
|
658
|
+
node_for(key).zrangebyscore(key, min, max, **options)
|
634
659
|
end
|
635
660
|
|
636
661
|
# Return a range of members in a sorted set, by score, with scores ordered
|
637
662
|
# from high to low.
|
638
|
-
def zrevrangebyscore(key, max, min, options
|
639
|
-
node_for(key).zrevrangebyscore(key, max, min, options)
|
663
|
+
def zrevrangebyscore(key, max, min, **options)
|
664
|
+
node_for(key).zrevrangebyscore(key, max, min, **options)
|
640
665
|
end
|
641
666
|
|
642
667
|
# Remove all members in a sorted set within the given scores.
|
@@ -649,18 +674,25 @@ class Redis
|
|
649
674
|
node_for(key).zcount(key, min, max)
|
650
675
|
end
|
651
676
|
|
677
|
+
# Get the intersection of multiple sorted sets
|
678
|
+
def zinter(*keys, **options)
|
679
|
+
ensure_same_node(:zinter, keys) do |node|
|
680
|
+
node.zinter(*keys, **options)
|
681
|
+
end
|
682
|
+
end
|
683
|
+
|
652
684
|
# Intersect multiple sorted sets and store the resulting sorted set in a new
|
653
685
|
# key.
|
654
|
-
def zinterstore(destination, keys, options
|
686
|
+
def zinterstore(destination, keys, **options)
|
655
687
|
ensure_same_node(:zinterstore, [destination] + keys) do |node|
|
656
|
-
node.zinterstore(destination, keys, options)
|
688
|
+
node.zinterstore(destination, keys, **options)
|
657
689
|
end
|
658
690
|
end
|
659
691
|
|
660
692
|
# Add multiple sorted sets and store the resulting sorted set in a new key.
|
661
|
-
def zunionstore(destination, keys, options
|
693
|
+
def zunionstore(destination, keys, **options)
|
662
694
|
ensure_same_node(:zunionstore, [destination] + keys) do |node|
|
663
|
-
node.zunionstore(destination, keys, options)
|
695
|
+
node.zunionstore(destination, keys, **options)
|
664
696
|
end
|
665
697
|
end
|
666
698
|
|
@@ -669,9 +701,9 @@ class Redis
|
|
669
701
|
node_for(key).hlen(key)
|
670
702
|
end
|
671
703
|
|
672
|
-
# Set
|
673
|
-
def hset(key,
|
674
|
-
node_for(key).hset(key,
|
704
|
+
# Set multiple hash fields to multiple values.
|
705
|
+
def hset(key, *attrs)
|
706
|
+
node_for(key).hset(key, *attrs)
|
675
707
|
end
|
676
708
|
|
677
709
|
# Set the value of a hash field, only if the field does not exist.
|
@@ -743,7 +775,7 @@ class Redis
|
|
743
775
|
end
|
744
776
|
|
745
777
|
def subscribed?
|
746
|
-
|
778
|
+
!!@subscribed_node
|
747
779
|
end
|
748
780
|
|
749
781
|
# Listen for messages published to the given channels.
|
@@ -761,7 +793,8 @@ class Redis
|
|
761
793
|
|
762
794
|
# Stop listening for messages posted to the given channels.
|
763
795
|
def unsubscribe(*channels)
|
764
|
-
raise
|
796
|
+
raise "Can't unsubscribe if not subscribed." unless subscribed?
|
797
|
+
|
765
798
|
@subscribed_node.unsubscribe(*channels)
|
766
799
|
end
|
767
800
|
|
@@ -777,13 +810,26 @@ class Redis
|
|
777
810
|
end
|
778
811
|
|
779
812
|
# Watch the given keys to determine execution of the MULTI/EXEC block.
|
780
|
-
def watch(*keys)
|
781
|
-
|
813
|
+
def watch(*keys, &block)
|
814
|
+
ensure_same_node(:watch, keys) do |node|
|
815
|
+
@watch_key = key_tag(keys.first) || keys.first.to_s
|
816
|
+
|
817
|
+
begin
|
818
|
+
node.watch(*keys, &block)
|
819
|
+
rescue StandardError
|
820
|
+
@watch_key = nil
|
821
|
+
raise
|
822
|
+
end
|
823
|
+
end
|
782
824
|
end
|
783
825
|
|
784
826
|
# Forget about all watched keys.
|
785
827
|
def unwatch
|
786
|
-
raise CannotDistribute, :unwatch
|
828
|
+
raise CannotDistribute, :unwatch unless @watch_key
|
829
|
+
|
830
|
+
result = node_for(@watch_key).unwatch
|
831
|
+
@watch_key = nil
|
832
|
+
result
|
787
833
|
end
|
788
834
|
|
789
835
|
def pipelined
|
@@ -791,18 +837,30 @@ class Redis
|
|
791
837
|
end
|
792
838
|
|
793
839
|
# Mark the start of a transaction block.
|
794
|
-
def multi
|
795
|
-
raise CannotDistribute, :multi
|
840
|
+
def multi(&block)
|
841
|
+
raise CannotDistribute, :multi unless @watch_key
|
842
|
+
|
843
|
+
result = node_for(@watch_key).multi(&block)
|
844
|
+
@watch_key = nil if block_given?
|
845
|
+
result
|
796
846
|
end
|
797
847
|
|
798
848
|
# Execute all commands issued after MULTI.
|
799
849
|
def exec
|
800
|
-
raise CannotDistribute, :exec
|
850
|
+
raise CannotDistribute, :exec unless @watch_key
|
851
|
+
|
852
|
+
result = node_for(@watch_key).exec
|
853
|
+
@watch_key = nil
|
854
|
+
result
|
801
855
|
end
|
802
856
|
|
803
857
|
# Discard all commands issued after MULTI.
|
804
858
|
def discard
|
805
|
-
raise CannotDistribute, :discard
|
859
|
+
raise CannotDistribute, :discard unless @watch_key
|
860
|
+
|
861
|
+
result = node_for(@watch_key).discard
|
862
|
+
@watch_key = nil
|
863
|
+
result
|
806
864
|
end
|
807
865
|
|
808
866
|
# Control remote script registry.
|
@@ -861,7 +919,7 @@ class Redis
|
|
861
919
|
self.class.new(@node_configs, @default_options)
|
862
920
|
end
|
863
921
|
|
864
|
-
|
922
|
+
protected
|
865
923
|
|
866
924
|
def on_each_node(command, *args)
|
867
925
|
nodes.map do |node|
|
data/lib/redis/errors.rb
CHANGED
data/lib/redis/hash_ring.rb
CHANGED
@@ -1,9 +1,9 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
|
+
|
2
3
|
require 'zlib'
|
3
4
|
|
4
5
|
class Redis
|
5
6
|
class HashRing
|
6
|
-
|
7
7
|
POINTS_PER_SERVER = 160 # this is the default in libmemcached
|
8
8
|
|
9
9
|
attr_reader :ring, :sorted_keys, :replicas, :nodes
|
@@ -11,7 +11,7 @@ class Redis
|
|
11
11
|
# nodes is a list of objects that have a proper to_s representation.
|
12
12
|
# replicas indicates how many virtual points should be used pr. node,
|
13
13
|
# replicas are required to improve the distribution.
|
14
|
-
def initialize(nodes=[], replicas=POINTS_PER_SERVER)
|
14
|
+
def initialize(nodes = [], replicas = POINTS_PER_SERVER)
|
15
15
|
@replicas = replicas
|
16
16
|
@ring = {}
|
17
17
|
@nodes = []
|
@@ -33,11 +33,11 @@ class Redis
|
|
33
33
|
end
|
34
34
|
|
35
35
|
def remove_node(node)
|
36
|
-
@nodes.reject!{|n| n.id == node.id}
|
36
|
+
@nodes.reject! { |n| n.id == node.id }
|
37
37
|
@replicas.times do |i|
|
38
38
|
key = Zlib.crc32("#{node.id}:#{i}")
|
39
39
|
@ring.delete(key)
|
40
|
-
@sorted_keys.reject! {|k| k == key}
|
40
|
+
@sorted_keys.reject! { |k| k == key }
|
41
41
|
end
|
42
42
|
end
|
43
43
|
|
@@ -47,27 +47,29 @@ class Redis
|
|
47
47
|
end
|
48
48
|
|
49
49
|
def get_node_pos(key)
|
50
|
-
return [nil,nil] if @ring.
|
50
|
+
return [nil, nil] if @ring.empty?
|
51
|
+
|
51
52
|
crc = Zlib.crc32(key)
|
52
53
|
idx = HashRing.binary_search(@sorted_keys, crc)
|
53
|
-
|
54
|
+
[@ring[@sorted_keys[idx]], idx]
|
54
55
|
end
|
55
56
|
|
56
57
|
def iter_nodes(key)
|
57
|
-
return [nil,nil] if @ring.
|
58
|
+
return [nil, nil] if @ring.empty?
|
59
|
+
|
58
60
|
_, pos = get_node_pos(key)
|
59
61
|
@ring.size.times do |n|
|
60
|
-
yield @ring[@sorted_keys[(pos+n) % @ring.size]]
|
62
|
+
yield @ring[@sorted_keys[(pos + n) % @ring.size]]
|
61
63
|
end
|
62
64
|
end
|
63
65
|
|
64
66
|
# Find the closest index in HashRing with value <= the given value
|
65
|
-
def self.binary_search(ary, value
|
67
|
+
def self.binary_search(ary, value)
|
66
68
|
upper = ary.size - 1
|
67
69
|
lower = 0
|
68
70
|
idx = 0
|
69
71
|
|
70
|
-
while
|
72
|
+
while lower <= upper
|
71
73
|
idx = (lower + upper) / 2
|
72
74
|
comp = ary[idx] <=> value
|
73
75
|
|
@@ -80,10 +82,8 @@ class Redis
|
|
80
82
|
end
|
81
83
|
end
|
82
84
|
|
83
|
-
if upper < 0
|
84
|
-
|
85
|
-
end
|
86
|
-
return upper
|
85
|
+
upper = ary.size - 1 if upper < 0
|
86
|
+
upper
|
87
87
|
end
|
88
88
|
end
|
89
89
|
end
|