redis 4.7.0 → 5.0.8
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 +75 -0
- data/README.md +101 -161
- data/lib/redis/client.rb +82 -625
- 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/geo.rb +3 -3
- data/lib/redis/commands/hashes.rb +9 -6
- data/lib/redis/commands/hyper_log_log.rb +1 -1
- data/lib/redis/commands/keys.rb +53 -27
- data/lib/redis/commands/lists.rb +73 -23
- data/lib/redis/commands/pubsub.rb +28 -25
- data/lib/redis/commands/server.rb +15 -15
- data/lib/redis/commands/sets.rb +43 -36
- data/lib/redis/commands/sorted_sets.rb +84 -12
- data/lib/redis/commands/streams.rb +39 -19
- data/lib/redis/commands/strings.rb +18 -17
- data/lib/redis/commands/transactions.rb +7 -31
- data/lib/redis/commands.rb +4 -9
- data/lib/redis/distributed.rb +128 -67
- data/lib/redis/errors.rb +15 -50
- data/lib/redis/hash_ring.rb +26 -26
- data/lib/redis/pipeline.rb +43 -222
- data/lib/redis/subscribe.rb +50 -14
- data/lib/redis/version.rb +1 -1
- data/lib/redis.rb +75 -182
- metadata +10 -54
- 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 -431
- data/lib/redis/connection/synchrony.rb +0 -148
- data/lib/redis/connection.rb +0 -11
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
|
-
end
|
|
470
|
-
|
|
471
|
-
if args.size > 1
|
|
472
|
-
# Issue deprecation notice in obnoxious mode...
|
|
473
470
|
end
|
|
474
471
|
|
|
475
|
-
|
|
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
|
|
|
@@ -533,19 +542,43 @@ class Redis
|
|
|
533
542
|
node_for(key).ltrim(key, start, stop)
|
|
534
543
|
end
|
|
535
544
|
|
|
545
|
+
# Iterate over keys, blocking and removing elements from the first non empty liist found.
|
|
546
|
+
def blmpop(timeout, *keys, modifier: "LEFT", count: nil)
|
|
547
|
+
ensure_same_node(:blmpop, keys) do |node|
|
|
548
|
+
node.blmpop(timeout, *keys, modifier: modifier, count: count)
|
|
549
|
+
end
|
|
550
|
+
end
|
|
551
|
+
|
|
552
|
+
# Iterate over keys, removing elements from the first non list found.
|
|
553
|
+
def lmpop(*keys, modifier: "LEFT", count: nil)
|
|
554
|
+
ensure_same_node(:lmpop, keys) do |node|
|
|
555
|
+
node.lmpop(*keys, modifier: modifier, count: count)
|
|
556
|
+
end
|
|
557
|
+
end
|
|
558
|
+
|
|
536
559
|
# Get the number of members in a set.
|
|
537
560
|
def scard(key)
|
|
538
561
|
node_for(key).scard(key)
|
|
539
562
|
end
|
|
540
563
|
|
|
541
564
|
# Add one or more members to a set.
|
|
542
|
-
def sadd(key,
|
|
543
|
-
node_for(key).sadd(key,
|
|
565
|
+
def sadd(key, *members)
|
|
566
|
+
node_for(key).sadd(key, *members)
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
# Add one or more members to a set.
|
|
570
|
+
def sadd?(key, *members)
|
|
571
|
+
node_for(key).sadd?(key, *members)
|
|
544
572
|
end
|
|
545
573
|
|
|
546
574
|
# Remove one or more members from a set.
|
|
547
|
-
def srem(key,
|
|
548
|
-
node_for(key).srem(key,
|
|
575
|
+
def srem(key, *members)
|
|
576
|
+
node_for(key).srem(key, *members)
|
|
577
|
+
end
|
|
578
|
+
|
|
579
|
+
# Remove one or more members from a set.
|
|
580
|
+
def srem?(key, *members)
|
|
581
|
+
node_for(key).srem?(key, *members)
|
|
549
582
|
end
|
|
550
583
|
|
|
551
584
|
# Remove and return a random member from a set.
|
|
@@ -592,43 +625,49 @@ class Redis
|
|
|
592
625
|
|
|
593
626
|
# Subtract multiple sets.
|
|
594
627
|
def sdiff(*keys)
|
|
628
|
+
keys.flatten!(1)
|
|
595
629
|
ensure_same_node(:sdiff, keys) do |node|
|
|
596
|
-
node.sdiff(
|
|
630
|
+
node.sdiff(keys)
|
|
597
631
|
end
|
|
598
632
|
end
|
|
599
633
|
|
|
600
634
|
# Subtract multiple sets and store the resulting set in a key.
|
|
601
635
|
def sdiffstore(destination, *keys)
|
|
602
|
-
|
|
603
|
-
|
|
636
|
+
keys.flatten!(1)
|
|
637
|
+
ensure_same_node(:sdiffstore, [destination].concat(keys)) do |node|
|
|
638
|
+
node.sdiffstore(destination, keys)
|
|
604
639
|
end
|
|
605
640
|
end
|
|
606
641
|
|
|
607
642
|
# Intersect multiple sets.
|
|
608
643
|
def sinter(*keys)
|
|
644
|
+
keys.flatten!(1)
|
|
609
645
|
ensure_same_node(:sinter, keys) do |node|
|
|
610
|
-
node.sinter(
|
|
646
|
+
node.sinter(keys)
|
|
611
647
|
end
|
|
612
648
|
end
|
|
613
649
|
|
|
614
650
|
# Intersect multiple sets and store the resulting set in a key.
|
|
615
651
|
def sinterstore(destination, *keys)
|
|
616
|
-
|
|
617
|
-
|
|
652
|
+
keys.flatten!(1)
|
|
653
|
+
ensure_same_node(:sinterstore, [destination].concat(keys)) do |node|
|
|
654
|
+
node.sinterstore(destination, keys)
|
|
618
655
|
end
|
|
619
656
|
end
|
|
620
657
|
|
|
621
658
|
# Add multiple sets.
|
|
622
659
|
def sunion(*keys)
|
|
660
|
+
keys.flatten!(1)
|
|
623
661
|
ensure_same_node(:sunion, keys) do |node|
|
|
624
|
-
node.sunion(
|
|
662
|
+
node.sunion(keys)
|
|
625
663
|
end
|
|
626
664
|
end
|
|
627
665
|
|
|
628
666
|
# Add multiple sets and store the resulting set in a key.
|
|
629
667
|
def sunionstore(destination, *keys)
|
|
630
|
-
|
|
631
|
-
|
|
668
|
+
keys.flatten!(1)
|
|
669
|
+
ensure_same_node(:sunionstore, [destination].concat(keys)) do |node|
|
|
670
|
+
node.sunionstore(destination, keys)
|
|
632
671
|
end
|
|
633
672
|
end
|
|
634
673
|
|
|
@@ -669,6 +708,20 @@ class Redis
|
|
|
669
708
|
node_for(key).zmscore(key, *members)
|
|
670
709
|
end
|
|
671
710
|
|
|
711
|
+
# Iterate over keys, blocking and removing members from the first non empty sorted set found.
|
|
712
|
+
def bzmpop(timeout, *keys, modifier: "MIN", count: nil)
|
|
713
|
+
ensure_same_node(:bzmpop, keys) do |node|
|
|
714
|
+
node.bzmpop(timeout, *keys, modifier: modifier, count: count)
|
|
715
|
+
end
|
|
716
|
+
end
|
|
717
|
+
|
|
718
|
+
# Iterate over keys, removing members from the first non empty sorted set found.
|
|
719
|
+
def zmpop(*keys, modifier: "MIN", count: nil)
|
|
720
|
+
ensure_same_node(:zmpop, keys) do |node|
|
|
721
|
+
node.zmpop(*keys, modifier: modifier, count: count)
|
|
722
|
+
end
|
|
723
|
+
end
|
|
724
|
+
|
|
672
725
|
# Return a range of members in a sorted set, by index, score or lexicographical ordering.
|
|
673
726
|
def zrange(key, start, stop, **options)
|
|
674
727
|
node_for(key).zrange(key, start, stop, **options)
|
|
@@ -727,43 +780,49 @@ class Redis
|
|
|
727
780
|
|
|
728
781
|
# Get the intersection of multiple sorted sets
|
|
729
782
|
def zinter(*keys, **options)
|
|
783
|
+
keys.flatten!(1)
|
|
730
784
|
ensure_same_node(:zinter, keys) do |node|
|
|
731
|
-
node.zinter(
|
|
785
|
+
node.zinter(keys, **options)
|
|
732
786
|
end
|
|
733
787
|
end
|
|
734
788
|
|
|
735
789
|
# Intersect multiple sorted sets and store the resulting sorted set in a new
|
|
736
790
|
# key.
|
|
737
|
-
def zinterstore(destination, keys, **options)
|
|
738
|
-
|
|
791
|
+
def zinterstore(destination, *keys, **options)
|
|
792
|
+
keys.flatten!(1)
|
|
793
|
+
ensure_same_node(:zinterstore, [destination].concat(keys)) do |node|
|
|
739
794
|
node.zinterstore(destination, keys, **options)
|
|
740
795
|
end
|
|
741
796
|
end
|
|
742
797
|
|
|
743
798
|
# Return the union of multiple sorted sets.
|
|
744
799
|
def zunion(*keys, **options)
|
|
800
|
+
keys.flatten!(1)
|
|
745
801
|
ensure_same_node(:zunion, keys) do |node|
|
|
746
|
-
node.zunion(
|
|
802
|
+
node.zunion(keys, **options)
|
|
747
803
|
end
|
|
748
804
|
end
|
|
749
805
|
|
|
750
806
|
# Add multiple sorted sets and store the resulting sorted set in a new key.
|
|
751
|
-
def zunionstore(destination, keys, **options)
|
|
752
|
-
|
|
807
|
+
def zunionstore(destination, *keys, **options)
|
|
808
|
+
keys.flatten!(1)
|
|
809
|
+
ensure_same_node(:zunionstore, [destination].concat(keys)) do |node|
|
|
753
810
|
node.zunionstore(destination, keys, **options)
|
|
754
811
|
end
|
|
755
812
|
end
|
|
756
813
|
|
|
757
814
|
# Return the difference between the first and all successive input sorted sets.
|
|
758
815
|
def zdiff(*keys, **options)
|
|
816
|
+
keys.flatten!(1)
|
|
759
817
|
ensure_same_node(:zdiff, keys) do |node|
|
|
760
|
-
node.zdiff(
|
|
818
|
+
node.zdiff(keys, **options)
|
|
761
819
|
end
|
|
762
820
|
end
|
|
763
821
|
|
|
764
822
|
# Compute the difference between the first and all successive input sorted sets
|
|
765
823
|
# and store the resulting sorted set in a new key.
|
|
766
|
-
def zdiffstore(destination, keys, **options)
|
|
824
|
+
def zdiffstore(destination, *keys, **options)
|
|
825
|
+
keys.flatten!(1)
|
|
767
826
|
ensure_same_node(:zdiffstore, [destination] + keys) do |node|
|
|
768
827
|
node.zdiffstore(destination, keys, **options)
|
|
769
828
|
end
|
|
@@ -790,7 +849,7 @@ class Redis
|
|
|
790
849
|
end
|
|
791
850
|
|
|
792
851
|
def mapped_hmset(key, hash)
|
|
793
|
-
node_for(key).hmset(key,
|
|
852
|
+
node_for(key).hmset(key, hash)
|
|
794
853
|
end
|
|
795
854
|
|
|
796
855
|
# Get the value of a hash field.
|
|
@@ -800,11 +859,13 @@ class Redis
|
|
|
800
859
|
|
|
801
860
|
# Get the values of all the given hash fields.
|
|
802
861
|
def hmget(key, *fields)
|
|
803
|
-
|
|
862
|
+
fields.flatten!(1)
|
|
863
|
+
node_for(key).hmget(key, fields)
|
|
804
864
|
end
|
|
805
865
|
|
|
806
866
|
def mapped_hmget(key, *fields)
|
|
807
|
-
|
|
867
|
+
fields.flatten!(1)
|
|
868
|
+
node_for(key).mapped_hmget(key, fields)
|
|
808
869
|
end
|
|
809
870
|
|
|
810
871
|
def hrandfield(key, count = nil, **options)
|
|
@@ -813,7 +874,8 @@ class Redis
|
|
|
813
874
|
|
|
814
875
|
# Delete one or more hash fields.
|
|
815
876
|
def hdel(key, *fields)
|
|
816
|
-
|
|
877
|
+
fields.flatten!(1)
|
|
878
|
+
node_for(key).hdel(key, fields)
|
|
817
879
|
end
|
|
818
880
|
|
|
819
881
|
# Determine if a hash field exists.
|
|
@@ -870,7 +932,7 @@ class Redis
|
|
|
870
932
|
|
|
871
933
|
# Stop listening for messages posted to the given channels.
|
|
872
934
|
def unsubscribe(*channels)
|
|
873
|
-
raise "Can't unsubscribe if not subscribed." unless subscribed?
|
|
935
|
+
raise SubscriptionError, "Can't unsubscribe if not subscribed." unless subscribed?
|
|
874
936
|
|
|
875
937
|
@subscribed_node.unsubscribe(*channels)
|
|
876
938
|
end
|
|
@@ -917,9 +979,7 @@ class Redis
|
|
|
917
979
|
def multi(&block)
|
|
918
980
|
raise CannotDistribute, :multi unless @watch_key
|
|
919
981
|
|
|
920
|
-
|
|
921
|
-
@watch_key = nil if block_given?
|
|
922
|
-
result
|
|
982
|
+
node_for(@watch_key).multi(&block)
|
|
923
983
|
end
|
|
924
984
|
|
|
925
985
|
# Execute all commands issued after MULTI.
|
|
@@ -1009,7 +1069,8 @@ class Redis
|
|
|
1009
1069
|
end
|
|
1010
1070
|
|
|
1011
1071
|
def key_tag(key)
|
|
1012
|
-
key.to_s
|
|
1072
|
+
key = key.to_s
|
|
1073
|
+
key[@tag, 1] if key.match?(@tag)
|
|
1013
1074
|
end
|
|
1014
1075
|
|
|
1015
1076
|
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,58 +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 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
|
|
60
|
+
class SubscriptionError < BaseError
|
|
96
61
|
end
|
|
97
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
|