redis 4.7.1 → 5.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 +93 -0
- data/README.md +125 -162
- data/lib/redis/client.rb +82 -625
- data/lib/redis/commands/bitmaps.rb +14 -4
- 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 +13 -6
- data/lib/redis/commands/hyper_log_log.rb +1 -1
- data/lib/redis/commands/keys.rb +75 -27
- data/lib/redis/commands/lists.rb +73 -23
- data/lib/redis/commands/pubsub.rb +34 -25
- data/lib/redis/commands/server.rb +15 -15
- data/lib/redis/commands/sets.rb +47 -36
- data/lib/redis/commands/sorted_sets.rb +128 -18
- data/lib/redis/commands/streams.rb +48 -21
- data/lib/redis/commands/strings.rb +18 -17
- data/lib/redis/commands/transactions.rb +7 -31
- data/lib/redis/commands.rb +11 -14
- data/lib/redis/distributed.rb +150 -75
- data/lib/redis/errors.rb +15 -50
- data/lib/redis/hash_ring.rb +26 -26
- data/lib/redis/pipeline.rb +47 -222
- data/lib/redis/subscribe.rb +50 -14
- data/lib/redis/version.rb +1 -1
- data/lib/redis.rb +76 -182
- metadata +10 -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
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,18 @@ 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)
|
131
|
+
end
|
132
|
+
|
133
|
+
# Get the expiration for a key as a UNIX timestamp.
|
134
|
+
def expiretime(key)
|
135
|
+
node_for(key).expiretime(key)
|
125
136
|
end
|
126
137
|
|
127
138
|
# Get the time to live (in seconds) for a key.
|
@@ -130,13 +141,18 @@ class Redis
|
|
130
141
|
end
|
131
142
|
|
132
143
|
# Set a key's time to live in milliseconds.
|
133
|
-
def pexpire(key, milliseconds)
|
134
|
-
node_for(key).pexpire(key, milliseconds)
|
144
|
+
def pexpire(key, milliseconds, **kwarg)
|
145
|
+
node_for(key).pexpire(key, milliseconds, **kwarg)
|
135
146
|
end
|
136
147
|
|
137
148
|
# 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)
|
149
|
+
def pexpireat(key, ms_unix_time, **kwarg)
|
150
|
+
node_for(key).pexpireat(key, ms_unix_time, **kwarg)
|
151
|
+
end
|
152
|
+
|
153
|
+
# Get the expiration for a key as number of milliseconds from UNIX Epoch.
|
154
|
+
def pexpiretime(key)
|
155
|
+
node_for(key).pexpiretime(key)
|
140
156
|
end
|
141
157
|
|
142
158
|
# Get the time to live (in milliseconds) for a key.
|
@@ -161,6 +177,7 @@ class Redis
|
|
161
177
|
|
162
178
|
# Delete a key.
|
163
179
|
def del(*args)
|
180
|
+
args.flatten!(1)
|
164
181
|
keys_per_node = args.group_by { |key| node_for(key) }
|
165
182
|
keys_per_node.inject(0) do |sum, (node, keys)|
|
166
183
|
sum + node.del(*keys)
|
@@ -169,6 +186,7 @@ class Redis
|
|
169
186
|
|
170
187
|
# Unlink keys.
|
171
188
|
def unlink(*args)
|
189
|
+
args.flatten!(1)
|
172
190
|
keys_per_node = args.group_by { |key| node_for(key) }
|
173
191
|
keys_per_node.inject(0) do |sum, (node, keys)|
|
174
192
|
sum + node.unlink(*keys)
|
@@ -177,23 +195,16 @@ class Redis
|
|
177
195
|
|
178
196
|
# Determine if a key exists.
|
179
197
|
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
|
198
|
+
args.flatten!(1)
|
199
|
+
keys_per_node = args.group_by { |key| node_for(key) }
|
200
|
+
keys_per_node.inject(0) do |sum, (node, keys)|
|
201
|
+
sum + node.exists(*keys)
|
192
202
|
end
|
193
203
|
end
|
194
204
|
|
195
205
|
# Determine if any of the keys exists.
|
196
206
|
def exists?(*args)
|
207
|
+
args.flatten!(1)
|
197
208
|
keys_per_node = args.group_by { |key| node_for(key) }
|
198
209
|
keys_per_node.each do |node, keys|
|
199
210
|
return true if node.exists?(*keys)
|
@@ -297,7 +308,7 @@ class Redis
|
|
297
308
|
end
|
298
309
|
|
299
310
|
# Set multiple keys to multiple values.
|
300
|
-
def mset(*
|
311
|
+
def mset(*)
|
301
312
|
raise CannotDistribute, :mset
|
302
313
|
end
|
303
314
|
|
@@ -306,7 +317,7 @@ class Redis
|
|
306
317
|
end
|
307
318
|
|
308
319
|
# Set multiple keys to multiple values, only if none of the keys exist.
|
309
|
-
def msetnx(*
|
320
|
+
def msetnx(*)
|
310
321
|
raise CannotDistribute, :msetnx
|
311
322
|
end
|
312
323
|
|
@@ -331,11 +342,13 @@ class Redis
|
|
331
342
|
|
332
343
|
# Get the values of all the given keys as an Array.
|
333
344
|
def mget(*keys)
|
345
|
+
keys.flatten!(1)
|
334
346
|
mapped_mget(*keys).values_at(*keys)
|
335
347
|
end
|
336
348
|
|
337
349
|
# Get the values of all the given keys as a Hash.
|
338
350
|
def mapped_mget(*keys)
|
351
|
+
keys.flatten!(1)
|
339
352
|
keys.group_by { |k| node_for k }.inject({}) do |results, (node, subkeys)|
|
340
353
|
results.merge! node.mapped_mget(*subkeys)
|
341
354
|
end
|
@@ -367,20 +380,21 @@ class Redis
|
|
367
380
|
end
|
368
381
|
|
369
382
|
# Count the number of set bits in a range of the string value stored at key.
|
370
|
-
def bitcount(key, start = 0, stop = -1)
|
371
|
-
node_for(key).bitcount(key, start, stop)
|
383
|
+
def bitcount(key, start = 0, stop = -1, scale: nil)
|
384
|
+
node_for(key).bitcount(key, start, stop, scale: scale)
|
372
385
|
end
|
373
386
|
|
374
387
|
# Perform a bitwise operation between strings and store the resulting string in a key.
|
375
388
|
def bitop(operation, destkey, *keys)
|
389
|
+
keys.flatten!(1)
|
376
390
|
ensure_same_node(:bitop, [destkey] + keys) do |node|
|
377
|
-
node.bitop(operation, destkey,
|
391
|
+
node.bitop(operation, destkey, keys)
|
378
392
|
end
|
379
393
|
end
|
380
394
|
|
381
395
|
# Return the position of the first bit set to 1 or 0 in a string.
|
382
|
-
def bitpos(key, bit, start = nil, stop = nil)
|
383
|
-
node_for(key).bitpos(key, bit, start, stop)
|
396
|
+
def bitpos(key, bit, start = nil, stop = nil, scale: nil)
|
397
|
+
node_for(key).bitpos(key, bit, start, stop, scale: scale)
|
384
398
|
end
|
385
399
|
|
386
400
|
# Set the string value of a key and return its old value.
|
@@ -463,22 +477,15 @@ class Redis
|
|
463
477
|
timeout = if args.last.is_a?(Hash)
|
464
478
|
options = args.pop
|
465
479
|
options[:timeout]
|
466
|
-
elsif args.last.respond_to?(:to_int)
|
467
|
-
# Issue deprecation notice in obnoxious mode...
|
468
|
-
args.pop.to_int
|
469
480
|
end
|
470
481
|
|
471
|
-
|
472
|
-
# Issue deprecation notice in obnoxious mode...
|
473
|
-
end
|
474
|
-
|
475
|
-
keys = args.flatten
|
482
|
+
args.flatten!(1)
|
476
483
|
|
477
|
-
ensure_same_node(cmd,
|
484
|
+
ensure_same_node(cmd, args) do |node|
|
478
485
|
if timeout
|
479
|
-
node.__send__(cmd,
|
486
|
+
node.__send__(cmd, args, timeout: timeout)
|
480
487
|
else
|
481
|
-
node.__send__(cmd,
|
488
|
+
node.__send__(cmd, args)
|
482
489
|
end
|
483
490
|
end
|
484
491
|
end
|
@@ -489,6 +496,18 @@ class Redis
|
|
489
496
|
_bpop(:blpop, args)
|
490
497
|
end
|
491
498
|
|
499
|
+
def bzpopmax(*args)
|
500
|
+
_bpop(:bzpopmax, args) do |reply|
|
501
|
+
reply.is_a?(Array) ? [reply[0], reply[1], Floatify.call(reply[2])] : reply
|
502
|
+
end
|
503
|
+
end
|
504
|
+
|
505
|
+
def bzpopmin(*args)
|
506
|
+
_bpop(:bzpopmin, args) do |reply|
|
507
|
+
reply.is_a?(Array) ? [reply[0], reply[1], Floatify.call(reply[2])] : reply
|
508
|
+
end
|
509
|
+
end
|
510
|
+
|
492
511
|
# Remove and get the last element in a list, or block until one is
|
493
512
|
# available.
|
494
513
|
def brpop(*args)
|
@@ -497,9 +516,9 @@ class Redis
|
|
497
516
|
|
498
517
|
# Pop a value from a list, push it to another list and return it; or block
|
499
518
|
# until one is available.
|
500
|
-
def brpoplpush(source, destination,
|
519
|
+
def brpoplpush(source, destination, **options)
|
501
520
|
ensure_same_node(:brpoplpush, [source, destination]) do |node|
|
502
|
-
node.brpoplpush(source, destination,
|
521
|
+
node.brpoplpush(source, destination, **options)
|
503
522
|
end
|
504
523
|
end
|
505
524
|
|
@@ -533,19 +552,43 @@ class Redis
|
|
533
552
|
node_for(key).ltrim(key, start, stop)
|
534
553
|
end
|
535
554
|
|
555
|
+
# Iterate over keys, blocking and removing elements from the first non empty liist found.
|
556
|
+
def blmpop(timeout, *keys, modifier: "LEFT", count: nil)
|
557
|
+
ensure_same_node(:blmpop, keys) do |node|
|
558
|
+
node.blmpop(timeout, *keys, modifier: modifier, count: count)
|
559
|
+
end
|
560
|
+
end
|
561
|
+
|
562
|
+
# Iterate over keys, removing elements from the first non list found.
|
563
|
+
def lmpop(*keys, modifier: "LEFT", count: nil)
|
564
|
+
ensure_same_node(:lmpop, keys) do |node|
|
565
|
+
node.lmpop(*keys, modifier: modifier, count: count)
|
566
|
+
end
|
567
|
+
end
|
568
|
+
|
536
569
|
# Get the number of members in a set.
|
537
570
|
def scard(key)
|
538
571
|
node_for(key).scard(key)
|
539
572
|
end
|
540
573
|
|
541
574
|
# Add one or more members to a set.
|
542
|
-
def sadd(key,
|
543
|
-
node_for(key).sadd(key,
|
575
|
+
def sadd(key, *members)
|
576
|
+
node_for(key).sadd(key, *members)
|
577
|
+
end
|
578
|
+
|
579
|
+
# Add one or more members to a set.
|
580
|
+
def sadd?(key, *members)
|
581
|
+
node_for(key).sadd?(key, *members)
|
544
582
|
end
|
545
583
|
|
546
584
|
# Remove one or more members from a set.
|
547
|
-
def srem(key,
|
548
|
-
node_for(key).srem(key,
|
585
|
+
def srem(key, *members)
|
586
|
+
node_for(key).srem(key, *members)
|
587
|
+
end
|
588
|
+
|
589
|
+
# Remove one or more members from a set.
|
590
|
+
def srem?(key, *members)
|
591
|
+
node_for(key).srem?(key, *members)
|
549
592
|
end
|
550
593
|
|
551
594
|
# Remove and return a random member from a set.
|
@@ -592,43 +635,49 @@ class Redis
|
|
592
635
|
|
593
636
|
# Subtract multiple sets.
|
594
637
|
def sdiff(*keys)
|
638
|
+
keys.flatten!(1)
|
595
639
|
ensure_same_node(:sdiff, keys) do |node|
|
596
|
-
node.sdiff(
|
640
|
+
node.sdiff(keys)
|
597
641
|
end
|
598
642
|
end
|
599
643
|
|
600
644
|
# Subtract multiple sets and store the resulting set in a key.
|
601
645
|
def sdiffstore(destination, *keys)
|
602
|
-
|
603
|
-
|
646
|
+
keys.flatten!(1)
|
647
|
+
ensure_same_node(:sdiffstore, [destination].concat(keys)) do |node|
|
648
|
+
node.sdiffstore(destination, keys)
|
604
649
|
end
|
605
650
|
end
|
606
651
|
|
607
652
|
# Intersect multiple sets.
|
608
653
|
def sinter(*keys)
|
654
|
+
keys.flatten!(1)
|
609
655
|
ensure_same_node(:sinter, keys) do |node|
|
610
|
-
node.sinter(
|
656
|
+
node.sinter(keys)
|
611
657
|
end
|
612
658
|
end
|
613
659
|
|
614
660
|
# Intersect multiple sets and store the resulting set in a key.
|
615
661
|
def sinterstore(destination, *keys)
|
616
|
-
|
617
|
-
|
662
|
+
keys.flatten!(1)
|
663
|
+
ensure_same_node(:sinterstore, [destination].concat(keys)) do |node|
|
664
|
+
node.sinterstore(destination, keys)
|
618
665
|
end
|
619
666
|
end
|
620
667
|
|
621
668
|
# Add multiple sets.
|
622
669
|
def sunion(*keys)
|
670
|
+
keys.flatten!(1)
|
623
671
|
ensure_same_node(:sunion, keys) do |node|
|
624
|
-
node.sunion(
|
672
|
+
node.sunion(keys)
|
625
673
|
end
|
626
674
|
end
|
627
675
|
|
628
676
|
# Add multiple sets and store the resulting set in a key.
|
629
677
|
def sunionstore(destination, *keys)
|
630
|
-
|
631
|
-
|
678
|
+
keys.flatten!(1)
|
679
|
+
ensure_same_node(:sunionstore, [destination].concat(keys)) do |node|
|
680
|
+
node.sunionstore(destination, keys)
|
632
681
|
end
|
633
682
|
end
|
634
683
|
|
@@ -669,6 +718,20 @@ class Redis
|
|
669
718
|
node_for(key).zmscore(key, *members)
|
670
719
|
end
|
671
720
|
|
721
|
+
# Iterate over keys, blocking and removing members from the first non empty sorted set found.
|
722
|
+
def bzmpop(timeout, *keys, modifier: "MIN", count: nil)
|
723
|
+
ensure_same_node(:bzmpop, keys) do |node|
|
724
|
+
node.bzmpop(timeout, *keys, modifier: modifier, count: count)
|
725
|
+
end
|
726
|
+
end
|
727
|
+
|
728
|
+
# Iterate over keys, removing members from the first non empty sorted set found.
|
729
|
+
def zmpop(*keys, modifier: "MIN", count: nil)
|
730
|
+
ensure_same_node(:zmpop, keys) do |node|
|
731
|
+
node.zmpop(*keys, modifier: modifier, count: count)
|
732
|
+
end
|
733
|
+
end
|
734
|
+
|
672
735
|
# Return a range of members in a sorted set, by index, score or lexicographical ordering.
|
673
736
|
def zrange(key, start, stop, **options)
|
674
737
|
node_for(key).zrange(key, start, stop, **options)
|
@@ -689,14 +752,14 @@ class Redis
|
|
689
752
|
end
|
690
753
|
|
691
754
|
# Determine the index of a member in a sorted set.
|
692
|
-
def zrank(key, member)
|
693
|
-
node_for(key).zrank(key, member)
|
755
|
+
def zrank(key, member, **options)
|
756
|
+
node_for(key).zrank(key, member, **options)
|
694
757
|
end
|
695
758
|
|
696
759
|
# Determine the index of a member in a sorted set, with scores ordered from
|
697
760
|
# high to low.
|
698
|
-
def zrevrank(key, member)
|
699
|
-
node_for(key).zrevrank(key, member)
|
761
|
+
def zrevrank(key, member, **options)
|
762
|
+
node_for(key).zrevrank(key, member, **options)
|
700
763
|
end
|
701
764
|
|
702
765
|
# Remove all members in a sorted set within the given indexes.
|
@@ -727,43 +790,49 @@ class Redis
|
|
727
790
|
|
728
791
|
# Get the intersection of multiple sorted sets
|
729
792
|
def zinter(*keys, **options)
|
793
|
+
keys.flatten!(1)
|
730
794
|
ensure_same_node(:zinter, keys) do |node|
|
731
|
-
node.zinter(
|
795
|
+
node.zinter(keys, **options)
|
732
796
|
end
|
733
797
|
end
|
734
798
|
|
735
799
|
# Intersect multiple sorted sets and store the resulting sorted set in a new
|
736
800
|
# key.
|
737
|
-
def zinterstore(destination, keys, **options)
|
738
|
-
|
801
|
+
def zinterstore(destination, *keys, **options)
|
802
|
+
keys.flatten!(1)
|
803
|
+
ensure_same_node(:zinterstore, [destination].concat(keys)) do |node|
|
739
804
|
node.zinterstore(destination, keys, **options)
|
740
805
|
end
|
741
806
|
end
|
742
807
|
|
743
808
|
# Return the union of multiple sorted sets.
|
744
809
|
def zunion(*keys, **options)
|
810
|
+
keys.flatten!(1)
|
745
811
|
ensure_same_node(:zunion, keys) do |node|
|
746
|
-
node.zunion(
|
812
|
+
node.zunion(keys, **options)
|
747
813
|
end
|
748
814
|
end
|
749
815
|
|
750
816
|
# Add multiple sorted sets and store the resulting sorted set in a new key.
|
751
|
-
def zunionstore(destination, keys, **options)
|
752
|
-
|
817
|
+
def zunionstore(destination, *keys, **options)
|
818
|
+
keys.flatten!(1)
|
819
|
+
ensure_same_node(:zunionstore, [destination].concat(keys)) do |node|
|
753
820
|
node.zunionstore(destination, keys, **options)
|
754
821
|
end
|
755
822
|
end
|
756
823
|
|
757
824
|
# Return the difference between the first and all successive input sorted sets.
|
758
825
|
def zdiff(*keys, **options)
|
826
|
+
keys.flatten!(1)
|
759
827
|
ensure_same_node(:zdiff, keys) do |node|
|
760
|
-
node.zdiff(
|
828
|
+
node.zdiff(keys, **options)
|
761
829
|
end
|
762
830
|
end
|
763
831
|
|
764
832
|
# Compute the difference between the first and all successive input sorted sets
|
765
833
|
# and store the resulting sorted set in a new key.
|
766
|
-
def zdiffstore(destination, keys, **options)
|
834
|
+
def zdiffstore(destination, *keys, **options)
|
835
|
+
keys.flatten!(1)
|
767
836
|
ensure_same_node(:zdiffstore, [destination] + keys) do |node|
|
768
837
|
node.zdiffstore(destination, keys, **options)
|
769
838
|
end
|
@@ -790,7 +859,7 @@ class Redis
|
|
790
859
|
end
|
791
860
|
|
792
861
|
def mapped_hmset(key, hash)
|
793
|
-
node_for(key).hmset(key,
|
862
|
+
node_for(key).hmset(key, hash)
|
794
863
|
end
|
795
864
|
|
796
865
|
# Get the value of a hash field.
|
@@ -800,11 +869,13 @@ class Redis
|
|
800
869
|
|
801
870
|
# Get the values of all the given hash fields.
|
802
871
|
def hmget(key, *fields)
|
803
|
-
|
872
|
+
fields.flatten!(1)
|
873
|
+
node_for(key).hmget(key, fields)
|
804
874
|
end
|
805
875
|
|
806
876
|
def mapped_hmget(key, *fields)
|
807
|
-
|
877
|
+
fields.flatten!(1)
|
878
|
+
node_for(key).mapped_hmget(key, fields)
|
808
879
|
end
|
809
880
|
|
810
881
|
def hrandfield(key, count = nil, **options)
|
@@ -813,7 +884,8 @@ class Redis
|
|
813
884
|
|
814
885
|
# Delete one or more hash fields.
|
815
886
|
def hdel(key, *fields)
|
816
|
-
|
887
|
+
fields.flatten!(1)
|
888
|
+
node_for(key).hdel(key, fields)
|
817
889
|
end
|
818
890
|
|
819
891
|
# Determine if a hash field exists.
|
@@ -870,18 +942,22 @@ class Redis
|
|
870
942
|
|
871
943
|
# Stop listening for messages posted to the given channels.
|
872
944
|
def unsubscribe(*channels)
|
873
|
-
raise "Can't unsubscribe if not subscribed." unless subscribed?
|
945
|
+
raise SubscriptionError, "Can't unsubscribe if not subscribed." unless subscribed?
|
874
946
|
|
875
947
|
@subscribed_node.unsubscribe(*channels)
|
876
948
|
end
|
877
949
|
|
878
950
|
# Listen for messages published to channels matching the given patterns.
|
951
|
+
# See the [Redis Server PSUBSCRIBE documentation](https://redis.io/docs/latest/commands/psubscribe/)
|
952
|
+
# for further details
|
879
953
|
def psubscribe(*channels, &block)
|
880
954
|
raise NotImplementedError
|
881
955
|
end
|
882
956
|
|
883
957
|
# Stop listening for messages posted to channels matching the given
|
884
958
|
# patterns.
|
959
|
+
# See the [Redis Server PUNSUBSCRIBE documentation](https://redis.io/docs/latest/commands/punsubscribe/)
|
960
|
+
# for further details
|
885
961
|
def punsubscribe(*channels)
|
886
962
|
raise NotImplementedError
|
887
963
|
end
|
@@ -917,9 +993,7 @@ class Redis
|
|
917
993
|
def multi(&block)
|
918
994
|
raise CannotDistribute, :multi unless @watch_key
|
919
995
|
|
920
|
-
|
921
|
-
@watch_key = nil if block_given?
|
922
|
-
result
|
996
|
+
node_for(@watch_key).multi(&block)
|
923
997
|
end
|
924
998
|
|
925
999
|
# Execute all commands issued after MULTI.
|
@@ -1009,7 +1083,8 @@ class Redis
|
|
1009
1083
|
end
|
1010
1084
|
|
1011
1085
|
def key_tag(key)
|
1012
|
-
key.to_s
|
1086
|
+
key = key.to_s
|
1087
|
+
key[@tag, 1] if key.match?(@tag)
|
1013
1088
|
end
|
1014
1089
|
|
1015
1090
|
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
|