redis 4.7.1 → 5.0.0.beta3
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 +21 -0
- data/README.md +77 -161
- data/lib/redis/client.rb +78 -615
- data/lib/redis/commands/bitmaps.rb +4 -1
- data/lib/redis/commands/cluster.rb +1 -18
- data/lib/redis/commands/connection.rb +5 -10
- data/lib/redis/commands/hashes.rb +6 -3
- data/lib/redis/commands/hyper_log_log.rb +1 -1
- data/lib/redis/commands/keys.rb +52 -26
- data/lib/redis/commands/lists.rb +10 -14
- data/lib/redis/commands/pubsub.rb +7 -9
- data/lib/redis/commands/server.rb +14 -14
- data/lib/redis/commands/sets.rb +42 -35
- data/lib/redis/commands/sorted_sets.rb +4 -2
- data/lib/redis/commands/streams.rb +12 -10
- data/lib/redis/commands/strings.rb +1 -0
- data/lib/redis/commands/transactions.rb +7 -31
- data/lib/redis/commands.rb +1 -8
- data/lib/redis/distributed.rb +99 -66
- data/lib/redis/errors.rb +10 -52
- data/lib/redis/hash_ring.rb +26 -26
- data/lib/redis/pipeline.rb +43 -222
- data/lib/redis/subscribe.rb +15 -9
- data/lib/redis/version.rb +1 -1
- data/lib/redis.rb +67 -179
- metadata +13 -57
- data/lib/redis/cluster/command.rb +0 -79
- data/lib/redis/cluster/command_loader.rb +0 -33
- data/lib/redis/cluster/key_slot_converter.rb +0 -72
- data/lib/redis/cluster/node.rb +0 -120
- data/lib/redis/cluster/node_key.rb +0 -31
- data/lib/redis/cluster/node_loader.rb +0 -34
- data/lib/redis/cluster/option.rb +0 -100
- data/lib/redis/cluster/slot.rb +0 -86
- data/lib/redis/cluster/slot_loader.rb +0 -46
- data/lib/redis/cluster.rb +0 -315
- data/lib/redis/connection/command_helper.rb +0 -41
- data/lib/redis/connection/hiredis.rb +0 -66
- data/lib/redis/connection/registry.rb +0 -13
- data/lib/redis/connection/ruby.rb +0 -437
- data/lib/redis/connection/synchrony.rb +0 -148
- data/lib/redis/connection.rb +0 -11
@@ -5,48 +5,26 @@ class Redis
|
|
5
5
|
module Transactions
|
6
6
|
# Mark the start of a transaction block.
|
7
7
|
#
|
8
|
-
# Passing a block is optional.
|
9
|
-
#
|
10
8
|
# @example With a block
|
11
9
|
# redis.multi do |multi|
|
12
10
|
# multi.set("key", "value")
|
13
11
|
# multi.incr("counter")
|
14
12
|
# end # => ["OK", 6]
|
15
13
|
#
|
16
|
-
# @example Without a block
|
17
|
-
# redis.multi
|
18
|
-
# # => "OK"
|
19
|
-
# redis.set("key", "value")
|
20
|
-
# # => "QUEUED"
|
21
|
-
# redis.incr("counter")
|
22
|
-
# # => "QUEUED"
|
23
|
-
# redis.exec
|
24
|
-
# # => ["OK", 6]
|
25
|
-
#
|
26
14
|
# @yield [multi] the commands that are called inside this block are cached
|
27
15
|
# and written to the server upon returning from it
|
28
16
|
# @yieldparam [Redis] multi `self`
|
29
17
|
#
|
30
|
-
# @return [
|
31
|
-
# -
|
32
|
-
# - when a block is given, an array with replies
|
18
|
+
# @return [Array<...>]
|
19
|
+
# - an array with replies
|
33
20
|
#
|
34
21
|
# @see #watch
|
35
22
|
# @see #unwatch
|
36
|
-
def multi
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
end
|
41
|
-
|
42
|
-
synchronize do |prior_client|
|
43
|
-
pipeline = Pipeline::Multi.new(prior_client)
|
44
|
-
pipelined_connection = PipelinedConnection.new(pipeline)
|
45
|
-
yield pipelined_connection
|
46
|
-
prior_client.call_pipeline(pipeline)
|
23
|
+
def multi
|
24
|
+
synchronize do |client|
|
25
|
+
client.multi do |raw_transaction|
|
26
|
+
yield MultiConnection.new(raw_transaction)
|
47
27
|
end
|
48
|
-
else
|
49
|
-
send_command([:multi])
|
50
28
|
end
|
51
29
|
end
|
52
30
|
|
@@ -82,7 +60,7 @@ class Redis
|
|
82
60
|
# @see #multi
|
83
61
|
def watch(*keys)
|
84
62
|
synchronize do |client|
|
85
|
-
res = client.
|
63
|
+
res = client.call_v([:watch] + keys)
|
86
64
|
|
87
65
|
if block_given?
|
88
66
|
begin
|
@@ -125,8 +103,6 @@ class Redis
|
|
125
103
|
|
126
104
|
# Discard all commands issued after MULTI.
|
127
105
|
#
|
128
|
-
# Only call this method when `#multi` was called **without** a block.
|
129
|
-
#
|
130
106
|
# @return [String] `"OK"`
|
131
107
|
#
|
132
108
|
# @see #multi
|
data/lib/redis/commands.rb
CHANGED
@@ -40,14 +40,7 @@ class Redis
|
|
40
40
|
# where the method call will return nil. Propagate the nil instead of falsely
|
41
41
|
# returning false.
|
42
42
|
Boolify = lambda { |value|
|
43
|
-
|
44
|
-
when 1
|
45
|
-
true
|
46
|
-
when 0
|
47
|
-
false
|
48
|
-
else
|
49
|
-
value
|
50
|
-
end
|
43
|
+
value != 0 unless value.nil?
|
51
44
|
}
|
52
45
|
|
53
46
|
BoolifySet = lambda { |value|
|
data/lib/redis/distributed.rb
CHANGED
@@ -20,7 +20,7 @@ class Redis
|
|
20
20
|
def initialize(node_configs, options = {})
|
21
21
|
@tag = options[:tag] || /^\{(.+?)\}/
|
22
22
|
@ring = options[:ring] || HashRing.new
|
23
|
-
@node_configs = node_configs.dup
|
23
|
+
@node_configs = node_configs.map(&:dup)
|
24
24
|
@default_options = options.dup
|
25
25
|
node_configs.each { |node_config| add_node(node_config) }
|
26
26
|
@subscribed_node = nil
|
@@ -41,6 +41,8 @@ class Redis
|
|
41
41
|
def add_node(options)
|
42
42
|
options = { url: options } if options.is_a?(String)
|
43
43
|
options = @default_options.merge(options)
|
44
|
+
options.delete(:tag)
|
45
|
+
options.delete(:ring)
|
44
46
|
@ring.add_node Redis.new(options)
|
45
47
|
end
|
46
48
|
|
@@ -64,6 +66,10 @@ class Redis
|
|
64
66
|
on_each_node :quit
|
65
67
|
end
|
66
68
|
|
69
|
+
def close
|
70
|
+
on_each_node :close
|
71
|
+
end
|
72
|
+
|
67
73
|
# Asynchronously save the dataset to disk.
|
68
74
|
def bgsave
|
69
75
|
on_each_node :bgsave
|
@@ -115,13 +121,13 @@ class Redis
|
|
115
121
|
end
|
116
122
|
|
117
123
|
# Set a key's time to live in seconds.
|
118
|
-
def expire(key, seconds)
|
119
|
-
node_for(key).expire(key, seconds)
|
124
|
+
def expire(key, seconds, **kwargs)
|
125
|
+
node_for(key).expire(key, seconds, **kwargs)
|
120
126
|
end
|
121
127
|
|
122
128
|
# Set the expiration for a key as a UNIX timestamp.
|
123
|
-
def expireat(key, unix_time)
|
124
|
-
node_for(key).expireat(key, unix_time)
|
129
|
+
def expireat(key, unix_time, **kwargs)
|
130
|
+
node_for(key).expireat(key, unix_time, **kwargs)
|
125
131
|
end
|
126
132
|
|
127
133
|
# Get the time to live (in seconds) for a key.
|
@@ -130,13 +136,13 @@ class Redis
|
|
130
136
|
end
|
131
137
|
|
132
138
|
# Set a key's time to live in milliseconds.
|
133
|
-
def pexpire(key, milliseconds)
|
134
|
-
node_for(key).pexpire(key, milliseconds)
|
139
|
+
def pexpire(key, milliseconds, **kwarg)
|
140
|
+
node_for(key).pexpire(key, milliseconds, **kwarg)
|
135
141
|
end
|
136
142
|
|
137
143
|
# Set the expiration for a key as number of milliseconds from UNIX Epoch.
|
138
|
-
def pexpireat(key, ms_unix_time)
|
139
|
-
node_for(key).pexpireat(key, ms_unix_time)
|
144
|
+
def pexpireat(key, ms_unix_time, **kwarg)
|
145
|
+
node_for(key).pexpireat(key, ms_unix_time, **kwarg)
|
140
146
|
end
|
141
147
|
|
142
148
|
# Get the time to live (in milliseconds) for a key.
|
@@ -161,6 +167,7 @@ class Redis
|
|
161
167
|
|
162
168
|
# Delete a key.
|
163
169
|
def del(*args)
|
170
|
+
args.flatten!(1)
|
164
171
|
keys_per_node = args.group_by { |key| node_for(key) }
|
165
172
|
keys_per_node.inject(0) do |sum, (node, keys)|
|
166
173
|
sum + node.del(*keys)
|
@@ -169,6 +176,7 @@ class Redis
|
|
169
176
|
|
170
177
|
# Unlink keys.
|
171
178
|
def unlink(*args)
|
179
|
+
args.flatten!(1)
|
172
180
|
keys_per_node = args.group_by { |key| node_for(key) }
|
173
181
|
keys_per_node.inject(0) do |sum, (node, keys)|
|
174
182
|
sum + node.unlink(*keys)
|
@@ -177,23 +185,16 @@ class Redis
|
|
177
185
|
|
178
186
|
# Determine if a key exists.
|
179
187
|
def exists(*args)
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
184
|
-
"(#{::Kernel.caller(1, 1).first})\n"
|
185
|
-
)
|
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
|
188
|
+
args.flatten!(1)
|
189
|
+
keys_per_node = args.group_by { |key| node_for(key) }
|
190
|
+
keys_per_node.inject(0) do |sum, (node, keys)|
|
191
|
+
sum + node.exists(*keys)
|
192
192
|
end
|
193
193
|
end
|
194
194
|
|
195
195
|
# Determine if any of the keys exists.
|
196
196
|
def exists?(*args)
|
197
|
+
args.flatten!(1)
|
197
198
|
keys_per_node = args.group_by { |key| node_for(key) }
|
198
199
|
keys_per_node.each do |node, keys|
|
199
200
|
return true if node.exists?(*keys)
|
@@ -297,7 +298,7 @@ class Redis
|
|
297
298
|
end
|
298
299
|
|
299
300
|
# Set multiple keys to multiple values.
|
300
|
-
def mset(*
|
301
|
+
def mset(*)
|
301
302
|
raise CannotDistribute, :mset
|
302
303
|
end
|
303
304
|
|
@@ -306,7 +307,7 @@ class Redis
|
|
306
307
|
end
|
307
308
|
|
308
309
|
# Set multiple keys to multiple values, only if none of the keys exist.
|
309
|
-
def msetnx(*
|
310
|
+
def msetnx(*)
|
310
311
|
raise CannotDistribute, :msetnx
|
311
312
|
end
|
312
313
|
|
@@ -331,11 +332,13 @@ class Redis
|
|
331
332
|
|
332
333
|
# Get the values of all the given keys as an Array.
|
333
334
|
def mget(*keys)
|
335
|
+
keys.flatten!(1)
|
334
336
|
mapped_mget(*keys).values_at(*keys)
|
335
337
|
end
|
336
338
|
|
337
339
|
# Get the values of all the given keys as a Hash.
|
338
340
|
def mapped_mget(*keys)
|
341
|
+
keys.flatten!(1)
|
339
342
|
keys.group_by { |k| node_for k }.inject({}) do |results, (node, subkeys)|
|
340
343
|
results.merge! node.mapped_mget(*subkeys)
|
341
344
|
end
|
@@ -373,8 +376,9 @@ class Redis
|
|
373
376
|
|
374
377
|
# Perform a bitwise operation between strings and store the resulting string in a key.
|
375
378
|
def bitop(operation, destkey, *keys)
|
379
|
+
keys.flatten!(1)
|
376
380
|
ensure_same_node(:bitop, [destkey] + keys) do |node|
|
377
|
-
node.bitop(operation, destkey,
|
381
|
+
node.bitop(operation, destkey, keys)
|
378
382
|
end
|
379
383
|
end
|
380
384
|
|
@@ -463,22 +467,15 @@ class Redis
|
|
463
467
|
timeout = if args.last.is_a?(Hash)
|
464
468
|
options = args.pop
|
465
469
|
options[:timeout]
|
466
|
-
elsif args.last.respond_to?(:to_int)
|
467
|
-
# Issue deprecation notice in obnoxious mode...
|
468
|
-
args.pop.to_int
|
469
470
|
end
|
470
471
|
|
471
|
-
|
472
|
-
# Issue deprecation notice in obnoxious mode...
|
473
|
-
end
|
474
|
-
|
475
|
-
keys = args.flatten
|
472
|
+
args.flatten!(1)
|
476
473
|
|
477
|
-
ensure_same_node(cmd,
|
474
|
+
ensure_same_node(cmd, args) do |node|
|
478
475
|
if timeout
|
479
|
-
node.__send__(cmd,
|
476
|
+
node.__send__(cmd, args, timeout: timeout)
|
480
477
|
else
|
481
|
-
node.__send__(cmd,
|
478
|
+
node.__send__(cmd, args)
|
482
479
|
end
|
483
480
|
end
|
484
481
|
end
|
@@ -489,6 +486,18 @@ class Redis
|
|
489
486
|
_bpop(:blpop, args)
|
490
487
|
end
|
491
488
|
|
489
|
+
def bzpopmax(*args)
|
490
|
+
_bpop(:bzpopmax, args) do |reply|
|
491
|
+
reply.is_a?(Array) ? [reply[0], reply[1], Floatify.call(reply[2])] : reply
|
492
|
+
end
|
493
|
+
end
|
494
|
+
|
495
|
+
def bzpopmin(*args)
|
496
|
+
_bpop(:bzpopmin, args) do |reply|
|
497
|
+
reply.is_a?(Array) ? [reply[0], reply[1], Floatify.call(reply[2])] : reply
|
498
|
+
end
|
499
|
+
end
|
500
|
+
|
492
501
|
# Remove and get the last element in a list, or block until one is
|
493
502
|
# available.
|
494
503
|
def brpop(*args)
|
@@ -497,9 +506,9 @@ class Redis
|
|
497
506
|
|
498
507
|
# Pop a value from a list, push it to another list and return it; or block
|
499
508
|
# until one is available.
|
500
|
-
def brpoplpush(source, destination,
|
509
|
+
def brpoplpush(source, destination, **options)
|
501
510
|
ensure_same_node(:brpoplpush, [source, destination]) do |node|
|
502
|
-
node.brpoplpush(source, destination,
|
511
|
+
node.brpoplpush(source, destination, **options)
|
503
512
|
end
|
504
513
|
end
|
505
514
|
|
@@ -539,13 +548,23 @@ class Redis
|
|
539
548
|
end
|
540
549
|
|
541
550
|
# Add one or more members to a set.
|
542
|
-
def sadd(key,
|
543
|
-
node_for(key).sadd(key,
|
551
|
+
def sadd(key, *members)
|
552
|
+
node_for(key).sadd(key, *members)
|
553
|
+
end
|
554
|
+
|
555
|
+
# Add one or more members to a set.
|
556
|
+
def sadd?(key, *members)
|
557
|
+
node_for(key).sadd?(key, *members)
|
544
558
|
end
|
545
559
|
|
546
560
|
# Remove one or more members from a set.
|
547
|
-
def srem(key,
|
548
|
-
node_for(key).srem(key,
|
561
|
+
def srem(key, *members)
|
562
|
+
node_for(key).srem(key, *members)
|
563
|
+
end
|
564
|
+
|
565
|
+
# Remove one or more members from a set.
|
566
|
+
def srem?(key, *members)
|
567
|
+
node_for(key).srem?(key, *members)
|
549
568
|
end
|
550
569
|
|
551
570
|
# Remove and return a random member from a set.
|
@@ -592,43 +611,49 @@ class Redis
|
|
592
611
|
|
593
612
|
# Subtract multiple sets.
|
594
613
|
def sdiff(*keys)
|
614
|
+
keys.flatten!(1)
|
595
615
|
ensure_same_node(:sdiff, keys) do |node|
|
596
|
-
node.sdiff(
|
616
|
+
node.sdiff(keys)
|
597
617
|
end
|
598
618
|
end
|
599
619
|
|
600
620
|
# Subtract multiple sets and store the resulting set in a key.
|
601
621
|
def sdiffstore(destination, *keys)
|
602
|
-
|
603
|
-
|
622
|
+
keys.flatten!(1)
|
623
|
+
ensure_same_node(:sdiffstore, [destination].concat(keys)) do |node|
|
624
|
+
node.sdiffstore(destination, keys)
|
604
625
|
end
|
605
626
|
end
|
606
627
|
|
607
628
|
# Intersect multiple sets.
|
608
629
|
def sinter(*keys)
|
630
|
+
keys.flatten!(1)
|
609
631
|
ensure_same_node(:sinter, keys) do |node|
|
610
|
-
node.sinter(
|
632
|
+
node.sinter(keys)
|
611
633
|
end
|
612
634
|
end
|
613
635
|
|
614
636
|
# Intersect multiple sets and store the resulting set in a key.
|
615
637
|
def sinterstore(destination, *keys)
|
616
|
-
|
617
|
-
|
638
|
+
keys.flatten!(1)
|
639
|
+
ensure_same_node(:sinterstore, [destination].concat(keys)) do |node|
|
640
|
+
node.sinterstore(destination, keys)
|
618
641
|
end
|
619
642
|
end
|
620
643
|
|
621
644
|
# Add multiple sets.
|
622
645
|
def sunion(*keys)
|
646
|
+
keys.flatten!(1)
|
623
647
|
ensure_same_node(:sunion, keys) do |node|
|
624
|
-
node.sunion(
|
648
|
+
node.sunion(keys)
|
625
649
|
end
|
626
650
|
end
|
627
651
|
|
628
652
|
# Add multiple sets and store the resulting set in a key.
|
629
653
|
def sunionstore(destination, *keys)
|
630
|
-
|
631
|
-
|
654
|
+
keys.flatten!(1)
|
655
|
+
ensure_same_node(:sunionstore, [destination].concat(keys)) do |node|
|
656
|
+
node.sunionstore(destination, keys)
|
632
657
|
end
|
633
658
|
end
|
634
659
|
|
@@ -727,43 +752,49 @@ class Redis
|
|
727
752
|
|
728
753
|
# Get the intersection of multiple sorted sets
|
729
754
|
def zinter(*keys, **options)
|
755
|
+
keys.flatten!(1)
|
730
756
|
ensure_same_node(:zinter, keys) do |node|
|
731
|
-
node.zinter(
|
757
|
+
node.zinter(keys, **options)
|
732
758
|
end
|
733
759
|
end
|
734
760
|
|
735
761
|
# Intersect multiple sorted sets and store the resulting sorted set in a new
|
736
762
|
# key.
|
737
|
-
def zinterstore(destination, keys, **options)
|
738
|
-
|
763
|
+
def zinterstore(destination, *keys, **options)
|
764
|
+
keys.flatten!(1)
|
765
|
+
ensure_same_node(:zinterstore, [destination].concat(keys)) do |node|
|
739
766
|
node.zinterstore(destination, keys, **options)
|
740
767
|
end
|
741
768
|
end
|
742
769
|
|
743
770
|
# Return the union of multiple sorted sets.
|
744
771
|
def zunion(*keys, **options)
|
772
|
+
keys.flatten!(1)
|
745
773
|
ensure_same_node(:zunion, keys) do |node|
|
746
|
-
node.zunion(
|
774
|
+
node.zunion(keys, **options)
|
747
775
|
end
|
748
776
|
end
|
749
777
|
|
750
778
|
# Add multiple sorted sets and store the resulting sorted set in a new key.
|
751
|
-
def zunionstore(destination, keys, **options)
|
752
|
-
|
779
|
+
def zunionstore(destination, *keys, **options)
|
780
|
+
keys.flatten!(1)
|
781
|
+
ensure_same_node(:zunionstore, [destination].concat(keys)) do |node|
|
753
782
|
node.zunionstore(destination, keys, **options)
|
754
783
|
end
|
755
784
|
end
|
756
785
|
|
757
786
|
# Return the difference between the first and all successive input sorted sets.
|
758
787
|
def zdiff(*keys, **options)
|
788
|
+
keys.flatten!(1)
|
759
789
|
ensure_same_node(:zdiff, keys) do |node|
|
760
|
-
node.zdiff(
|
790
|
+
node.zdiff(keys, **options)
|
761
791
|
end
|
762
792
|
end
|
763
793
|
|
764
794
|
# Compute the difference between the first and all successive input sorted sets
|
765
795
|
# and store the resulting sorted set in a new key.
|
766
|
-
def zdiffstore(destination, keys, **options)
|
796
|
+
def zdiffstore(destination, *keys, **options)
|
797
|
+
keys.flatten!(1)
|
767
798
|
ensure_same_node(:zdiffstore, [destination] + keys) do |node|
|
768
799
|
node.zdiffstore(destination, keys, **options)
|
769
800
|
end
|
@@ -790,7 +821,7 @@ class Redis
|
|
790
821
|
end
|
791
822
|
|
792
823
|
def mapped_hmset(key, hash)
|
793
|
-
node_for(key).hmset(key,
|
824
|
+
node_for(key).hmset(key, hash)
|
794
825
|
end
|
795
826
|
|
796
827
|
# Get the value of a hash field.
|
@@ -800,11 +831,13 @@ class Redis
|
|
800
831
|
|
801
832
|
# Get the values of all the given hash fields.
|
802
833
|
def hmget(key, *fields)
|
803
|
-
|
834
|
+
fields.flatten!(1)
|
835
|
+
node_for(key).hmget(key, fields)
|
804
836
|
end
|
805
837
|
|
806
838
|
def mapped_hmget(key, *fields)
|
807
|
-
|
839
|
+
fields.flatten!(1)
|
840
|
+
node_for(key).mapped_hmget(key, fields)
|
808
841
|
end
|
809
842
|
|
810
843
|
def hrandfield(key, count = nil, **options)
|
@@ -813,7 +846,8 @@ class Redis
|
|
813
846
|
|
814
847
|
# Delete one or more hash fields.
|
815
848
|
def hdel(key, *fields)
|
816
|
-
|
849
|
+
fields.flatten!(1)
|
850
|
+
node_for(key).hdel(key, fields)
|
817
851
|
end
|
818
852
|
|
819
853
|
# Determine if a hash field exists.
|
@@ -917,9 +951,7 @@ class Redis
|
|
917
951
|
def multi(&block)
|
918
952
|
raise CannotDistribute, :multi unless @watch_key
|
919
953
|
|
920
|
-
|
921
|
-
@watch_key = nil if block_given?
|
922
|
-
result
|
954
|
+
node_for(@watch_key).multi(&block)
|
923
955
|
end
|
924
956
|
|
925
957
|
# Execute all commands issued after MULTI.
|
@@ -1009,7 +1041,8 @@ class Redis
|
|
1009
1041
|
end
|
1010
1042
|
|
1011
1043
|
def key_tag(key)
|
1012
|
-
key.to_s
|
1044
|
+
key = key.to_s
|
1045
|
+
key[@tag, 1] if key.match?(@tag)
|
1013
1046
|
end
|
1014
1047
|
|
1015
1048
|
def ensure_same_node(command, keys)
|
data/lib/redis/errors.rb
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
class Redis
|
4
4
|
# Base error for all redis-rb errors.
|
5
|
-
class BaseError <
|
5
|
+
class BaseError < StandardError
|
6
6
|
end
|
7
7
|
|
8
8
|
# Raised by the connection when a protocol error occurs.
|
@@ -20,6 +20,15 @@ class Redis
|
|
20
20
|
class CommandError < BaseError
|
21
21
|
end
|
22
22
|
|
23
|
+
class PermissionError < CommandError
|
24
|
+
end
|
25
|
+
|
26
|
+
class WrongTypeError < CommandError
|
27
|
+
end
|
28
|
+
|
29
|
+
class ReadOnlyError < CommandError
|
30
|
+
end
|
31
|
+
|
23
32
|
# Base error for connection related errors.
|
24
33
|
class BaseConnectionError < BaseError
|
25
34
|
end
|
@@ -43,55 +52,4 @@ class Redis
|
|
43
52
|
# Raised when client options are invalid.
|
44
53
|
class InvalidClientOptionError < BaseError
|
45
54
|
end
|
46
|
-
|
47
|
-
class Cluster
|
48
|
-
# Raised when client connected to redis as cluster mode
|
49
|
-
# and failed to fetch cluster state information by commands.
|
50
|
-
class InitialSetupError < BaseError
|
51
|
-
# @param errors [Array<Redis::BaseError>]
|
52
|
-
def initialize(errors)
|
53
|
-
super("Redis client could not fetch cluster information: #{errors.map(&:message).uniq.join(',')}")
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
# Raised when client connected to redis as cluster mode
|
58
|
-
# and some cluster subcommands were called.
|
59
|
-
class OrchestrationCommandNotSupported < BaseError
|
60
|
-
def initialize(command, subcommand = '')
|
61
|
-
str = [command, subcommand].map(&:to_s).reject(&:empty?).join(' ').upcase
|
62
|
-
msg = "#{str} command should be used with care "\
|
63
|
-
'only by applications orchestrating Redis Cluster, like redis-trib, '\
|
64
|
-
'and the command if used out of the right context can leave the cluster '\
|
65
|
-
'in a wrong state or cause data loss.'
|
66
|
-
super(msg)
|
67
|
-
end
|
68
|
-
end
|
69
|
-
|
70
|
-
# Raised when error occurs on any node of cluster.
|
71
|
-
class CommandErrorCollection < BaseError
|
72
|
-
attr_reader :errors
|
73
|
-
|
74
|
-
# @param errors [Hash{String => Redis::CommandError}]
|
75
|
-
# @param error_message [String]
|
76
|
-
def initialize(errors, error_message = 'Command errors were replied on any node')
|
77
|
-
@errors = errors
|
78
|
-
super(error_message)
|
79
|
-
end
|
80
|
-
end
|
81
|
-
|
82
|
-
# Raised when cluster client can't select node.
|
83
|
-
class AmbiguousNodeError < BaseError
|
84
|
-
def initialize(command)
|
85
|
-
super("Cluster client doesn't know which node the #{command} command should be sent to.")
|
86
|
-
end
|
87
|
-
end
|
88
|
-
|
89
|
-
# Raised when commands in pipelining include cross slot keys.
|
90
|
-
class CrossSlotPipeliningError < BaseError
|
91
|
-
def initialize(keys)
|
92
|
-
super("Cluster client couldn't send pipelining to single node. "\
|
93
|
-
"The commands include cross slot keys. #{keys}")
|
94
|
-
end
|
95
|
-
end
|
96
|
-
end
|
97
55
|
end
|
data/lib/redis/hash_ring.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'zlib'
|
4
|
+
require 'digest/md5'
|
4
5
|
|
5
6
|
class Redis
|
6
7
|
class HashRing
|
@@ -25,7 +26,7 @@ class Redis
|
|
25
26
|
def add_node(node)
|
26
27
|
@nodes << node
|
27
28
|
@replicas.times do |i|
|
28
|
-
key =
|
29
|
+
key = server_hash_for("#{node.id}:#{i}")
|
29
30
|
@ring[key] = node
|
30
31
|
@sorted_keys << key
|
31
32
|
end
|
@@ -35,7 +36,7 @@ class Redis
|
|
35
36
|
def remove_node(node)
|
36
37
|
@nodes.reject! { |n| n.id == node.id }
|
37
38
|
@replicas.times do |i|
|
38
|
-
key =
|
39
|
+
key = server_hash_for("#{node.id}:#{i}")
|
39
40
|
@ring.delete(key)
|
40
41
|
@sorted_keys.reject! { |k| k == key }
|
41
42
|
end
|
@@ -43,47 +44,46 @@ class Redis
|
|
43
44
|
|
44
45
|
# get the node in the hash ring for this key
|
45
46
|
def get_node(key)
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
def get_node_pos(key)
|
50
|
-
return [nil, nil] if @ring.empty?
|
51
|
-
|
52
|
-
crc = Zlib.crc32(key)
|
53
|
-
idx = HashRing.binary_search(@sorted_keys, crc)
|
54
|
-
[@ring[@sorted_keys[idx]], idx]
|
47
|
+
hash = hash_for(key)
|
48
|
+
idx = binary_search(@sorted_keys, hash)
|
49
|
+
@ring[@sorted_keys[idx]]
|
55
50
|
end
|
56
51
|
|
57
52
|
def iter_nodes(key)
|
58
53
|
return [nil, nil] if @ring.empty?
|
59
54
|
|
60
|
-
|
55
|
+
crc = hash_for(key)
|
56
|
+
pos = binary_search(@sorted_keys, crc)
|
61
57
|
@ring.size.times do |n|
|
62
58
|
yield @ring[@sorted_keys[(pos + n) % @ring.size]]
|
63
59
|
end
|
64
60
|
end
|
65
61
|
|
62
|
+
private
|
63
|
+
|
64
|
+
def hash_for(key)
|
65
|
+
Zlib.crc32(key)
|
66
|
+
end
|
67
|
+
|
68
|
+
def server_hash_for(key)
|
69
|
+
Digest::MD5.digest(key).unpack1("L>")
|
70
|
+
end
|
71
|
+
|
66
72
|
# Find the closest index in HashRing with value <= the given value
|
67
|
-
def
|
68
|
-
upper = ary.size
|
73
|
+
def binary_search(ary, value)
|
74
|
+
upper = ary.size
|
69
75
|
lower = 0
|
70
|
-
idx = 0
|
71
|
-
|
72
|
-
while lower <= upper
|
73
|
-
idx = (lower + upper) / 2
|
74
|
-
comp = ary[idx] <=> value
|
75
76
|
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
upper =
|
77
|
+
while lower < upper
|
78
|
+
mid = (lower + upper) / 2
|
79
|
+
if ary[mid] > value
|
80
|
+
upper = mid
|
80
81
|
else
|
81
|
-
lower =
|
82
|
+
lower = mid + 1
|
82
83
|
end
|
83
84
|
end
|
84
85
|
|
85
|
-
upper
|
86
|
-
upper
|
86
|
+
upper - 1
|
87
87
|
end
|
88
88
|
end
|
89
89
|
end
|