redis 4.7.1 → 5.0.0.beta3

Sign up to get free protection for your applications and to get access to all the features.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +21 -0
  3. data/README.md +77 -161
  4. data/lib/redis/client.rb +78 -615
  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/hashes.rb +6 -3
  9. data/lib/redis/commands/hyper_log_log.rb +1 -1
  10. data/lib/redis/commands/keys.rb +52 -26
  11. data/lib/redis/commands/lists.rb +10 -14
  12. data/lib/redis/commands/pubsub.rb +7 -9
  13. data/lib/redis/commands/server.rb +14 -14
  14. data/lib/redis/commands/sets.rb +42 -35
  15. data/lib/redis/commands/sorted_sets.rb +4 -2
  16. data/lib/redis/commands/streams.rb +12 -10
  17. data/lib/redis/commands/strings.rb +1 -0
  18. data/lib/redis/commands/transactions.rb +7 -31
  19. data/lib/redis/commands.rb +1 -8
  20. data/lib/redis/distributed.rb +99 -66
  21. data/lib/redis/errors.rb +10 -52
  22. data/lib/redis/hash_ring.rb +26 -26
  23. data/lib/redis/pipeline.rb +43 -222
  24. data/lib/redis/subscribe.rb +15 -9
  25. data/lib/redis/version.rb +1 -1
  26. data/lib/redis.rb +67 -179
  27. metadata +13 -57
  28. data/lib/redis/cluster/command.rb +0 -79
  29. data/lib/redis/cluster/command_loader.rb +0 -33
  30. data/lib/redis/cluster/key_slot_converter.rb +0 -72
  31. data/lib/redis/cluster/node.rb +0 -120
  32. data/lib/redis/cluster/node_key.rb +0 -31
  33. data/lib/redis/cluster/node_loader.rb +0 -34
  34. data/lib/redis/cluster/option.rb +0 -100
  35. data/lib/redis/cluster/slot.rb +0 -86
  36. data/lib/redis/cluster/slot_loader.rb +0 -46
  37. data/lib/redis/cluster.rb +0 -315
  38. data/lib/redis/connection/command_helper.rb +0 -41
  39. data/lib/redis/connection/hiredis.rb +0 -66
  40. data/lib/redis/connection/registry.rb +0 -13
  41. data/lib/redis/connection/ruby.rb +0 -437
  42. data/lib/redis/connection/synchrony.rb +0 -148
  43. data/lib/redis/connection.rb +0 -11
@@ -5,48 +5,26 @@ class Redis
5
5
  module Transactions
6
6
  # Mark the start of a transaction block.
7
7
  #
8
- # Passing a block is optional.
9
- #
10
8
  # @example With a block
11
9
  # redis.multi do |multi|
12
10
  # multi.set("key", "value")
13
11
  # multi.incr("counter")
14
12
  # end # => ["OK", 6]
15
13
  #
16
- # @example Without a block
17
- # redis.multi
18
- # # => "OK"
19
- # redis.set("key", "value")
20
- # # => "QUEUED"
21
- # redis.incr("counter")
22
- # # => "QUEUED"
23
- # redis.exec
24
- # # => ["OK", 6]
25
- #
26
14
  # @yield [multi] the commands that are called inside this block are cached
27
15
  # and written to the server upon returning from it
28
16
  # @yieldparam [Redis] multi `self`
29
17
  #
30
- # @return [String, Array<...>]
31
- # - when a block is not given, `OK`
32
- # - when a block is given, an array with replies
18
+ # @return [Array<...>]
19
+ # - an array with replies
33
20
  #
34
21
  # @see #watch
35
22
  # @see #unwatch
36
- def multi(&block) # :nodoc:
37
- if block_given?
38
- if block&.arity == 0
39
- Pipeline.deprecation_warning("multi", Kernel.caller_locations(1, 5))
40
- end
41
-
42
- synchronize do |prior_client|
43
- pipeline = Pipeline::Multi.new(prior_client)
44
- pipelined_connection = PipelinedConnection.new(pipeline)
45
- yield pipelined_connection
46
- prior_client.call_pipeline(pipeline)
23
+ def multi
24
+ synchronize do |client|
25
+ client.multi do |raw_transaction|
26
+ yield MultiConnection.new(raw_transaction)
47
27
  end
48
- else
49
- send_command([:multi])
50
28
  end
51
29
  end
52
30
 
@@ -82,7 +60,7 @@ class Redis
82
60
  # @see #multi
83
61
  def watch(*keys)
84
62
  synchronize do |client|
85
- res = client.call([:watch, *keys])
63
+ res = client.call_v([:watch] + keys)
86
64
 
87
65
  if block_given?
88
66
  begin
@@ -125,8 +103,6 @@ class Redis
125
103
 
126
104
  # Discard all commands issued after MULTI.
127
105
  #
128
- # Only call this method when `#multi` was called **without** a block.
129
- #
130
106
  # @return [String] `"OK"`
131
107
  #
132
108
  # @see #multi
@@ -40,14 +40,7 @@ class Redis
40
40
  # where the method call will return nil. Propagate the nil instead of falsely
41
41
  # returning false.
42
42
  Boolify = lambda { |value|
43
- case value
44
- when 1
45
- true
46
- when 0
47
- false
48
- else
49
- value
50
- end
43
+ value != 0 unless value.nil?
51
44
  }
52
45
 
53
46
  BoolifySet = lambda { |value|
@@ -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
470
  end
470
471
 
471
- if args.size > 1
472
- # Issue deprecation notice in obnoxious mode...
473
- end
474
-
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
 
@@ -539,13 +548,23 @@ class Redis
539
548
  end
540
549
 
541
550
  # Add one or more members to a set.
542
- def sadd(key, member)
543
- node_for(key).sadd(key, member)
551
+ def sadd(key, *members)
552
+ node_for(key).sadd(key, *members)
553
+ end
554
+
555
+ # Add one or more members to a set.
556
+ def sadd?(key, *members)
557
+ node_for(key).sadd?(key, *members)
544
558
  end
545
559
 
546
560
  # Remove one or more members from a set.
547
- def srem(key, member)
548
- node_for(key).srem(key, member)
561
+ def srem(key, *members)
562
+ node_for(key).srem(key, *members)
563
+ end
564
+
565
+ # Remove one or more members from a set.
566
+ def srem?(key, *members)
567
+ node_for(key).srem?(key, *members)
549
568
  end
550
569
 
551
570
  # Remove and return a random member from a set.
@@ -592,43 +611,49 @@ class Redis
592
611
 
593
612
  # Subtract multiple sets.
594
613
  def sdiff(*keys)
614
+ keys.flatten!(1)
595
615
  ensure_same_node(:sdiff, keys) do |node|
596
- node.sdiff(*keys)
616
+ node.sdiff(keys)
597
617
  end
598
618
  end
599
619
 
600
620
  # Subtract multiple sets and store the resulting set in a key.
601
621
  def sdiffstore(destination, *keys)
602
- ensure_same_node(:sdiffstore, [destination] + keys) do |node|
603
- node.sdiffstore(destination, *keys)
622
+ keys.flatten!(1)
623
+ ensure_same_node(:sdiffstore, [destination].concat(keys)) do |node|
624
+ node.sdiffstore(destination, keys)
604
625
  end
605
626
  end
606
627
 
607
628
  # Intersect multiple sets.
608
629
  def sinter(*keys)
630
+ keys.flatten!(1)
609
631
  ensure_same_node(:sinter, keys) do |node|
610
- node.sinter(*keys)
632
+ node.sinter(keys)
611
633
  end
612
634
  end
613
635
 
614
636
  # Intersect multiple sets and store the resulting set in a key.
615
637
  def sinterstore(destination, *keys)
616
- ensure_same_node(:sinterstore, [destination] + keys) do |node|
617
- node.sinterstore(destination, *keys)
638
+ keys.flatten!(1)
639
+ ensure_same_node(:sinterstore, [destination].concat(keys)) do |node|
640
+ node.sinterstore(destination, keys)
618
641
  end
619
642
  end
620
643
 
621
644
  # Add multiple sets.
622
645
  def sunion(*keys)
646
+ keys.flatten!(1)
623
647
  ensure_same_node(:sunion, keys) do |node|
624
- node.sunion(*keys)
648
+ node.sunion(keys)
625
649
  end
626
650
  end
627
651
 
628
652
  # Add multiple sets and store the resulting set in a key.
629
653
  def sunionstore(destination, *keys)
630
- ensure_same_node(:sunionstore, [destination] + keys) do |node|
631
- node.sunionstore(destination, *keys)
654
+ keys.flatten!(1)
655
+ ensure_same_node(:sunionstore, [destination].concat(keys)) do |node|
656
+ node.sunionstore(destination, keys)
632
657
  end
633
658
  end
634
659
 
@@ -727,43 +752,49 @@ class Redis
727
752
 
728
753
  # Get the intersection of multiple sorted sets
729
754
  def zinter(*keys, **options)
755
+ keys.flatten!(1)
730
756
  ensure_same_node(:zinter, keys) do |node|
731
- node.zinter(*keys, **options)
757
+ node.zinter(keys, **options)
732
758
  end
733
759
  end
734
760
 
735
761
  # Intersect multiple sorted sets and store the resulting sorted set in a new
736
762
  # key.
737
- def zinterstore(destination, keys, **options)
738
- ensure_same_node(:zinterstore, [destination] + keys) do |node|
763
+ def zinterstore(destination, *keys, **options)
764
+ keys.flatten!(1)
765
+ ensure_same_node(:zinterstore, [destination].concat(keys)) do |node|
739
766
  node.zinterstore(destination, keys, **options)
740
767
  end
741
768
  end
742
769
 
743
770
  # Return the union of multiple sorted sets.
744
771
  def zunion(*keys, **options)
772
+ keys.flatten!(1)
745
773
  ensure_same_node(:zunion, keys) do |node|
746
- node.zunion(*keys, **options)
774
+ node.zunion(keys, **options)
747
775
  end
748
776
  end
749
777
 
750
778
  # 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|
779
+ def zunionstore(destination, *keys, **options)
780
+ keys.flatten!(1)
781
+ ensure_same_node(:zunionstore, [destination].concat(keys)) do |node|
753
782
  node.zunionstore(destination, keys, **options)
754
783
  end
755
784
  end
756
785
 
757
786
  # Return the difference between the first and all successive input sorted sets.
758
787
  def zdiff(*keys, **options)
788
+ keys.flatten!(1)
759
789
  ensure_same_node(:zdiff, keys) do |node|
760
- node.zdiff(*keys, **options)
790
+ node.zdiff(keys, **options)
761
791
  end
762
792
  end
763
793
 
764
794
  # Compute the difference between the first and all successive input sorted sets
765
795
  # and store the resulting sorted set in a new key.
766
- def zdiffstore(destination, keys, **options)
796
+ def zdiffstore(destination, *keys, **options)
797
+ keys.flatten!(1)
767
798
  ensure_same_node(:zdiffstore, [destination] + keys) do |node|
768
799
  node.zdiffstore(destination, keys, **options)
769
800
  end
@@ -790,7 +821,7 @@ class Redis
790
821
  end
791
822
 
792
823
  def mapped_hmset(key, hash)
793
- node_for(key).hmset(key, *hash.to_a.flatten)
824
+ node_for(key).hmset(key, hash)
794
825
  end
795
826
 
796
827
  # Get the value of a hash field.
@@ -800,11 +831,13 @@ class Redis
800
831
 
801
832
  # Get the values of all the given hash fields.
802
833
  def hmget(key, *fields)
803
- node_for(key).hmget(key, *fields)
834
+ fields.flatten!(1)
835
+ node_for(key).hmget(key, fields)
804
836
  end
805
837
 
806
838
  def mapped_hmget(key, *fields)
807
- Hash[*fields.zip(hmget(key, *fields)).flatten]
839
+ fields.flatten!(1)
840
+ node_for(key).mapped_hmget(key, fields)
808
841
  end
809
842
 
810
843
  def hrandfield(key, count = nil, **options)
@@ -813,7 +846,8 @@ class Redis
813
846
 
814
847
  # Delete one or more hash fields.
815
848
  def hdel(key, *fields)
816
- node_for(key).hdel(key, *fields)
849
+ fields.flatten!(1)
850
+ node_for(key).hdel(key, fields)
817
851
  end
818
852
 
819
853
  # Determine if a hash field exists.
@@ -917,9 +951,7 @@ class Redis
917
951
  def multi(&block)
918
952
  raise CannotDistribute, :multi unless @watch_key
919
953
 
920
- result = node_for(@watch_key).multi(&block)
921
- @watch_key = nil if block_given?
922
- result
954
+ node_for(@watch_key).multi(&block)
923
955
  end
924
956
 
925
957
  # Execute all commands issued after MULTI.
@@ -1009,7 +1041,8 @@ class Redis
1009
1041
  end
1010
1042
 
1011
1043
  def key_tag(key)
1012
- key.to_s[@tag, 1] if @tag
1044
+ key = key.to_s
1045
+ key[@tag, 1] if key.match?(@tag)
1013
1046
  end
1014
1047
 
1015
1048
  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 ReadOnlyError < CommandError
30
+ end
31
+
23
32
  # Base error for connection related errors.
24
33
  class BaseConnectionError < BaseError
25
34
  end
@@ -43,55 +52,4 @@ class Redis
43
52
  # Raised when client options are invalid.
44
53
  class InvalidClientOptionError < BaseError
45
54
  end
46
-
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
96
- end
97
55
  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