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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +93 -0
  3. data/README.md +125 -162
  4. data/lib/redis/client.rb +82 -625
  5. data/lib/redis/commands/bitmaps.rb +14 -4
  6. data/lib/redis/commands/cluster.rb +1 -18
  7. data/lib/redis/commands/connection.rb +5 -10
  8. data/lib/redis/commands/geo.rb +3 -3
  9. data/lib/redis/commands/hashes.rb +13 -6
  10. data/lib/redis/commands/hyper_log_log.rb +1 -1
  11. data/lib/redis/commands/keys.rb +75 -27
  12. data/lib/redis/commands/lists.rb +73 -23
  13. data/lib/redis/commands/pubsub.rb +34 -25
  14. data/lib/redis/commands/server.rb +15 -15
  15. data/lib/redis/commands/sets.rb +47 -36
  16. data/lib/redis/commands/sorted_sets.rb +128 -18
  17. data/lib/redis/commands/streams.rb +48 -21
  18. data/lib/redis/commands/strings.rb +18 -17
  19. data/lib/redis/commands/transactions.rb +7 -31
  20. data/lib/redis/commands.rb +11 -14
  21. data/lib/redis/distributed.rb +150 -75
  22. data/lib/redis/errors.rb +15 -50
  23. data/lib/redis/hash_ring.rb +26 -26
  24. data/lib/redis/pipeline.rb +47 -222
  25. data/lib/redis/subscribe.rb +50 -14
  26. data/lib/redis/version.rb +1 -1
  27. data/lib/redis.rb +76 -182
  28. metadata +10 -57
  29. data/lib/redis/cluster/command.rb +0 -79
  30. data/lib/redis/cluster/command_loader.rb +0 -33
  31. data/lib/redis/cluster/key_slot_converter.rb +0 -72
  32. data/lib/redis/cluster/node.rb +0 -120
  33. data/lib/redis/cluster/node_key.rb +0 -31
  34. data/lib/redis/cluster/node_loader.rb +0 -34
  35. data/lib/redis/cluster/option.rb +0 -100
  36. data/lib/redis/cluster/slot.rb +0 -86
  37. data/lib/redis/cluster/slot_loader.rb +0 -46
  38. data/lib/redis/cluster.rb +0 -315
  39. data/lib/redis/connection/command_helper.rb +0 -41
  40. data/lib/redis/connection/hiredis.rb +0 -66
  41. data/lib/redis/connection/registry.rb +0 -13
  42. data/lib/redis/connection/ruby.rb +0 -437
  43. data/lib/redis/connection/synchrony.rb +0 -148
  44. data/lib/redis/connection.rb +0 -11
@@ -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
- if !Redis.exists_returns_integer && args.size == 1
181
- ::Redis.deprecate!(
182
- "`Redis#exists(key)` will return an Integer in redis-rb 4.3, if you want to keep the old behavior, " \
183
- "use `exists?` instead. To opt-in to the new behavior now you can set Redis.exists_returns_integer = true. " \
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(*_args)
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(*_args)
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, *keys)
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
- if args.size > 1
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, keys) do |node|
484
+ ensure_same_node(cmd, args) do |node|
478
485
  if timeout
479
- node.__send__(cmd, keys, timeout: timeout)
486
+ node.__send__(cmd, args, timeout: timeout)
480
487
  else
481
- node.__send__(cmd, keys)
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, deprecated_timeout = 0, **options)
519
+ def brpoplpush(source, destination, **options)
501
520
  ensure_same_node(:brpoplpush, [source, destination]) do |node|
502
- node.brpoplpush(source, destination, deprecated_timeout, **options)
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, member)
543
- node_for(key).sadd(key, member)
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, member)
548
- node_for(key).srem(key, member)
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(*keys)
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
- ensure_same_node(:sdiffstore, [destination] + keys) do |node|
603
- node.sdiffstore(destination, *keys)
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(*keys)
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
- ensure_same_node(:sinterstore, [destination] + keys) do |node|
617
- node.sinterstore(destination, *keys)
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(*keys)
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
- ensure_same_node(:sunionstore, [destination] + keys) do |node|
631
- node.sunionstore(destination, *keys)
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(*keys, **options)
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
- ensure_same_node(:zinterstore, [destination] + keys) do |node|
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(*keys, **options)
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
- ensure_same_node(:zunionstore, [destination] + keys) do |node|
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(*keys, **options)
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, *hash.to_a.flatten)
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
- node_for(key).hmget(key, *fields)
872
+ fields.flatten!(1)
873
+ node_for(key).hmget(key, fields)
804
874
  end
805
875
 
806
876
  def mapped_hmget(key, *fields)
807
- Hash[*fields.zip(hmget(key, *fields)).flatten]
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
- node_for(key).hdel(key, *fields)
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
- result = node_for(@watch_key).multi(&block)
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[@tag, 1] if @tag
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 < RuntimeError
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 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
60
+ class SubscriptionError < BaseError
96
61
  end
97
62
  end
@@ -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 = Zlib.crc32("#{node.id}:#{i}")
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 = Zlib.crc32("#{node.id}:#{i}")
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
- get_node_pos(key)[0]
47
- end
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
- _, pos = get_node_pos(key)
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 self.binary_search(ary, value)
68
- upper = ary.size - 1
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
- if comp == 0
77
- return idx
78
- elsif comp > 0
79
- upper = idx - 1
77
+ while lower < upper
78
+ mid = (lower + upper) / 2
79
+ if ary[mid] > value
80
+ upper = mid
80
81
  else
81
- lower = idx + 1
82
+ lower = mid + 1
82
83
  end
83
84
  end
84
85
 
85
- upper = ary.size - 1 if upper < 0
86
- upper
86
+ upper - 1
87
87
  end
88
88
  end
89
89
  end