redis 4.8.1 → 5.1.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +65 -0
  3. data/README.md +101 -161
  4. data/lib/redis/client.rb +82 -616
  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 +9 -6
  10. data/lib/redis/commands/hyper_log_log.rb +1 -1
  11. data/lib/redis/commands/keys.rb +21 -23
  12. data/lib/redis/commands/lists.rb +74 -25
  13. data/lib/redis/commands/pubsub.rb +28 -25
  14. data/lib/redis/commands/server.rb +15 -15
  15. data/lib/redis/commands/sets.rb +31 -40
  16. data/lib/redis/commands/sorted_sets.rb +84 -12
  17. data/lib/redis/commands/streams.rb +39 -19
  18. data/lib/redis/commands/strings.rb +18 -17
  19. data/lib/redis/commands/transactions.rb +7 -31
  20. data/lib/redis/commands.rb +4 -7
  21. data/lib/redis/distributed.rb +128 -68
  22. data/lib/redis/errors.rb +15 -50
  23. data/lib/redis/hash_ring.rb +26 -26
  24. data/lib/redis/pipeline.rb +43 -222
  25. data/lib/redis/subscribe.rb +50 -14
  26. data/lib/redis/version.rb +1 -1
  27. data/lib/redis.rb +76 -184
  28. metadata +10 -54
  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 -68
  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
@@ -124,6 +130,11 @@ class Redis
124
130
  node_for(key).expireat(key, unix_time, **kwargs)
125
131
  end
126
132
 
133
+ # Get the expiration for a key as a UNIX timestamp.
134
+ def expiretime(key)
135
+ node_for(key).expiretime(key)
136
+ end
137
+
127
138
  # Get the time to live (in seconds) for a key.
128
139
  def ttl(key)
129
140
  node_for(key).ttl(key)
@@ -139,6 +150,11 @@ class Redis
139
150
  node_for(key).pexpireat(key, ms_unix_time, **kwarg)
140
151
  end
141
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)
156
+ end
157
+
142
158
  # Get the time to live (in milliseconds) for a key.
143
159
  def pttl(key)
144
160
  node_for(key).pttl(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,23 +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
- last_arg = args.pop
468
- ::Redis.deprecate!(
469
- "Passing the timeout as a positional argument is deprecated, it should be passed as a keyword argument:\n" \
470
- " redis.#{cmd}(#{args.map(&:inspect).join(', ')}, timeout: #{last_arg.to_int})" \
471
- "(called from: #{caller(2, 1).first})"
472
- )
473
- last_arg.to_int
474
480
  end
475
481
 
476
- keys = args.flatten
482
+ args.flatten!(1)
477
483
 
478
- ensure_same_node(cmd, keys) do |node|
484
+ ensure_same_node(cmd, args) do |node|
479
485
  if timeout
480
- node.__send__(cmd, keys, timeout: timeout)
486
+ node.__send__(cmd, args, timeout: timeout)
481
487
  else
482
- node.__send__(cmd, keys)
488
+ node.__send__(cmd, args)
483
489
  end
484
490
  end
485
491
  end
@@ -490,6 +496,18 @@ class Redis
490
496
  _bpop(:blpop, args)
491
497
  end
492
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
+
493
511
  # Remove and get the last element in a list, or block until one is
494
512
  # available.
495
513
  def brpop(*args)
@@ -498,9 +516,9 @@ class Redis
498
516
 
499
517
  # Pop a value from a list, push it to another list and return it; or block
500
518
  # until one is available.
501
- def brpoplpush(source, destination, deprecated_timeout = 0, **options)
519
+ def brpoplpush(source, destination, **options)
502
520
  ensure_same_node(:brpoplpush, [source, destination]) do |node|
503
- node.brpoplpush(source, destination, deprecated_timeout, **options)
521
+ node.brpoplpush(source, destination, **options)
504
522
  end
505
523
  end
506
524
 
@@ -534,29 +552,43 @@ class Redis
534
552
  node_for(key).ltrim(key, start, stop)
535
553
  end
536
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
+
537
569
  # Get the number of members in a set.
538
570
  def scard(key)
539
571
  node_for(key).scard(key)
540
572
  end
541
573
 
542
574
  # Add one or more members to a set.
543
- def sadd(key, member)
544
- node_for(key).sadd(key, member)
575
+ def sadd(key, *members)
576
+ node_for(key).sadd(key, *members)
545
577
  end
546
578
 
547
579
  # Add one or more members to a set.
548
- def sadd?(key, member)
549
- node_for(key).sadd?(key, member)
580
+ def sadd?(key, *members)
581
+ node_for(key).sadd?(key, *members)
550
582
  end
551
583
 
552
584
  # Remove one or more members from a set.
553
- def srem(key, member)
554
- node_for(key).srem(key, member)
585
+ def srem(key, *members)
586
+ node_for(key).srem(key, *members)
555
587
  end
556
588
 
557
589
  # Remove one or more members from a set.
558
- def srem?(key, member)
559
- node_for(key).srem?(key, member)
590
+ def srem?(key, *members)
591
+ node_for(key).srem?(key, *members)
560
592
  end
561
593
 
562
594
  # Remove and return a random member from a set.
@@ -603,43 +635,49 @@ class Redis
603
635
 
604
636
  # Subtract multiple sets.
605
637
  def sdiff(*keys)
638
+ keys.flatten!(1)
606
639
  ensure_same_node(:sdiff, keys) do |node|
607
- node.sdiff(*keys)
640
+ node.sdiff(keys)
608
641
  end
609
642
  end
610
643
 
611
644
  # Subtract multiple sets and store the resulting set in a key.
612
645
  def sdiffstore(destination, *keys)
613
- ensure_same_node(:sdiffstore, [destination] + keys) do |node|
614
- node.sdiffstore(destination, *keys)
646
+ keys.flatten!(1)
647
+ ensure_same_node(:sdiffstore, [destination].concat(keys)) do |node|
648
+ node.sdiffstore(destination, keys)
615
649
  end
616
650
  end
617
651
 
618
652
  # Intersect multiple sets.
619
653
  def sinter(*keys)
654
+ keys.flatten!(1)
620
655
  ensure_same_node(:sinter, keys) do |node|
621
- node.sinter(*keys)
656
+ node.sinter(keys)
622
657
  end
623
658
  end
624
659
 
625
660
  # Intersect multiple sets and store the resulting set in a key.
626
661
  def sinterstore(destination, *keys)
627
- ensure_same_node(:sinterstore, [destination] + keys) do |node|
628
- node.sinterstore(destination, *keys)
662
+ keys.flatten!(1)
663
+ ensure_same_node(:sinterstore, [destination].concat(keys)) do |node|
664
+ node.sinterstore(destination, keys)
629
665
  end
630
666
  end
631
667
 
632
668
  # Add multiple sets.
633
669
  def sunion(*keys)
670
+ keys.flatten!(1)
634
671
  ensure_same_node(:sunion, keys) do |node|
635
- node.sunion(*keys)
672
+ node.sunion(keys)
636
673
  end
637
674
  end
638
675
 
639
676
  # Add multiple sets and store the resulting set in a key.
640
677
  def sunionstore(destination, *keys)
641
- ensure_same_node(:sunionstore, [destination] + keys) do |node|
642
- node.sunionstore(destination, *keys)
678
+ keys.flatten!(1)
679
+ ensure_same_node(:sunionstore, [destination].concat(keys)) do |node|
680
+ node.sunionstore(destination, keys)
643
681
  end
644
682
  end
645
683
 
@@ -680,6 +718,20 @@ class Redis
680
718
  node_for(key).zmscore(key, *members)
681
719
  end
682
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
+
683
735
  # Return a range of members in a sorted set, by index, score or lexicographical ordering.
684
736
  def zrange(key, start, stop, **options)
685
737
  node_for(key).zrange(key, start, stop, **options)
@@ -738,43 +790,49 @@ class Redis
738
790
 
739
791
  # Get the intersection of multiple sorted sets
740
792
  def zinter(*keys, **options)
793
+ keys.flatten!(1)
741
794
  ensure_same_node(:zinter, keys) do |node|
742
- node.zinter(*keys, **options)
795
+ node.zinter(keys, **options)
743
796
  end
744
797
  end
745
798
 
746
799
  # Intersect multiple sorted sets and store the resulting sorted set in a new
747
800
  # key.
748
- def zinterstore(destination, keys, **options)
749
- 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|
750
804
  node.zinterstore(destination, keys, **options)
751
805
  end
752
806
  end
753
807
 
754
808
  # Return the union of multiple sorted sets.
755
809
  def zunion(*keys, **options)
810
+ keys.flatten!(1)
756
811
  ensure_same_node(:zunion, keys) do |node|
757
- node.zunion(*keys, **options)
812
+ node.zunion(keys, **options)
758
813
  end
759
814
  end
760
815
 
761
816
  # Add multiple sorted sets and store the resulting sorted set in a new key.
762
- def zunionstore(destination, keys, **options)
763
- 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|
764
820
  node.zunionstore(destination, keys, **options)
765
821
  end
766
822
  end
767
823
 
768
824
  # Return the difference between the first and all successive input sorted sets.
769
825
  def zdiff(*keys, **options)
826
+ keys.flatten!(1)
770
827
  ensure_same_node(:zdiff, keys) do |node|
771
- node.zdiff(*keys, **options)
828
+ node.zdiff(keys, **options)
772
829
  end
773
830
  end
774
831
 
775
832
  # Compute the difference between the first and all successive input sorted sets
776
833
  # and store the resulting sorted set in a new key.
777
- def zdiffstore(destination, keys, **options)
834
+ def zdiffstore(destination, *keys, **options)
835
+ keys.flatten!(1)
778
836
  ensure_same_node(:zdiffstore, [destination] + keys) do |node|
779
837
  node.zdiffstore(destination, keys, **options)
780
838
  end
@@ -801,7 +859,7 @@ class Redis
801
859
  end
802
860
 
803
861
  def mapped_hmset(key, hash)
804
- node_for(key).hmset(key, *hash.to_a.flatten)
862
+ node_for(key).hmset(key, hash)
805
863
  end
806
864
 
807
865
  # Get the value of a hash field.
@@ -811,11 +869,13 @@ class Redis
811
869
 
812
870
  # Get the values of all the given hash fields.
813
871
  def hmget(key, *fields)
814
- node_for(key).hmget(key, *fields)
872
+ fields.flatten!(1)
873
+ node_for(key).hmget(key, fields)
815
874
  end
816
875
 
817
876
  def mapped_hmget(key, *fields)
818
- Hash[*fields.zip(hmget(key, *fields)).flatten]
877
+ fields.flatten!(1)
878
+ node_for(key).mapped_hmget(key, fields)
819
879
  end
820
880
 
821
881
  def hrandfield(key, count = nil, **options)
@@ -824,7 +884,8 @@ class Redis
824
884
 
825
885
  # Delete one or more hash fields.
826
886
  def hdel(key, *fields)
827
- node_for(key).hdel(key, *fields)
887
+ fields.flatten!(1)
888
+ node_for(key).hdel(key, fields)
828
889
  end
829
890
 
830
891
  # Determine if a hash field exists.
@@ -881,7 +942,7 @@ class Redis
881
942
 
882
943
  # Stop listening for messages posted to the given channels.
883
944
  def unsubscribe(*channels)
884
- raise "Can't unsubscribe if not subscribed." unless subscribed?
945
+ raise SubscriptionError, "Can't unsubscribe if not subscribed." unless subscribed?
885
946
 
886
947
  @subscribed_node.unsubscribe(*channels)
887
948
  end
@@ -928,9 +989,7 @@ class Redis
928
989
  def multi(&block)
929
990
  raise CannotDistribute, :multi unless @watch_key
930
991
 
931
- result = node_for(@watch_key).multi(&block)
932
- @watch_key = nil if block_given?
933
- result
992
+ node_for(@watch_key).multi(&block)
934
993
  end
935
994
 
936
995
  # Execute all commands issued after MULTI.
@@ -1020,7 +1079,8 @@ class Redis
1020
1079
  end
1021
1080
 
1022
1081
  def key_tag(key)
1023
- key.to_s[@tag, 1] if @tag
1082
+ key = key.to_s
1083
+ key[@tag, 1] if key.match?(@tag)
1024
1084
  end
1025
1085
 
1026
1086
  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