redis 4.1.2 → 4.2.2
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 +4 -4
- data/CHANGELOG.md +39 -1
- data/README.md +14 -5
- data/lib/redis.rb +385 -343
- data/lib/redis/client.rb +66 -69
- data/lib/redis/cluster.rb +13 -4
- data/lib/redis/cluster/node.rb +3 -0
- data/lib/redis/cluster/node_key.rb +3 -7
- data/lib/redis/cluster/option.rb +27 -14
- data/lib/redis/cluster/slot.rb +30 -13
- data/lib/redis/cluster/slot_loader.rb +4 -4
- data/lib/redis/connection.rb +2 -0
- data/lib/redis/connection/command_helper.rb +3 -2
- data/lib/redis/connection/hiredis.rb +4 -3
- data/lib/redis/connection/registry.rb +2 -1
- data/lib/redis/connection/ruby.rb +47 -58
- data/lib/redis/connection/synchrony.rb +9 -4
- data/lib/redis/distributed.rb +117 -62
- data/lib/redis/errors.rb +2 -0
- data/lib/redis/hash_ring.rb +15 -14
- data/lib/redis/pipeline.rb +16 -3
- data/lib/redis/subscribe.rb +11 -12
- data/lib/redis/version.rb +3 -1
- metadata +14 -9
data/lib/redis/cluster/slot.rb
CHANGED
@@ -1,7 +1,5 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require 'set'
|
4
|
-
|
5
3
|
class Redis
|
6
4
|
class Cluster
|
7
5
|
# Keep slot and node key map for Redis Cluster Client
|
@@ -28,11 +26,20 @@ class Redis
|
|
28
26
|
return nil unless exists?(slot)
|
29
27
|
return find_node_key_of_master(slot) if replica_disabled?
|
30
28
|
|
31
|
-
@map[slot][:slaves].
|
29
|
+
@map[slot][:slaves].sample
|
32
30
|
end
|
33
31
|
|
34
32
|
def put(slot, node_key)
|
35
|
-
|
33
|
+
# Since we're sharing a hash for build_slot_node_key_map, duplicate it
|
34
|
+
# if it already exists instead of preserving as-is.
|
35
|
+
@map[slot] = @map[slot] ? @map[slot].dup : { master: nil, slaves: [] }
|
36
|
+
|
37
|
+
if master?(node_key)
|
38
|
+
@map[slot][:master] = node_key
|
39
|
+
elsif !@map[slot][:slaves].include?(node_key)
|
40
|
+
@map[slot][:slaves] << node_key
|
41
|
+
end
|
42
|
+
|
36
43
|
nil
|
37
44
|
end
|
38
45
|
|
@@ -50,19 +57,29 @@ class Redis
|
|
50
57
|
@node_flags[node_key] == ROLE_SLAVE
|
51
58
|
end
|
52
59
|
|
60
|
+
# available_slots is mapping of node_key to list of slot ranges
|
53
61
|
def build_slot_node_key_map(available_slots)
|
54
|
-
|
55
|
-
|
62
|
+
by_ranges = {}
|
63
|
+
available_slots.each do |node_key, slots_arr|
|
64
|
+
by_ranges[slots_arr] ||= { master: nil, slaves: [] }
|
65
|
+
|
66
|
+
if master?(node_key)
|
67
|
+
by_ranges[slots_arr][:master] = node_key
|
68
|
+
elsif !by_ranges[slots_arr][:slaves].include?(node_key)
|
69
|
+
by_ranges[slots_arr][:slaves] << node_key
|
70
|
+
end
|
56
71
|
end
|
57
|
-
end
|
58
72
|
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
73
|
+
by_slot = {}
|
74
|
+
by_ranges.each do |slots_arr, nodes|
|
75
|
+
slots_arr.each do |slots|
|
76
|
+
slots.each do |slot|
|
77
|
+
by_slot[slot] = nodes
|
78
|
+
end
|
79
|
+
end
|
65
80
|
end
|
81
|
+
|
82
|
+
by_slot
|
66
83
|
end
|
67
84
|
end
|
68
85
|
end
|
@@ -13,7 +13,7 @@ class Redis
|
|
13
13
|
info = {}
|
14
14
|
|
15
15
|
nodes.each do |node|
|
16
|
-
info =
|
16
|
+
info = fetch_slot_info(node)
|
17
17
|
info.empty? ? next : break
|
18
18
|
end
|
19
19
|
|
@@ -23,9 +23,10 @@ class Redis
|
|
23
23
|
end
|
24
24
|
|
25
25
|
def fetch_slot_info(node)
|
26
|
+
hash_with_default_arr = Hash.new { |h, k| h[k] = [] }
|
26
27
|
node.call(%i[cluster slots])
|
27
|
-
.
|
28
|
-
.
|
28
|
+
.flat_map { |arr| parse_slot_info(arr, default_ip: node.host) }
|
29
|
+
.each_with_object(hash_with_default_arr) { |arr, h| h[arr[0]] << arr[1] }
|
29
30
|
rescue CannotConnectError, ConnectionError, CommandError
|
30
31
|
{} # can retry on another node
|
31
32
|
end
|
@@ -34,7 +35,6 @@ class Redis
|
|
34
35
|
first_slot, last_slot = arr[0..1]
|
35
36
|
slot_range = (first_slot..last_slot).freeze
|
36
37
|
arr[2..-1].map { |addr| [stringify_node_key(addr, default_ip), slot_range] }
|
37
|
-
.flatten
|
38
38
|
end
|
39
39
|
|
40
40
|
def stringify_node_key(arr, default_ip)
|
data/lib/redis/connection.rb
CHANGED
@@ -1,7 +1,8 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Redis
|
2
4
|
module Connection
|
3
5
|
module CommandHelper
|
4
|
-
|
5
6
|
COMMAND_DELIMITER = "\r\n"
|
6
7
|
|
7
8
|
def build_command(args)
|
@@ -28,7 +29,7 @@ class Redis
|
|
28
29
|
command.join(COMMAND_DELIMITER)
|
29
30
|
end
|
30
31
|
|
31
|
-
|
32
|
+
protected
|
32
33
|
|
33
34
|
def encode(string)
|
34
35
|
string.force_encoding(Encoding.default_external)
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative "registry"
|
2
4
|
require_relative "../errors"
|
3
5
|
require "hiredis/connection"
|
@@ -6,7 +8,6 @@ require "timeout"
|
|
6
8
|
class Redis
|
7
9
|
module Connection
|
8
10
|
class Hiredis
|
9
|
-
|
10
11
|
def self.connect(config)
|
11
12
|
connection = ::Hiredis::Connection.new
|
12
13
|
connect_timeout = (config.fetch(:connect_timeout, 0) * 1_000_000).to_i
|
@@ -31,7 +32,7 @@ class Redis
|
|
31
32
|
end
|
32
33
|
|
33
34
|
def connected?
|
34
|
-
@connection
|
35
|
+
@connection&.connected?
|
35
36
|
end
|
36
37
|
|
37
38
|
def timeout=(timeout)
|
@@ -57,7 +58,7 @@ class Redis
|
|
57
58
|
rescue Errno::EAGAIN
|
58
59
|
raise TimeoutError
|
59
60
|
rescue RuntimeError => err
|
60
|
-
raise ProtocolError
|
61
|
+
raise ProtocolError, err.message
|
61
62
|
end
|
62
63
|
end
|
63
64
|
end
|
@@ -1,6 +1,7 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
class Redis
|
2
4
|
module Connection
|
3
|
-
|
4
5
|
# Store a list of loaded connection drivers in the Connection module.
|
5
6
|
# Redis::Client uses the last required driver by default, and will be aware
|
6
7
|
# of the loaded connection drivers if the user chooses to override the
|
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require_relative "registry"
|
2
4
|
require_relative "command_helper"
|
3
5
|
require_relative "../errors"
|
@@ -13,8 +15,7 @@ end
|
|
13
15
|
class Redis
|
14
16
|
module Connection
|
15
17
|
module SocketMixin
|
16
|
-
|
17
|
-
CRLF = "\r\n".freeze
|
18
|
+
CRLF = "\r\n"
|
18
19
|
|
19
20
|
def initialize(*args)
|
20
21
|
super(*args)
|
@@ -24,46 +25,32 @@ class Redis
|
|
24
25
|
end
|
25
26
|
|
26
27
|
def timeout=(timeout)
|
27
|
-
if timeout && timeout > 0
|
28
|
-
@timeout = timeout
|
29
|
-
else
|
30
|
-
@timeout = nil
|
31
|
-
end
|
28
|
+
@timeout = (timeout if timeout && timeout > 0)
|
32
29
|
end
|
33
30
|
|
34
31
|
def write_timeout=(timeout)
|
35
|
-
if timeout && timeout > 0
|
36
|
-
@write_timeout = timeout
|
37
|
-
else
|
38
|
-
@write_timeout = nil
|
39
|
-
end
|
32
|
+
@write_timeout = (timeout if timeout && timeout > 0)
|
40
33
|
end
|
41
34
|
|
42
35
|
def read(nbytes)
|
43
36
|
result = @buffer.slice!(0, nbytes)
|
44
37
|
|
45
|
-
while result.bytesize < nbytes
|
46
|
-
result << _read_from_socket(nbytes - result.bytesize)
|
47
|
-
end
|
38
|
+
result << _read_from_socket(nbytes - result.bytesize) while result.bytesize < nbytes
|
48
39
|
|
49
40
|
result
|
50
41
|
end
|
51
42
|
|
52
43
|
def gets
|
53
|
-
crlf = nil
|
54
|
-
|
55
|
-
while (crlf = @buffer.index(CRLF)) == nil
|
56
|
-
@buffer << _read_from_socket(1024)
|
44
|
+
while (crlf = @buffer.index(CRLF)).nil?
|
45
|
+
@buffer << _read_from_socket(16_384)
|
57
46
|
end
|
58
47
|
|
59
48
|
@buffer.slice!(0, crlf + CRLF.bytesize)
|
60
49
|
end
|
61
50
|
|
62
51
|
def _read_from_socket(nbytes)
|
63
|
-
|
64
52
|
begin
|
65
53
|
read_nonblock(nbytes)
|
66
|
-
|
67
54
|
rescue IO::WaitReadable
|
68
55
|
if IO.select([self], nil, nil, @timeout)
|
69
56
|
retry
|
@@ -77,7 +64,6 @@ class Redis
|
|
77
64
|
raise Redis::TimeoutError
|
78
65
|
end
|
79
66
|
end
|
80
|
-
|
81
67
|
rescue EOFError
|
82
68
|
raise Errno::ECONNRESET
|
83
69
|
end
|
@@ -85,7 +71,6 @@ class Redis
|
|
85
71
|
def _write_to_socket(data)
|
86
72
|
begin
|
87
73
|
write_nonblock(data)
|
88
|
-
|
89
74
|
rescue IO::WaitWritable
|
90
75
|
if IO.select(nil, [self], nil, @write_timeout)
|
91
76
|
retry
|
@@ -99,7 +84,6 @@ class Redis
|
|
99
84
|
raise Redis::TimeoutError
|
100
85
|
end
|
101
86
|
end
|
102
|
-
|
103
87
|
rescue EOFError
|
104
88
|
raise Errno::ECONNRESET
|
105
89
|
end
|
@@ -114,6 +98,7 @@ class Redis
|
|
114
98
|
|
115
99
|
total_count += count
|
116
100
|
return total_count if total_count >= length
|
101
|
+
|
117
102
|
data = data.byteslice(count..-1)
|
118
103
|
end
|
119
104
|
end
|
@@ -124,7 +109,6 @@ class Redis
|
|
124
109
|
require "timeout"
|
125
110
|
|
126
111
|
class TCPSocket < ::TCPSocket
|
127
|
-
|
128
112
|
include SocketMixin
|
129
113
|
|
130
114
|
def self.connect(host, port, timeout)
|
@@ -140,7 +124,6 @@ class Redis
|
|
140
124
|
if defined?(::UNIXSocket)
|
141
125
|
|
142
126
|
class UNIXSocket < ::UNIXSocket
|
143
|
-
|
144
127
|
include SocketMixin
|
145
128
|
|
146
129
|
def self.connect(path, timeout)
|
@@ -158,7 +141,6 @@ class Redis
|
|
158
141
|
|
159
142
|
def _read_from_socket(nbytes)
|
160
143
|
readpartial(nbytes)
|
161
|
-
|
162
144
|
rescue EOFError
|
163
145
|
raise Errno::ECONNRESET
|
164
146
|
end
|
@@ -169,19 +151,16 @@ class Redis
|
|
169
151
|
else
|
170
152
|
|
171
153
|
class TCPSocket < ::Socket
|
172
|
-
|
173
154
|
include SocketMixin
|
174
155
|
|
175
|
-
def self.connect_addrinfo(
|
176
|
-
sock = new(::Socket.const_get(
|
177
|
-
sockaddr = ::Socket.pack_sockaddr_in(port,
|
156
|
+
def self.connect_addrinfo(addrinfo, port, timeout)
|
157
|
+
sock = new(::Socket.const_get(addrinfo[0]), Socket::SOCK_STREAM, 0)
|
158
|
+
sockaddr = ::Socket.pack_sockaddr_in(port, addrinfo[3])
|
178
159
|
|
179
160
|
begin
|
180
161
|
sock.connect_nonblock(sockaddr)
|
181
162
|
rescue Errno::EINPROGRESS
|
182
|
-
if IO.select(nil, [sock], nil, timeout)
|
183
|
-
raise TimeoutError
|
184
|
-
end
|
163
|
+
raise TimeoutError if IO.select(nil, [sock], nil, timeout).nil?
|
185
164
|
|
186
165
|
begin
|
187
166
|
sock.connect_nonblock(sockaddr)
|
@@ -220,14 +199,13 @@ class Redis
|
|
220
199
|
return connect_addrinfo(ai, port, timeout)
|
221
200
|
rescue SystemCallError
|
222
201
|
# Raise if this was our last attempt.
|
223
|
-
raise if addrinfo.length == i+1
|
202
|
+
raise if addrinfo.length == i + 1
|
224
203
|
end
|
225
204
|
end
|
226
205
|
end
|
227
206
|
end
|
228
207
|
|
229
208
|
class UNIXSocket < ::Socket
|
230
|
-
|
231
209
|
include SocketMixin
|
232
210
|
|
233
211
|
def self.connect(path, timeout)
|
@@ -237,9 +215,7 @@ class Redis
|
|
237
215
|
begin
|
238
216
|
sock.connect_nonblock(sockaddr)
|
239
217
|
rescue Errno::EINPROGRESS
|
240
|
-
if IO.select(nil, [sock], nil, timeout)
|
241
|
-
raise TimeoutError
|
242
|
-
end
|
218
|
+
raise TimeoutError if IO.select(nil, [sock], nil, timeout).nil?
|
243
219
|
|
244
220
|
begin
|
245
221
|
sock.connect_nonblock(sockaddr)
|
@@ -262,7 +238,9 @@ class Redis
|
|
262
238
|
tcp_sock = TCPSocket.connect(host, port, timeout)
|
263
239
|
|
264
240
|
ctx = OpenSSL::SSL::SSLContext.new
|
265
|
-
|
241
|
+
|
242
|
+
# The provided parameters are merged into OpenSSL::SSL::SSLContext::DEFAULT_PARAMS
|
243
|
+
ctx.set_params(ssl_params || {})
|
266
244
|
|
267
245
|
ssl_sock = new(tcp_sock, ctx)
|
268
246
|
ssl_sock.hostname = host
|
@@ -289,7 +267,10 @@ class Redis
|
|
289
267
|
end
|
290
268
|
end
|
291
269
|
|
292
|
-
unless ctx.verify_mode == OpenSSL::SSL::VERIFY_NONE
|
270
|
+
unless ctx.verify_mode == OpenSSL::SSL::VERIFY_NONE || (
|
271
|
+
ctx.respond_to?(:verify_hostname) &&
|
272
|
+
!ctx.verify_hostname
|
273
|
+
)
|
293
274
|
ssl_sock.post_connection_check(host)
|
294
275
|
end
|
295
276
|
|
@@ -301,15 +282,16 @@ class Redis
|
|
301
282
|
class Ruby
|
302
283
|
include Redis::Connection::CommandHelper
|
303
284
|
|
304
|
-
MINUS = "-"
|
305
|
-
PLUS = "+"
|
306
|
-
COLON = ":"
|
307
|
-
DOLLAR = "$"
|
308
|
-
ASTERISK = "*"
|
285
|
+
MINUS = "-"
|
286
|
+
PLUS = "+"
|
287
|
+
COLON = ":"
|
288
|
+
DOLLAR = "$"
|
289
|
+
ASTERISK = "*"
|
309
290
|
|
310
291
|
def self.connect(config)
|
311
292
|
if config[:scheme] == "unix"
|
312
293
|
raise ArgumentError, "SSL incompatible with unix sockets" if config[:ssl]
|
294
|
+
|
313
295
|
sock = UNIXSocket.connect(config[:path], config[:connect_timeout])
|
314
296
|
elsif config[:scheme] == "rediss" || config[:ssl]
|
315
297
|
sock = SSLSocket.connect(config[:host], config[:port], config[:connect_timeout], config[:ssl_params])
|
@@ -321,10 +303,11 @@ class Redis
|
|
321
303
|
instance.timeout = config[:read_timeout]
|
322
304
|
instance.write_timeout = config[:write_timeout]
|
323
305
|
instance.set_tcp_keepalive config[:tcp_keepalive]
|
306
|
+
instance.set_tcp_nodelay if sock.is_a? TCPSocket
|
324
307
|
instance
|
325
308
|
end
|
326
309
|
|
327
|
-
if [
|
310
|
+
if %i[SOL_SOCKET SO_KEEPALIVE SOL_TCP TCP_KEEPIDLE TCP_KEEPINTVL TCP_KEEPCNT].all? { |c| Socket.const_defined? c }
|
328
311
|
def set_tcp_keepalive(keepalive)
|
329
312
|
return unless keepalive.is_a?(Hash)
|
330
313
|
|
@@ -336,14 +319,13 @@ class Redis
|
|
336
319
|
|
337
320
|
def get_tcp_keepalive
|
338
321
|
{
|
339
|
-
:
|
340
|
-
:
|
341
|
-
:
|
322
|
+
time: @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPIDLE).int,
|
323
|
+
intvl: @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPINTVL).int,
|
324
|
+
probes: @sock.getsockopt(Socket::SOL_TCP, Socket::TCP_KEEPCNT).int
|
342
325
|
}
|
343
326
|
end
|
344
327
|
else
|
345
|
-
def set_tcp_keepalive(keepalive)
|
346
|
-
end
|
328
|
+
def set_tcp_keepalive(keepalive); end
|
347
329
|
|
348
330
|
def get_tcp_keepalive
|
349
331
|
{
|
@@ -351,12 +333,21 @@ class Redis
|
|
351
333
|
end
|
352
334
|
end
|
353
335
|
|
336
|
+
# disables Nagle's Algorithm, prevents multiple round trips with MULTI
|
337
|
+
if %i[IPPROTO_TCP TCP_NODELAY].all? { |c| Socket.const_defined? c }
|
338
|
+
def set_tcp_nodelay
|
339
|
+
@sock.setsockopt(Socket::IPPROTO_TCP, Socket::TCP_NODELAY, 1)
|
340
|
+
end
|
341
|
+
else
|
342
|
+
def set_tcp_nodelay; end
|
343
|
+
end
|
344
|
+
|
354
345
|
def initialize(sock)
|
355
346
|
@sock = sock
|
356
347
|
end
|
357
348
|
|
358
349
|
def connected?
|
359
|
-
|
350
|
+
!!@sock
|
360
351
|
end
|
361
352
|
|
362
353
|
def disconnect
|
@@ -367,9 +358,7 @@ class Redis
|
|
367
358
|
end
|
368
359
|
|
369
360
|
def timeout=(timeout)
|
370
|
-
if @sock.respond_to?(:timeout=)
|
371
|
-
@sock.timeout = timeout
|
372
|
-
end
|
361
|
+
@sock.timeout = timeout if @sock.respond_to?(:timeout=)
|
373
362
|
end
|
374
363
|
|
375
364
|
def write_timeout=(timeout)
|
@@ -384,7 +373,6 @@ class Redis
|
|
384
373
|
line = @sock.gets
|
385
374
|
reply_type = line.slice!(0, 1)
|
386
375
|
format_reply(reply_type, line)
|
387
|
-
|
388
376
|
rescue Errno::EAGAIN
|
389
377
|
raise TimeoutError
|
390
378
|
end
|
@@ -396,7 +384,7 @@ class Redis
|
|
396
384
|
when COLON then format_integer_reply(line)
|
397
385
|
when DOLLAR then format_bulk_reply(line)
|
398
386
|
when ASTERISK then format_multi_bulk_reply(line)
|
399
|
-
else raise ProtocolError
|
387
|
+
else raise ProtocolError, reply_type
|
400
388
|
end
|
401
389
|
end
|
402
390
|
|
@@ -415,6 +403,7 @@ class Redis
|
|
415
403
|
def format_bulk_reply(line)
|
416
404
|
bulklen = line.to_i
|
417
405
|
return if bulklen == -1
|
406
|
+
|
418
407
|
reply = encode(@sock.read(bulklen))
|
419
408
|
@sock.read(2) # Discard CRLF.
|
420
409
|
reply
|