redis 3.0.0 → 4.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +315 -0
- data/README.md +301 -58
- data/lib/redis/client.rb +383 -88
- data/lib/redis/cluster/command.rb +81 -0
- data/lib/redis/cluster/command_loader.rb +33 -0
- data/lib/redis/cluster/key_slot_converter.rb +72 -0
- data/lib/redis/cluster/node.rb +108 -0
- data/lib/redis/cluster/node_key.rb +31 -0
- data/lib/redis/cluster/node_loader.rb +37 -0
- data/lib/redis/cluster/option.rb +93 -0
- data/lib/redis/cluster/slot.rb +86 -0
- data/lib/redis/cluster/slot_loader.rb +49 -0
- data/lib/redis/cluster.rb +291 -0
- data/lib/redis/connection/command_helper.rb +7 -10
- data/lib/redis/connection/hiredis.rb +12 -8
- data/lib/redis/connection/registry.rb +2 -1
- data/lib/redis/connection/ruby.rb +266 -74
- data/lib/redis/connection/synchrony.rb +41 -14
- data/lib/redis/connection.rb +4 -2
- data/lib/redis/distributed.rb +258 -76
- data/lib/redis/errors.rb +48 -0
- data/lib/redis/hash_ring.rb +31 -73
- data/lib/redis/pipeline.rb +74 -18
- data/lib/redis/subscribe.rb +24 -13
- data/lib/redis/version.rb +3 -1
- data/lib/redis.rb +2068 -464
- metadata +63 -160
- data/.gitignore +0 -10
- data/.order +0 -169
- data/.travis/Gemfile +0 -11
- data/.travis.yml +0 -50
- data/.yardopts +0 -3
- data/Rakefile +0 -392
- data/benchmarking/logging.rb +0 -62
- data/benchmarking/pipeline.rb +0 -51
- data/benchmarking/speed.rb +0 -21
- data/benchmarking/suite.rb +0 -24
- data/benchmarking/worker.rb +0 -71
- data/examples/basic.rb +0 -15
- data/examples/dist_redis.rb +0 -43
- data/examples/incr-decr.rb +0 -17
- data/examples/list.rb +0 -26
- data/examples/pubsub.rb +0 -31
- data/examples/sets.rb +0 -36
- data/examples/unicorn/config.ru +0 -3
- data/examples/unicorn/unicorn.rb +0 -20
- data/redis.gemspec +0 -41
- data/test/blocking_commands_test.rb +0 -42
- data/test/command_map_test.rb +0 -30
- data/test/commands_on_hashes_test.rb +0 -21
- data/test/commands_on_lists_test.rb +0 -20
- data/test/commands_on_sets_test.rb +0 -77
- data/test/commands_on_sorted_sets_test.rb +0 -109
- data/test/commands_on_strings_test.rb +0 -83
- data/test/commands_on_value_types_test.rb +0 -99
- data/test/connection_handling_test.rb +0 -189
- data/test/db/.gitignore +0 -1
- data/test/distributed_blocking_commands_test.rb +0 -46
- data/test/distributed_commands_on_hashes_test.rb +0 -10
- data/test/distributed_commands_on_lists_test.rb +0 -22
- data/test/distributed_commands_on_sets_test.rb +0 -83
- data/test/distributed_commands_on_sorted_sets_test.rb +0 -18
- data/test/distributed_commands_on_strings_test.rb +0 -48
- data/test/distributed_commands_on_value_types_test.rb +0 -87
- data/test/distributed_commands_requiring_clustering_test.rb +0 -148
- data/test/distributed_connection_handling_test.rb +0 -23
- data/test/distributed_internals_test.rb +0 -15
- data/test/distributed_key_tags_test.rb +0 -52
- data/test/distributed_persistence_control_commands_test.rb +0 -26
- data/test/distributed_publish_subscribe_test.rb +0 -92
- data/test/distributed_remote_server_control_commands_test.rb +0 -53
- data/test/distributed_scripting_test.rb +0 -102
- data/test/distributed_sorting_test.rb +0 -20
- data/test/distributed_test.rb +0 -58
- data/test/distributed_transactions_test.rb +0 -32
- data/test/encoding_test.rb +0 -18
- data/test/error_replies_test.rb +0 -59
- data/test/helper.rb +0 -188
- data/test/helper_test.rb +0 -22
- data/test/internals_test.rb +0 -214
- data/test/lint/blocking_commands.rb +0 -124
- data/test/lint/hashes.rb +0 -162
- data/test/lint/lists.rb +0 -143
- data/test/lint/sets.rb +0 -96
- data/test/lint/sorted_sets.rb +0 -201
- data/test/lint/strings.rb +0 -157
- data/test/lint/value_types.rb +0 -106
- data/test/persistence_control_commands_test.rb +0 -26
- data/test/pipelining_commands_test.rb +0 -195
- data/test/publish_subscribe_test.rb +0 -153
- data/test/remote_server_control_commands_test.rb +0 -104
- data/test/scripting_test.rb +0 -78
- data/test/sorting_test.rb +0 -45
- data/test/support/connection/hiredis.rb +0 -1
- data/test/support/connection/ruby.rb +0 -1
- data/test/support/connection/synchrony.rb +0 -17
- data/test/support/redis_mock.rb +0 -92
- data/test/support/wire/synchrony.rb +0 -24
- data/test/support/wire/thread.rb +0 -5
- data/test/synchrony_driver.rb +0 -57
- data/test/test.conf +0 -9
- data/test/thread_safety_test.rb +0 -32
- data/test/transactions_test.rb +0 -244
- data/test/unknown_commands_test.rb +0 -14
- data/test/url_param_test.rb +0 -64
data/lib/redis/hash_ring.rb
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'zlib'
|
2
4
|
|
3
5
|
class Redis
|
4
6
|
class HashRing
|
5
|
-
|
6
7
|
POINTS_PER_SERVER = 160 # this is the default in libmemcached
|
7
8
|
|
8
9
|
attr_reader :ring, :sorted_keys, :replicas, :nodes
|
@@ -10,7 +11,7 @@ class Redis
|
|
10
11
|
# nodes is a list of objects that have a proper to_s representation.
|
11
12
|
# replicas indicates how many virtual points should be used pr. node,
|
12
13
|
# replicas are required to improve the distribution.
|
13
|
-
def initialize(nodes=[], replicas=POINTS_PER_SERVER)
|
14
|
+
def initialize(nodes = [], replicas = POINTS_PER_SERVER)
|
14
15
|
@replicas = replicas
|
15
16
|
@ring = {}
|
16
17
|
@nodes = []
|
@@ -32,11 +33,11 @@ class Redis
|
|
32
33
|
end
|
33
34
|
|
34
35
|
def remove_node(node)
|
35
|
-
@nodes.reject!{|n| n.id == node.id}
|
36
|
+
@nodes.reject! { |n| n.id == node.id }
|
36
37
|
@replicas.times do |i|
|
37
38
|
key = Zlib.crc32("#{node.id}:#{i}")
|
38
39
|
@ring.delete(key)
|
39
|
-
@sorted_keys.reject! {|k| k == key}
|
40
|
+
@sorted_keys.reject! { |k| k == key }
|
40
41
|
end
|
41
42
|
end
|
42
43
|
|
@@ -46,86 +47,43 @@ class Redis
|
|
46
47
|
end
|
47
48
|
|
48
49
|
def get_node_pos(key)
|
49
|
-
return [nil,nil] if @ring.
|
50
|
+
return [nil, nil] if @ring.empty?
|
51
|
+
|
50
52
|
crc = Zlib.crc32(key)
|
51
53
|
idx = HashRing.binary_search(@sorted_keys, crc)
|
52
|
-
|
54
|
+
[@ring[@sorted_keys[idx]], idx]
|
53
55
|
end
|
54
56
|
|
55
57
|
def iter_nodes(key)
|
56
|
-
return [nil,nil] if @ring.
|
58
|
+
return [nil, nil] if @ring.empty?
|
59
|
+
|
57
60
|
_, pos = get_node_pos(key)
|
58
|
-
@
|
59
|
-
yield @ring[
|
61
|
+
@ring.size.times do |n|
|
62
|
+
yield @ring[@sorted_keys[(pos + n) % @ring.size]]
|
60
63
|
end
|
61
64
|
end
|
62
65
|
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
78
|
-
|
79
|
-
while (lower <= upper) {
|
80
|
-
idx = (lower + upper) / 2;
|
81
|
-
|
82
|
-
VALUE continuumValue = RARRAY_PTR(ary)[idx];
|
83
|
-
unsigned int l = NUM2UINT(continuumValue);
|
84
|
-
if (l == r) {
|
85
|
-
return idx;
|
86
|
-
}
|
87
|
-
else if (l > r) {
|
88
|
-
upper = idx - 1;
|
89
|
-
}
|
90
|
-
else {
|
91
|
-
lower = idx + 1;
|
92
|
-
}
|
93
|
-
}
|
94
|
-
if (upper < 0) {
|
95
|
-
upper = RARRAY_LEN(ary) - 1;
|
96
|
-
}
|
97
|
-
return upper;
|
98
|
-
}
|
99
|
-
EOM
|
66
|
+
# Find the closest index in HashRing with value <= the given value
|
67
|
+
def self.binary_search(ary, value)
|
68
|
+
upper = ary.size - 1
|
69
|
+
lower = 0
|
70
|
+
idx = 0
|
71
|
+
|
72
|
+
while lower <= upper
|
73
|
+
idx = (lower + upper) / 2
|
74
|
+
comp = ary[idx] <=> value
|
75
|
+
|
76
|
+
if comp == 0
|
77
|
+
return idx
|
78
|
+
elsif comp > 0
|
79
|
+
upper = idx - 1
|
80
|
+
else
|
81
|
+
lower = idx + 1
|
100
82
|
end
|
101
|
-
rescue Exception
|
102
|
-
# Find the closest index in HashRing with value <= the given value
|
103
|
-
def binary_search(ary, value, &block)
|
104
|
-
upper = ary.size - 1
|
105
|
-
lower = 0
|
106
|
-
idx = 0
|
107
|
-
|
108
|
-
while(lower <= upper) do
|
109
|
-
idx = (lower + upper) / 2
|
110
|
-
comp = ary[idx] <=> value
|
111
|
-
|
112
|
-
if comp == 0
|
113
|
-
return idx
|
114
|
-
elsif comp > 0
|
115
|
-
upper = idx - 1
|
116
|
-
else
|
117
|
-
lower = idx + 1
|
118
|
-
end
|
119
|
-
end
|
120
|
-
|
121
|
-
if upper < 0
|
122
|
-
upper = ary.size - 1
|
123
|
-
end
|
124
|
-
return upper
|
125
|
-
end
|
126
|
-
|
127
83
|
end
|
128
|
-
end
|
129
84
|
|
85
|
+
upper = ary.size - 1 if upper < 0
|
86
|
+
upper
|
87
|
+
end
|
130
88
|
end
|
131
89
|
end
|
data/lib/redis/pipeline.rb
CHANGED
@@ -1,19 +1,23 @@
|
|
1
|
-
|
2
|
-
unless defined?(::BasicObject)
|
3
|
-
class BasicObject
|
4
|
-
instance_methods.each { |meth| undef_method(meth) unless meth =~ /\A(__|instance_eval)/ }
|
5
|
-
end
|
6
|
-
end
|
1
|
+
# frozen_string_literal: true
|
7
2
|
|
3
|
+
class Redis
|
8
4
|
class Pipeline
|
5
|
+
attr_accessor :db
|
6
|
+
attr_reader :client
|
7
|
+
|
9
8
|
attr :futures
|
10
9
|
|
11
|
-
def initialize
|
10
|
+
def initialize(client)
|
11
|
+
@client = client.is_a?(Pipeline) ? client.client : client
|
12
12
|
@with_reconnect = true
|
13
13
|
@shutdown = false
|
14
14
|
@futures = []
|
15
15
|
end
|
16
16
|
|
17
|
+
def timeout
|
18
|
+
client.timeout
|
19
|
+
end
|
20
|
+
|
17
21
|
def with_reconnect?
|
18
22
|
@with_reconnect
|
19
23
|
end
|
@@ -26,26 +30,39 @@ class Redis
|
|
26
30
|
@shutdown
|
27
31
|
end
|
28
32
|
|
29
|
-
def
|
33
|
+
def empty?
|
34
|
+
@futures.empty?
|
35
|
+
end
|
36
|
+
|
37
|
+
def call(command, timeout: nil, &block)
|
30
38
|
# A pipeline that contains a shutdown should not raise ECONNRESET when
|
31
39
|
# the connection is gone.
|
32
40
|
@shutdown = true if command.first == :shutdown
|
33
|
-
future = Future.new(command, block)
|
41
|
+
future = Future.new(command, block, timeout)
|
34
42
|
@futures << future
|
35
43
|
future
|
36
44
|
end
|
37
45
|
|
46
|
+
def call_with_timeout(command, timeout, &block)
|
47
|
+
call(command, timeout: timeout, &block)
|
48
|
+
end
|
49
|
+
|
38
50
|
def call_pipeline(pipeline)
|
39
51
|
@shutdown = true if pipeline.shutdown?
|
40
52
|
@futures.concat(pipeline.futures)
|
53
|
+
@db = pipeline.db
|
41
54
|
nil
|
42
55
|
end
|
43
56
|
|
44
57
|
def commands
|
45
|
-
@futures.map
|
58
|
+
@futures.map(&:_command)
|
46
59
|
end
|
47
60
|
|
48
|
-
def
|
61
|
+
def timeouts
|
62
|
+
@futures.map(&:timeout)
|
63
|
+
end
|
64
|
+
|
65
|
+
def with_reconnect(val = true)
|
49
66
|
@with_reconnect = false unless val
|
50
67
|
yield
|
51
68
|
end
|
@@ -68,14 +85,20 @@ class Redis
|
|
68
85
|
|
69
86
|
class Multi < self
|
70
87
|
def finish(replies)
|
71
|
-
|
88
|
+
exec = replies.last
|
89
|
+
|
90
|
+
return if exec.nil? # The transaction failed because of WATCH.
|
72
91
|
|
73
|
-
|
92
|
+
# EXEC command failed.
|
93
|
+
raise exec if exec.is_a?(CommandError)
|
94
|
+
|
95
|
+
if exec.size < futures.size
|
74
96
|
# Some command wasn't recognized by Redis.
|
75
|
-
|
97
|
+
command_error = replies.detect { |r| r.is_a?(CommandError) }
|
98
|
+
raise command_error
|
76
99
|
end
|
77
100
|
|
78
|
-
super(
|
101
|
+
super(exec) do |reply|
|
79
102
|
# Because an EXEC returns nested replies, hiredis won't be able to
|
80
103
|
# convert an error reply to a CommandError instance itself. This is
|
81
104
|
# specific to MULTI/EXEC, so we solve this here.
|
@@ -83,8 +106,20 @@ class Redis
|
|
83
106
|
end
|
84
107
|
end
|
85
108
|
|
109
|
+
def timeouts
|
110
|
+
if empty?
|
111
|
+
[]
|
112
|
+
else
|
113
|
+
[nil, *super, nil]
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
86
117
|
def commands
|
87
|
-
|
118
|
+
if empty?
|
119
|
+
[]
|
120
|
+
else
|
121
|
+
[[:multi]] + super + [[:exec]]
|
122
|
+
end
|
88
123
|
end
|
89
124
|
end
|
90
125
|
end
|
@@ -98,12 +133,25 @@ class Redis
|
|
98
133
|
class Future < BasicObject
|
99
134
|
FutureNotReady = ::Redis::FutureNotReady.new
|
100
135
|
|
101
|
-
|
136
|
+
attr_reader :timeout
|
137
|
+
|
138
|
+
def initialize(command, transformation, timeout)
|
102
139
|
@command = command
|
103
140
|
@transformation = transformation
|
141
|
+
@timeout = timeout
|
104
142
|
@object = FutureNotReady
|
105
143
|
end
|
106
144
|
|
145
|
+
def ==(_other)
|
146
|
+
message = +"The methods == and != are deprecated for Redis::Future and will be removed in 4.2.0"
|
147
|
+
message << " - You probably meant to call .value == or .value !="
|
148
|
+
message << " (#{::Kernel.caller(1, 1).first})\n"
|
149
|
+
|
150
|
+
::Kernel.warn(message)
|
151
|
+
|
152
|
+
super
|
153
|
+
end
|
154
|
+
|
107
155
|
def inspect
|
108
156
|
"<Redis::Future #{@command.inspect}>"
|
109
157
|
end
|
@@ -118,8 +166,16 @@ class Redis
|
|
118
166
|
end
|
119
167
|
|
120
168
|
def value
|
121
|
-
::Kernel.raise(@object) if @object.
|
169
|
+
::Kernel.raise(@object) if @object.is_a?(::RuntimeError)
|
122
170
|
@object
|
123
171
|
end
|
172
|
+
|
173
|
+
def is_a?(other)
|
174
|
+
self.class.ancestors.include?(other)
|
175
|
+
end
|
176
|
+
|
177
|
+
def class
|
178
|
+
Future
|
179
|
+
end
|
124
180
|
end
|
125
181
|
end
|
data/lib/redis/subscribe.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Redis
|
2
4
|
class SubscribedClient
|
3
5
|
def initialize(client)
|
@@ -12,32 +14,41 @@ class Redis
|
|
12
14
|
subscription("subscribe", "unsubscribe", channels, block)
|
13
15
|
end
|
14
16
|
|
17
|
+
def subscribe_with_timeout(timeout, *channels, &block)
|
18
|
+
subscription("subscribe", "unsubscribe", channels, block, timeout)
|
19
|
+
end
|
20
|
+
|
15
21
|
def psubscribe(*channels, &block)
|
16
22
|
subscription("psubscribe", "punsubscribe", channels, block)
|
17
23
|
end
|
18
24
|
|
25
|
+
def psubscribe_with_timeout(timeout, *channels, &block)
|
26
|
+
subscription("psubscribe", "punsubscribe", channels, block, timeout)
|
27
|
+
end
|
28
|
+
|
19
29
|
def unsubscribe(*channels)
|
20
|
-
call
|
30
|
+
call([:unsubscribe, *channels])
|
21
31
|
end
|
22
32
|
|
23
33
|
def punsubscribe(*channels)
|
24
|
-
call
|
34
|
+
call([:punsubscribe, *channels])
|
25
35
|
end
|
26
36
|
|
27
|
-
|
37
|
+
protected
|
28
38
|
|
29
|
-
def subscription(start, stop, channels, block)
|
39
|
+
def subscription(start, stop, channels, block, timeout = 0)
|
30
40
|
sub = Subscription.new(&block)
|
31
41
|
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
send(stop)
|
42
|
+
unsubscribed = false
|
43
|
+
|
44
|
+
@client.call_loop([start, *channels], timeout) do |line|
|
45
|
+
type, *rest = line
|
46
|
+
sub.callbacks[type].call(*rest)
|
47
|
+
unsubscribed = type == stop && rest.last == 0
|
48
|
+
break if unsubscribed
|
40
49
|
end
|
50
|
+
# No need to unsubscribe here. The real client closes the connection
|
51
|
+
# whenever an exception is raised (see #ensure_connected).
|
41
52
|
end
|
42
53
|
end
|
43
54
|
|
@@ -46,7 +57,7 @@ class Redis
|
|
46
57
|
|
47
58
|
def initialize
|
48
59
|
@callbacks = Hash.new do |hash, key|
|
49
|
-
hash[key] =
|
60
|
+
hash[key] = ->(*_) {}
|
50
61
|
end
|
51
62
|
|
52
63
|
yield(self)
|
data/lib/redis/version.rb
CHANGED