redis 4.5.1 → 5.0.6
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 +120 -0
- data/README.md +82 -153
- data/lib/redis/client.rb +77 -615
- data/lib/redis/commands/bitmaps.rb +66 -0
- data/lib/redis/commands/cluster.rb +28 -0
- data/lib/redis/commands/connection.rb +53 -0
- data/lib/redis/commands/geo.rb +84 -0
- data/lib/redis/commands/hashes.rb +254 -0
- data/lib/redis/commands/hyper_log_log.rb +37 -0
- data/lib/redis/commands/keys.rb +437 -0
- data/lib/redis/commands/lists.rb +285 -0
- data/lib/redis/commands/pubsub.rb +54 -0
- data/lib/redis/commands/scripting.rb +114 -0
- data/lib/redis/commands/server.rb +188 -0
- data/lib/redis/commands/sets.rb +214 -0
- data/lib/redis/commands/sorted_sets.rb +818 -0
- data/lib/redis/commands/streams.rb +402 -0
- data/lib/redis/commands/strings.rb +314 -0
- data/lib/redis/commands/transactions.rb +115 -0
- data/lib/redis/commands.rb +237 -0
- data/lib/redis/distributed.rb +140 -70
- data/lib/redis/errors.rb +15 -41
- data/lib/redis/hash_ring.rb +26 -26
- data/lib/redis/pipeline.rb +66 -120
- data/lib/redis/subscribe.rb +23 -15
- data/lib/redis/version.rb +1 -1
- data/lib/redis.rb +106 -3736
- metadata +26 -53
- data/lib/redis/cluster/command.rb +0 -81
- 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 -108
- data/lib/redis/cluster/node_key.rb +0 -31
- data/lib/redis/cluster/node_loader.rb +0 -37
- data/lib/redis/cluster/option.rb +0 -93
- data/lib/redis/cluster/slot.rb +0 -86
- data/lib/redis/cluster/slot_loader.rb +0 -49
- data/lib/redis/cluster.rb +0 -291
- data/lib/redis/connection/command_helper.rb +0 -41
- data/lib/redis/connection/hiredis.rb +0 -67
- data/lib/redis/connection/registry.rb +0 -13
- data/lib/redis/connection/ruby.rb +0 -428
- data/lib/redis/connection/synchrony.rb +0 -146
- data/lib/redis/connection.rb +0 -11
data/lib/redis/distributed.rb
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require "redis/hash_ring"
|
4
4
|
|
5
5
|
class Redis
|
6
6
|
class Distributed
|
@@ -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,27 +185,16 @@ class Redis
|
|
177
185
|
|
178
186
|
# Determine if a key exists.
|
179
187
|
def exists(*args)
|
180
|
-
|
181
|
-
|
182
|
-
|
183
|
-
|
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
|
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)
|
196
192
|
end
|
197
193
|
end
|
198
194
|
|
199
195
|
# Determine if any of the keys exists.
|
200
196
|
def exists?(*args)
|
197
|
+
args.flatten!(1)
|
201
198
|
keys_per_node = args.group_by { |key| node_for(key) }
|
202
199
|
keys_per_node.each do |node, keys|
|
203
200
|
return true if node.exists?(*keys)
|
@@ -215,6 +212,13 @@ class Redis
|
|
215
212
|
node_for(key).move(key, db)
|
216
213
|
end
|
217
214
|
|
215
|
+
# Copy a value from one key to another.
|
216
|
+
def copy(source, destination, **options)
|
217
|
+
ensure_same_node(:copy, [source, destination]) do |node|
|
218
|
+
node.copy(source, destination, **options)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
218
222
|
# Return a random key from the keyspace.
|
219
223
|
def randomkey
|
220
224
|
raise CannotDistribute, :randomkey
|
@@ -294,7 +298,7 @@ class Redis
|
|
294
298
|
end
|
295
299
|
|
296
300
|
# Set multiple keys to multiple values.
|
297
|
-
def mset(*
|
301
|
+
def mset(*)
|
298
302
|
raise CannotDistribute, :mset
|
299
303
|
end
|
300
304
|
|
@@ -303,7 +307,7 @@ class Redis
|
|
303
307
|
end
|
304
308
|
|
305
309
|
# Set multiple keys to multiple values, only if none of the keys exist.
|
306
|
-
def msetnx(*
|
310
|
+
def msetnx(*)
|
307
311
|
raise CannotDistribute, :msetnx
|
308
312
|
end
|
309
313
|
|
@@ -328,11 +332,13 @@ class Redis
|
|
328
332
|
|
329
333
|
# Get the values of all the given keys as an Array.
|
330
334
|
def mget(*keys)
|
335
|
+
keys.flatten!(1)
|
331
336
|
mapped_mget(*keys).values_at(*keys)
|
332
337
|
end
|
333
338
|
|
334
339
|
# Get the values of all the given keys as a Hash.
|
335
340
|
def mapped_mget(*keys)
|
341
|
+
keys.flatten!(1)
|
336
342
|
keys.group_by { |k| node_for k }.inject({}) do |results, (node, subkeys)|
|
337
343
|
results.merge! node.mapped_mget(*subkeys)
|
338
344
|
end
|
@@ -370,8 +376,9 @@ class Redis
|
|
370
376
|
|
371
377
|
# Perform a bitwise operation between strings and store the resulting string in a key.
|
372
378
|
def bitop(operation, destkey, *keys)
|
379
|
+
keys.flatten!(1)
|
373
380
|
ensure_same_node(:bitop, [destkey] + keys) do |node|
|
374
|
-
node.bitop(operation, destkey,
|
381
|
+
node.bitop(operation, destkey, keys)
|
375
382
|
end
|
376
383
|
end
|
377
384
|
|
@@ -460,22 +467,15 @@ class Redis
|
|
460
467
|
timeout = if args.last.is_a?(Hash)
|
461
468
|
options = args.pop
|
462
469
|
options[:timeout]
|
463
|
-
elsif args.last.respond_to?(:to_int)
|
464
|
-
# Issue deprecation notice in obnoxious mode...
|
465
|
-
args.pop.to_int
|
466
|
-
end
|
467
|
-
|
468
|
-
if args.size > 1
|
469
|
-
# Issue deprecation notice in obnoxious mode...
|
470
470
|
end
|
471
471
|
|
472
|
-
|
472
|
+
args.flatten!(1)
|
473
473
|
|
474
|
-
ensure_same_node(cmd,
|
474
|
+
ensure_same_node(cmd, args) do |node|
|
475
475
|
if timeout
|
476
|
-
node.__send__(cmd,
|
476
|
+
node.__send__(cmd, args, timeout: timeout)
|
477
477
|
else
|
478
|
-
node.__send__(cmd,
|
478
|
+
node.__send__(cmd, args)
|
479
479
|
end
|
480
480
|
end
|
481
481
|
end
|
@@ -486,6 +486,18 @@ class Redis
|
|
486
486
|
_bpop(:blpop, args)
|
487
487
|
end
|
488
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
|
+
|
489
501
|
# Remove and get the last element in a list, or block until one is
|
490
502
|
# available.
|
491
503
|
def brpop(*args)
|
@@ -494,9 +506,9 @@ class Redis
|
|
494
506
|
|
495
507
|
# Pop a value from a list, push it to another list and return it; or block
|
496
508
|
# until one is available.
|
497
|
-
def brpoplpush(source, destination,
|
509
|
+
def brpoplpush(source, destination, **options)
|
498
510
|
ensure_same_node(:brpoplpush, [source, destination]) do |node|
|
499
|
-
node.brpoplpush(source, destination,
|
511
|
+
node.brpoplpush(source, destination, **options)
|
500
512
|
end
|
501
513
|
end
|
502
514
|
|
@@ -536,13 +548,23 @@ class Redis
|
|
536
548
|
end
|
537
549
|
|
538
550
|
# Add one or more members to a set.
|
539
|
-
def sadd(key,
|
540
|
-
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)
|
558
|
+
end
|
559
|
+
|
560
|
+
# Remove one or more members from a set.
|
561
|
+
def srem(key, *members)
|
562
|
+
node_for(key).srem(key, *members)
|
541
563
|
end
|
542
564
|
|
543
565
|
# Remove one or more members from a set.
|
544
|
-
def srem(key,
|
545
|
-
node_for(key).srem(key,
|
566
|
+
def srem?(key, *members)
|
567
|
+
node_for(key).srem?(key, *members)
|
546
568
|
end
|
547
569
|
|
548
570
|
# Remove and return a random member from a set.
|
@@ -589,43 +611,49 @@ class Redis
|
|
589
611
|
|
590
612
|
# Subtract multiple sets.
|
591
613
|
def sdiff(*keys)
|
614
|
+
keys.flatten!(1)
|
592
615
|
ensure_same_node(:sdiff, keys) do |node|
|
593
|
-
node.sdiff(
|
616
|
+
node.sdiff(keys)
|
594
617
|
end
|
595
618
|
end
|
596
619
|
|
597
620
|
# Subtract multiple sets and store the resulting set in a key.
|
598
621
|
def sdiffstore(destination, *keys)
|
599
|
-
|
600
|
-
|
622
|
+
keys.flatten!(1)
|
623
|
+
ensure_same_node(:sdiffstore, [destination].concat(keys)) do |node|
|
624
|
+
node.sdiffstore(destination, keys)
|
601
625
|
end
|
602
626
|
end
|
603
627
|
|
604
628
|
# Intersect multiple sets.
|
605
629
|
def sinter(*keys)
|
630
|
+
keys.flatten!(1)
|
606
631
|
ensure_same_node(:sinter, keys) do |node|
|
607
|
-
node.sinter(
|
632
|
+
node.sinter(keys)
|
608
633
|
end
|
609
634
|
end
|
610
635
|
|
611
636
|
# Intersect multiple sets and store the resulting set in a key.
|
612
637
|
def sinterstore(destination, *keys)
|
613
|
-
|
614
|
-
|
638
|
+
keys.flatten!(1)
|
639
|
+
ensure_same_node(:sinterstore, [destination].concat(keys)) do |node|
|
640
|
+
node.sinterstore(destination, keys)
|
615
641
|
end
|
616
642
|
end
|
617
643
|
|
618
644
|
# Add multiple sets.
|
619
645
|
def sunion(*keys)
|
646
|
+
keys.flatten!(1)
|
620
647
|
ensure_same_node(:sunion, keys) do |node|
|
621
|
-
node.sunion(
|
648
|
+
node.sunion(keys)
|
622
649
|
end
|
623
650
|
end
|
624
651
|
|
625
652
|
# Add multiple sets and store the resulting set in a key.
|
626
653
|
def sunionstore(destination, *keys)
|
627
|
-
|
628
|
-
|
654
|
+
keys.flatten!(1)
|
655
|
+
ensure_same_node(:sunionstore, [destination].concat(keys)) do |node|
|
656
|
+
node.sunionstore(destination, keys)
|
629
657
|
end
|
630
658
|
end
|
631
659
|
|
@@ -666,11 +694,19 @@ class Redis
|
|
666
694
|
node_for(key).zmscore(key, *members)
|
667
695
|
end
|
668
696
|
|
669
|
-
# Return a range of members in a sorted set, by index.
|
697
|
+
# Return a range of members in a sorted set, by index, score or lexicographical ordering.
|
670
698
|
def zrange(key, start, stop, **options)
|
671
699
|
node_for(key).zrange(key, start, stop, **options)
|
672
700
|
end
|
673
701
|
|
702
|
+
# Select a range of members in a sorted set, by index, score or lexicographical ordering
|
703
|
+
# and store the resulting sorted set in a new key.
|
704
|
+
def zrangestore(dest_key, src_key, start, stop, **options)
|
705
|
+
ensure_same_node(:zrangestore, [dest_key, src_key]) do |node|
|
706
|
+
node.zrangestore(dest_key, src_key, start, stop, **options)
|
707
|
+
end
|
708
|
+
end
|
709
|
+
|
674
710
|
# Return a range of members in a sorted set, by index, with scores ordered
|
675
711
|
# from high to low.
|
676
712
|
def zrevrange(key, start, stop, **options)
|
@@ -716,26 +752,54 @@ class Redis
|
|
716
752
|
|
717
753
|
# Get the intersection of multiple sorted sets
|
718
754
|
def zinter(*keys, **options)
|
755
|
+
keys.flatten!(1)
|
719
756
|
ensure_same_node(:zinter, keys) do |node|
|
720
|
-
node.zinter(
|
757
|
+
node.zinter(keys, **options)
|
721
758
|
end
|
722
759
|
end
|
723
760
|
|
724
761
|
# Intersect multiple sorted sets and store the resulting sorted set in a new
|
725
762
|
# key.
|
726
|
-
def zinterstore(destination, keys, **options)
|
727
|
-
|
763
|
+
def zinterstore(destination, *keys, **options)
|
764
|
+
keys.flatten!(1)
|
765
|
+
ensure_same_node(:zinterstore, [destination].concat(keys)) do |node|
|
728
766
|
node.zinterstore(destination, keys, **options)
|
729
767
|
end
|
730
768
|
end
|
731
769
|
|
770
|
+
# Return the union of multiple sorted sets.
|
771
|
+
def zunion(*keys, **options)
|
772
|
+
keys.flatten!(1)
|
773
|
+
ensure_same_node(:zunion, keys) do |node|
|
774
|
+
node.zunion(keys, **options)
|
775
|
+
end
|
776
|
+
end
|
777
|
+
|
732
778
|
# Add multiple sorted sets and store the resulting sorted set in a new key.
|
733
|
-
def zunionstore(destination, keys, **options)
|
734
|
-
|
779
|
+
def zunionstore(destination, *keys, **options)
|
780
|
+
keys.flatten!(1)
|
781
|
+
ensure_same_node(:zunionstore, [destination].concat(keys)) do |node|
|
735
782
|
node.zunionstore(destination, keys, **options)
|
736
783
|
end
|
737
784
|
end
|
738
785
|
|
786
|
+
# Return the difference between the first and all successive input sorted sets.
|
787
|
+
def zdiff(*keys, **options)
|
788
|
+
keys.flatten!(1)
|
789
|
+
ensure_same_node(:zdiff, keys) do |node|
|
790
|
+
node.zdiff(keys, **options)
|
791
|
+
end
|
792
|
+
end
|
793
|
+
|
794
|
+
# Compute the difference between the first and all successive input sorted sets
|
795
|
+
# and store the resulting sorted set in a new key.
|
796
|
+
def zdiffstore(destination, *keys, **options)
|
797
|
+
keys.flatten!(1)
|
798
|
+
ensure_same_node(:zdiffstore, [destination] + keys) do |node|
|
799
|
+
node.zdiffstore(destination, keys, **options)
|
800
|
+
end
|
801
|
+
end
|
802
|
+
|
739
803
|
# Get the number of fields in a hash.
|
740
804
|
def hlen(key)
|
741
805
|
node_for(key).hlen(key)
|
@@ -757,7 +821,7 @@ class Redis
|
|
757
821
|
end
|
758
822
|
|
759
823
|
def mapped_hmset(key, hash)
|
760
|
-
node_for(key).hmset(key,
|
824
|
+
node_for(key).hmset(key, hash)
|
761
825
|
end
|
762
826
|
|
763
827
|
# Get the value of a hash field.
|
@@ -767,16 +831,23 @@ class Redis
|
|
767
831
|
|
768
832
|
# Get the values of all the given hash fields.
|
769
833
|
def hmget(key, *fields)
|
770
|
-
|
834
|
+
fields.flatten!(1)
|
835
|
+
node_for(key).hmget(key, fields)
|
771
836
|
end
|
772
837
|
|
773
838
|
def mapped_hmget(key, *fields)
|
774
|
-
|
839
|
+
fields.flatten!(1)
|
840
|
+
node_for(key).mapped_hmget(key, fields)
|
841
|
+
end
|
842
|
+
|
843
|
+
def hrandfield(key, count = nil, **options)
|
844
|
+
node_for(key).hrandfield(key, count, **options)
|
775
845
|
end
|
776
846
|
|
777
847
|
# Delete one or more hash fields.
|
778
848
|
def hdel(key, *fields)
|
779
|
-
|
849
|
+
fields.flatten!(1)
|
850
|
+
node_for(key).hdel(key, fields)
|
780
851
|
end
|
781
852
|
|
782
853
|
# Determine if a hash field exists.
|
@@ -833,7 +904,7 @@ class Redis
|
|
833
904
|
|
834
905
|
# Stop listening for messages posted to the given channels.
|
835
906
|
def unsubscribe(*channels)
|
836
|
-
raise "Can't unsubscribe if not subscribed." unless subscribed?
|
907
|
+
raise SubscriptionError, "Can't unsubscribe if not subscribed." unless subscribed?
|
837
908
|
|
838
909
|
@subscribed_node.unsubscribe(*channels)
|
839
910
|
end
|
@@ -880,9 +951,7 @@ class Redis
|
|
880
951
|
def multi(&block)
|
881
952
|
raise CannotDistribute, :multi unless @watch_key
|
882
953
|
|
883
|
-
|
884
|
-
@watch_key = nil if block_given?
|
885
|
-
result
|
954
|
+
node_for(@watch_key).multi(&block)
|
886
955
|
end
|
887
956
|
|
888
957
|
# Execute all commands issued after MULTI.
|
@@ -972,7 +1041,8 @@ class Redis
|
|
972
1041
|
end
|
973
1042
|
|
974
1043
|
def key_tag(key)
|
975
|
-
key.to_s
|
1044
|
+
key = key.to_s
|
1045
|
+
key[@tag, 1] if key.match?(@tag)
|
976
1046
|
end
|
977
1047
|
|
978
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 OutOfMemoryError < CommandError
|
30
|
+
end
|
31
|
+
|
23
32
|
# Base error for connection related errors.
|
24
33
|
class BaseConnectionError < BaseError
|
25
34
|
end
|
@@ -40,49 +49,14 @@ class Redis
|
|
40
49
|
class InheritedError < BaseConnectionError
|
41
50
|
end
|
42
51
|
|
52
|
+
# Generally raised during Redis failover scenarios
|
53
|
+
class ReadOnlyError < BaseConnectionError
|
54
|
+
end
|
55
|
+
|
43
56
|
# Raised when client options are invalid.
|
44
57
|
class InvalidClientOptionError < BaseError
|
45
58
|
end
|
46
59
|
|
47
|
-
class
|
48
|
-
# Raised when client connected to redis as cluster mode
|
49
|
-
# and some cluster subcommands were called.
|
50
|
-
class OrchestrationCommandNotSupported < BaseError
|
51
|
-
def initialize(command, subcommand = '')
|
52
|
-
str = [command, subcommand].map(&:to_s).reject(&:empty?).join(' ').upcase
|
53
|
-
msg = "#{str} command should be used with care "\
|
54
|
-
'only by applications orchestrating Redis Cluster, like redis-trib, '\
|
55
|
-
'and the command if used out of the right context can leave the cluster '\
|
56
|
-
'in a wrong state or cause data loss.'
|
57
|
-
super(msg)
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
# Raised when error occurs on any node of cluster.
|
62
|
-
class CommandErrorCollection < BaseError
|
63
|
-
attr_reader :errors
|
64
|
-
|
65
|
-
# @param errors [Hash{String => Redis::CommandError}]
|
66
|
-
# @param error_message [String]
|
67
|
-
def initialize(errors, error_message = 'Command errors were replied on any node')
|
68
|
-
@errors = errors
|
69
|
-
super(error_message)
|
70
|
-
end
|
71
|
-
end
|
72
|
-
|
73
|
-
# Raised when cluster client can't select node.
|
74
|
-
class AmbiguousNodeError < BaseError
|
75
|
-
def initialize(command)
|
76
|
-
super("Cluster client doesn't know which node the #{command} command should be sent to.")
|
77
|
-
end
|
78
|
-
end
|
79
|
-
|
80
|
-
# Raised when commands in pipelining include cross slot keys.
|
81
|
-
class CrossSlotPipeliningError < BaseError
|
82
|
-
def initialize(keys)
|
83
|
-
super("Cluster client couldn't send pipelining to single node. "\
|
84
|
-
"The commands include cross slot keys. #{keys}")
|
85
|
-
end
|
86
|
-
end
|
60
|
+
class SubscriptionError < BaseError
|
87
61
|
end
|
88
62
|
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
|