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.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +75 -0
  3. data/README.md +101 -161
  4. data/lib/redis/client.rb +82 -625
  5. data/lib/redis/commands/bitmaps.rb +4 -1
  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 +53 -27
  12. data/lib/redis/commands/lists.rb +73 -23
  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 +43 -36
  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 -9
  21. data/lib/redis/distributed.rb +128 -67
  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 +75 -182
  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 -66
  41. data/lib/redis/connection/registry.rb +0 -13
  42. data/lib/redis/connection/ruby.rb +0 -431
  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,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
- 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
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(*_args)
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(*_args)
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, *keys)
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
- keys = args.flatten
472
+ args.flatten!(1)
476
473
 
477
- ensure_same_node(cmd, keys) do |node|
474
+ ensure_same_node(cmd, args) do |node|
478
475
  if timeout
479
- node.__send__(cmd, keys, timeout: timeout)
476
+ node.__send__(cmd, args, timeout: timeout)
480
477
  else
481
- node.__send__(cmd, keys)
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, deprecated_timeout = 0, **options)
509
+ def brpoplpush(source, destination, **options)
501
510
  ensure_same_node(:brpoplpush, [source, destination]) do |node|
502
- node.brpoplpush(source, destination, deprecated_timeout, **options)
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, member)
543
- node_for(key).sadd(key, member)
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, member)
548
- node_for(key).srem(key, member)
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(*keys)
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
- ensure_same_node(:sdiffstore, [destination] + keys) do |node|
603
- node.sdiffstore(destination, *keys)
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(*keys)
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
- ensure_same_node(:sinterstore, [destination] + keys) do |node|
617
- node.sinterstore(destination, *keys)
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(*keys)
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
- ensure_same_node(:sunionstore, [destination] + keys) do |node|
631
- node.sunionstore(destination, *keys)
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(*keys, **options)
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
- ensure_same_node(:zinterstore, [destination] + keys) do |node|
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(*keys, **options)
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
- ensure_same_node(:zunionstore, [destination] + keys) do |node|
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(*keys, **options)
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, *hash.to_a.flatten)
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
- node_for(key).hmget(key, *fields)
862
+ fields.flatten!(1)
863
+ node_for(key).hmget(key, fields)
804
864
  end
805
865
 
806
866
  def mapped_hmget(key, *fields)
807
- Hash[*fields.zip(hmget(key, *fields)).flatten]
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
- node_for(key).hdel(key, *fields)
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
- result = node_for(@watch_key).multi(&block)
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[@tag, 1] if @tag
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 < 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