redis 4.1.4 → 4.5.1
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 +77 -0
- data/README.md +27 -17
- data/lib/redis/client.rb +108 -74
- data/lib/redis/cluster/command_loader.rb +6 -7
- data/lib/redis/cluster/node.rb +5 -1
- data/lib/redis/cluster/option.rb +9 -3
- data/lib/redis/cluster/slot.rb +28 -14
- data/lib/redis/cluster/slot_loader.rb +2 -3
- data/lib/redis/cluster.rb +13 -13
- data/lib/redis/connection/command_helper.rb +4 -2
- data/lib/redis/connection/hiredis.rb +3 -3
- data/lib/redis/connection/registry.rb +1 -1
- data/lib/redis/connection/ruby.rb +92 -109
- data/lib/redis/connection/synchrony.rb +8 -4
- data/lib/redis/connection.rb +1 -0
- data/lib/redis/distributed.rb +161 -63
- data/lib/redis/errors.rb +1 -0
- data/lib/redis/hash_ring.rb +14 -14
- data/lib/redis/pipeline.rb +6 -8
- data/lib/redis/subscribe.rb +10 -12
- data/lib/redis/version.rb +2 -1
- data/lib/redis.rb +597 -261
- metadata +15 -10
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 7a7232fef186e6d6a11a90d8dd9aa7c71114f017e0afe9378999a96c9e6b4e05
|
4
|
+
data.tar.gz: 689f176b87909bf61eb60e57d1eb795198162a7e039104c80facf36880964bda
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 22b11dee92e298b46bb94a707156d3dbf9afb83c8e9e8cbf82cf366d582a7c1b7295d7f09a0fec01965245f4800c2482c0559d646f46ff1c6bba6423ab398ba9
|
7
|
+
data.tar.gz: 9a74ba29c8cb3d7634a44c78e30018a048e19cca66c7fad6a226722183f66546bebb370c92ecdf20522bb35ee72a09b2fda64891ff6d94db1702375bd1ba6b46
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,81 @@
|
|
1
1
|
# Unreleased
|
2
2
|
|
3
|
+
# 4.5.1
|
4
|
+
|
5
|
+
* Restore the accidential auth behavior of redis-rb 4.3.0 with a warning. If provided with the `default` user's password, but a wrong username,
|
6
|
+
redis-rb will first try to connect as the provided user, but then will fallback to connect as the `default` user with the provided password.
|
7
|
+
This behavior is deprecated and will be removed in Redis 4.6.0. Fix #1038.
|
8
|
+
|
9
|
+
# 4.5.0
|
10
|
+
|
11
|
+
* Handle parts of the command using incompatible encodings. See #1037.
|
12
|
+
* Add GET option to SET command. See #1036.
|
13
|
+
* Add ZRANDMEMBER command. See #1035.
|
14
|
+
* Add LMOVE/BLMOVE commands. See #1034.
|
15
|
+
* Add ZMSCORE command. See #1032.
|
16
|
+
* Add LT/GT options to ZADD. See #1033.
|
17
|
+
* Add SMISMEMBER command. See #1031.
|
18
|
+
* Add EXAT/PXAT options to SET. See #1028.
|
19
|
+
* Add GETDEL/GETEX commands. See #1024.
|
20
|
+
* `Redis#exists` now returns an Integer by default, as warned since 4.2.0. The old behavior can be restored with `Redis.exists_returns_integer = false`.
|
21
|
+
* Fix Redis < 6 detection during connect. See #1025.
|
22
|
+
* Fix fetching command details in Redis cluster when the first node is unhealthy. See #1026.
|
23
|
+
|
24
|
+
# 4.4.0
|
25
|
+
|
26
|
+
* Redis cluster: fix cross-slot validation in pipelines. Fix ##1019.
|
27
|
+
* Add support for `XAUTOCLAIM`. See #1018.
|
28
|
+
* Properly issue `READONLY` when reconnecting to replicas. Fix #1017.
|
29
|
+
* Make `del` a noop if passed an empty list of keys. See #998.
|
30
|
+
* Add support for `ZINTER`. See #995.
|
31
|
+
|
32
|
+
# 4.3.1
|
33
|
+
|
34
|
+
* Fix password authentication against redis server 5 and older.
|
35
|
+
|
36
|
+
# 4.3.0
|
37
|
+
|
38
|
+
* Add the TYPE argument to scan and scan_each. See #985.
|
39
|
+
* Support AUTH command for ACL. See #967.
|
40
|
+
|
41
|
+
# 4.2.5
|
42
|
+
|
43
|
+
* Optimize the ruby connector write buffering. See #964.
|
44
|
+
|
45
|
+
# 4.2.4
|
46
|
+
|
47
|
+
* Fix bytesize calculations in the ruby connector, and work on a copy of the buffer. Fix #961, #962.
|
48
|
+
|
49
|
+
# 4.2.3
|
50
|
+
|
51
|
+
* Use io/wait instead of IO.select in the ruby connector. See #960.
|
52
|
+
* Use exception free non blocking IOs in the ruby connector. See #926.
|
53
|
+
* Prevent corruption of the client when an interrupt happen during inside a pipeline block. See #945.
|
54
|
+
|
55
|
+
# 4.2.2
|
56
|
+
|
57
|
+
* Fix `WATCH` support for `Redis::Distributed`. See #941.
|
58
|
+
* Fix handling of empty stream responses. See #905, #929.
|
59
|
+
|
60
|
+
# 4.2.1
|
61
|
+
|
62
|
+
* Fix `exists?` returning an actual boolean when called with multiple keys. See #918.
|
63
|
+
* Setting `Redis.exists_returns_integer = false` disables warning message about new behaviour. See #920.
|
64
|
+
|
65
|
+
# 4.2.0
|
66
|
+
|
67
|
+
* Convert commands to accept keyword arguments rather than option hashes. This both help catching typos, and reduce needless allocations.
|
68
|
+
* Deprecate the synchrony driver. It will be removed in 5.0 and hopefully maintained as a separate gem. See #915.
|
69
|
+
* Make `Redis#exists` variadic, will return an Integer if called with multiple keys.
|
70
|
+
* Add `Redis#exists?` to get a Boolean if any of the keys exists.
|
71
|
+
* `Redis#exists` when called with a single key will warn that future versions will return an Integer.
|
72
|
+
Set `Redis.exists_returns_integer = true` to opt-in to the new behavior.
|
73
|
+
* Support `keepttl` ooption in `set`. See #913.
|
74
|
+
* Optimized initialization of Redis::Cluster. See #912.
|
75
|
+
* Accept sentinel options even with string key. See #599.
|
76
|
+
* Verify TLS connections by default. See #900.
|
77
|
+
* Make `Redis#hset` variadic. It now returns an integer, not a boolean. See #910.
|
78
|
+
|
3
79
|
# 4.1.4
|
4
80
|
|
5
81
|
* Alias `Redis#disconnect` as `#close`. See #901.
|
@@ -9,6 +85,7 @@
|
|
9
85
|
* Increase buffer size in the ruby connector. See #880.
|
10
86
|
* Fix thread safety of `Redis.queue`. See #878.
|
11
87
|
* Deprecate `Redis::Future#==` as it's likely to be a mistake. See #876.
|
88
|
+
* Support `KEEPTTL` option for SET command. See #913.
|
12
89
|
|
13
90
|
# 4.1.3
|
14
91
|
|
data/README.md
CHANGED
@@ -1,8 +1,9 @@
|
|
1
|
-
# redis-rb [![Build Status][
|
1
|
+
# redis-rb [![Build Status][gh-actions-image]][gh-actions-link] [![Inline docs][inchpages-image]][inchpages-link]
|
2
2
|
|
3
3
|
A Ruby client that tries to match [Redis][redis-home]' API one-to-one, while still
|
4
4
|
providing an idiomatic interface.
|
5
5
|
|
6
|
+
See [RubyDoc.info][rubydoc] for the API docs of the latest published gem.
|
6
7
|
|
7
8
|
## Getting started
|
8
9
|
|
@@ -34,6 +35,9 @@ You can also specify connection options as a [`redis://` URL][redis-url]:
|
|
34
35
|
redis = Redis.new(url: "redis://:p4ssw0rd@10.0.1.1:6380/15")
|
35
36
|
```
|
36
37
|
|
38
|
+
The client expects passwords with special chracters to be URL-encoded (i.e.
|
39
|
+
`CGI.escape(password)`).
|
40
|
+
|
37
41
|
By default, the client will try to read the `REDIS_URL` environment variable
|
38
42
|
and use that as URL to connect to. The above statement is therefore equivalent
|
39
43
|
to setting this environment variable and calling `Redis.new` without arguments.
|
@@ -50,6 +54,12 @@ To connect to a password protected Redis instance, use:
|
|
50
54
|
redis = Redis.new(password: "mysecret")
|
51
55
|
```
|
52
56
|
|
57
|
+
To connect a Redis instance using [ACL](https://redis.io/topics/acl), use:
|
58
|
+
|
59
|
+
```ruby
|
60
|
+
redis = Redis.new(username: 'myname', password: 'mysecret')
|
61
|
+
```
|
62
|
+
|
53
63
|
The Redis class exports methods that are named identical to the commands
|
54
64
|
they execute. The arguments these methods accept are often identical to
|
55
65
|
the arguments specified on the [Redis website][redis-commands]. For
|
@@ -147,8 +157,8 @@ redis.mget('{key}1', '{key}2')
|
|
147
157
|
|
148
158
|
## Storing objects
|
149
159
|
|
150
|
-
Redis
|
151
|
-
|
160
|
+
Redis "string" types can be used to store serialized Ruby objects, for
|
161
|
+
example with JSON:
|
152
162
|
|
153
163
|
```ruby
|
154
164
|
require "json"
|
@@ -261,6 +271,7 @@ All timeout values are specified in seconds.
|
|
261
271
|
When using pub/sub, you can subscribe to a channel using a timeout as well:
|
262
272
|
|
263
273
|
```ruby
|
274
|
+
redis = Redis.new(reconnect_attempts: 0)
|
264
275
|
redis.subscribe_with_timeout(5, "news") do |on|
|
265
276
|
on.message do |channel, message|
|
266
277
|
# ...
|
@@ -322,7 +333,7 @@ This library supports natively terminating client side SSL/TLS connections
|
|
322
333
|
when talking to Redis via a server-side proxy such as [stunnel], [hitch],
|
323
334
|
or [ghostunnel].
|
324
335
|
|
325
|
-
To enable SSL support, pass the `:ssl =>
|
336
|
+
To enable SSL support, pass the `:ssl => true` option when configuring the
|
326
337
|
Redis client, or pass in `:url => "rediss://..."` (like HTTPS for Redis).
|
327
338
|
You will also need to pass in an `:ssl_params => { ... }` hash used to
|
328
339
|
configure the `OpenSSL::SSL::SSLContext` object used for the connection:
|
@@ -435,7 +446,7 @@ redis = Redis.new(:driver => :synchrony)
|
|
435
446
|
## Testing
|
436
447
|
|
437
448
|
This library is tested against recent Ruby and Redis versions.
|
438
|
-
Check [
|
449
|
+
Check [Github Actions][gh-actions-link] for the exact versions supported.
|
439
450
|
|
440
451
|
## See Also
|
441
452
|
|
@@ -451,15 +462,14 @@ client and evangelized Redis in Rubyland. Thank you, Ezra.
|
|
451
462
|
## Contributing
|
452
463
|
|
453
464
|
[Fork the project](https://github.com/redis/redis-rb) and send pull
|
454
|
-
requests.
|
455
|
-
|
456
|
-
|
457
|
-
[inchpages-image]:
|
458
|
-
[inchpages-link]:
|
459
|
-
[redis-commands]:
|
460
|
-
[redis-home]:
|
461
|
-
[redis-url]:
|
462
|
-
[
|
463
|
-
[
|
464
|
-
[
|
465
|
-
[rubydoc]: http://www.rubydoc.info/gems/redis
|
465
|
+
requests.
|
466
|
+
|
467
|
+
|
468
|
+
[inchpages-image]: https://inch-ci.org/github/redis/redis-rb.svg
|
469
|
+
[inchpages-link]: https://inch-ci.org/github/redis/redis-rb
|
470
|
+
[redis-commands]: https://redis.io/commands
|
471
|
+
[redis-home]: https://redis.io
|
472
|
+
[redis-url]: http://www.iana.org/assignments/uri-schemes/prov/redis
|
473
|
+
[gh-actions-image]: https://github.com/redis/redis-rb/workflows/Test/badge.svg
|
474
|
+
[gh-actions-link]: https://github.com/redis/redis-rb/actions
|
475
|
+
[rubydoc]: http://www.rubydoc.info/gems/redis
|
data/lib/redis/client.rb
CHANGED
@@ -6,24 +6,31 @@ require "cgi"
|
|
6
6
|
|
7
7
|
class Redis
|
8
8
|
class Client
|
9
|
-
|
9
|
+
# Defaults are also used for converting string keys to symbols.
|
10
10
|
DEFAULTS = {
|
11
|
-
:
|
12
|
-
:
|
13
|
-
:
|
14
|
-
:
|
15
|
-
:
|
16
|
-
:
|
17
|
-
:
|
18
|
-
:
|
19
|
-
:
|
20
|
-
:
|
21
|
-
:
|
22
|
-
:
|
23
|
-
:
|
24
|
-
:
|
25
|
-
:
|
26
|
-
|
11
|
+
url: -> { ENV["REDIS_URL"] },
|
12
|
+
scheme: "redis",
|
13
|
+
host: "127.0.0.1",
|
14
|
+
port: 6379,
|
15
|
+
path: nil,
|
16
|
+
read_timeout: nil,
|
17
|
+
write_timeout: nil,
|
18
|
+
connect_timeout: nil,
|
19
|
+
timeout: 5.0,
|
20
|
+
username: nil,
|
21
|
+
password: nil,
|
22
|
+
db: 0,
|
23
|
+
driver: nil,
|
24
|
+
id: nil,
|
25
|
+
tcp_keepalive: 0,
|
26
|
+
reconnect_attempts: 1,
|
27
|
+
reconnect_delay: 0,
|
28
|
+
reconnect_delay_max: 0.5,
|
29
|
+
inherit_socket: false,
|
30
|
+
logger: nil,
|
31
|
+
sentinels: nil,
|
32
|
+
role: nil
|
33
|
+
}.freeze
|
27
34
|
|
28
35
|
attr_reader :options
|
29
36
|
|
@@ -55,6 +62,10 @@ class Redis
|
|
55
62
|
@options[:read_timeout]
|
56
63
|
end
|
57
64
|
|
65
|
+
def username
|
66
|
+
@options[:username]
|
67
|
+
end
|
68
|
+
|
58
69
|
def password
|
59
70
|
@options[:password]
|
60
71
|
end
|
@@ -89,7 +100,7 @@ class Redis
|
|
89
100
|
@pending_reads = 0
|
90
101
|
|
91
102
|
@connector =
|
92
|
-
if options.
|
103
|
+
if !@options[:sentinels].nil?
|
93
104
|
Connector::Sentinel.new(@options)
|
94
105
|
elsif options.include?(:connector) && options[:connector].respond_to?(:new)
|
95
106
|
options.delete(:connector).new(@options)
|
@@ -104,7 +115,33 @@ class Redis
|
|
104
115
|
# Don't try to reconnect when the connection is fresh
|
105
116
|
with_reconnect(false) do
|
106
117
|
establish_connection
|
107
|
-
|
118
|
+
if password
|
119
|
+
if username
|
120
|
+
begin
|
121
|
+
call [:auth, username, password]
|
122
|
+
rescue CommandError => err # Likely on Redis < 6
|
123
|
+
if err.message.match?(/ERR wrong number of arguments for \'auth\' command/)
|
124
|
+
call [:auth, password]
|
125
|
+
elsif err.message.match?(/WRONGPASS invalid username-password pair/)
|
126
|
+
begin
|
127
|
+
call [:auth, password]
|
128
|
+
rescue CommandError
|
129
|
+
raise err
|
130
|
+
end
|
131
|
+
::Kernel.warn(
|
132
|
+
"[redis-rb] The Redis connection was configured with username #{username.inspect}, but" \
|
133
|
+
" the provided password was for the default user. This will start failing in redis-rb 4.6."
|
134
|
+
)
|
135
|
+
else
|
136
|
+
raise
|
137
|
+
end
|
138
|
+
end
|
139
|
+
else
|
140
|
+
call [:auth, password]
|
141
|
+
end
|
142
|
+
end
|
143
|
+
|
144
|
+
call [:readonly] if @options[:readonly]
|
108
145
|
call [:select, db] if db != 0
|
109
146
|
call [:client, :setname, @options[:id]] if @options[:id]
|
110
147
|
@connector.check(self)
|
@@ -125,7 +162,7 @@ class Redis
|
|
125
162
|
reply = process([command]) { read }
|
126
163
|
raise reply if reply.is_a?(CommandError)
|
127
164
|
|
128
|
-
if block_given?
|
165
|
+
if block_given? && reply != 'QUEUED'
|
129
166
|
yield reply
|
130
167
|
else
|
131
168
|
reply
|
@@ -166,6 +203,7 @@ class Redis
|
|
166
203
|
end
|
167
204
|
rescue ConnectionError => e
|
168
205
|
return nil if pipeline.shutdown?
|
206
|
+
|
169
207
|
# Assume the pipeline was sent in one piece, but execution of
|
170
208
|
# SHUTDOWN caused none of the replies for commands that were executed
|
171
209
|
# prior to it from coming back around.
|
@@ -244,13 +282,13 @@ class Redis
|
|
244
282
|
end
|
245
283
|
|
246
284
|
def connected?
|
247
|
-
!!
|
285
|
+
!!(connection && connection.connected?)
|
248
286
|
end
|
249
287
|
|
250
288
|
def disconnect
|
251
289
|
connection.disconnect if connected?
|
252
290
|
end
|
253
|
-
|
291
|
+
alias close disconnect
|
254
292
|
|
255
293
|
def reconnect
|
256
294
|
disconnect
|
@@ -301,30 +339,27 @@ class Redis
|
|
301
339
|
with_socket_timeout(0, &blk)
|
302
340
|
end
|
303
341
|
|
304
|
-
def with_reconnect(val=true)
|
305
|
-
|
306
|
-
|
307
|
-
|
308
|
-
|
309
|
-
@reconnect = original
|
310
|
-
end
|
342
|
+
def with_reconnect(val = true)
|
343
|
+
original, @reconnect = @reconnect, val
|
344
|
+
yield
|
345
|
+
ensure
|
346
|
+
@reconnect = original
|
311
347
|
end
|
312
348
|
|
313
349
|
def without_reconnect(&blk)
|
314
350
|
with_reconnect(false, &blk)
|
315
351
|
end
|
316
352
|
|
317
|
-
|
353
|
+
protected
|
318
354
|
|
319
355
|
def logging(commands)
|
320
|
-
return yield unless @logger
|
356
|
+
return yield unless @logger&.debug?
|
321
357
|
|
322
358
|
begin
|
323
359
|
commands.each do |name, *args|
|
324
360
|
logged_args = args.map do |a|
|
325
|
-
|
326
|
-
|
327
|
-
when a.respond_to?(:to_s) then a.to_s
|
361
|
+
if a.respond_to?(:inspect) then a.inspect
|
362
|
+
elsif a.respond_to?(:to_s) then a.to_s
|
328
363
|
else
|
329
364
|
# handle poorly-behaved descendants of BasicObject
|
330
365
|
klass = a.instance_exec { (class << self; self end).superclass }
|
@@ -358,9 +393,9 @@ class Redis
|
|
358
393
|
Errno::ENETUNREACH,
|
359
394
|
Errno::ENOENT,
|
360
395
|
Errno::ETIMEDOUT,
|
361
|
-
Errno::EINVAL
|
396
|
+
Errno::EINVAL => error
|
362
397
|
|
363
|
-
raise CannotConnectError, "Error connecting to Redis on #{location} (#{
|
398
|
+
raise CannotConnectError, "Error connecting to Redis on #{location} (#{error.class})"
|
364
399
|
end
|
365
400
|
|
366
401
|
def ensure_connected
|
@@ -374,9 +409,9 @@ class Redis
|
|
374
409
|
if connected?
|
375
410
|
unless inherit_socket? || Process.pid == @pid
|
376
411
|
raise InheritedError,
|
377
|
-
|
378
|
-
|
379
|
-
|
412
|
+
"Tried to use a connection from a child process without reconnecting. " \
|
413
|
+
"You need to reconnect to Redis after forking " \
|
414
|
+
"or set :inherit_socket to true."
|
380
415
|
end
|
381
416
|
else
|
382
417
|
connect
|
@@ -387,7 +422,7 @@ class Redis
|
|
387
422
|
disconnect
|
388
423
|
|
389
424
|
if attempts <= @options[:reconnect_attempts] && @reconnect
|
390
|
-
sleep_t = [(@options[:reconnect_delay] * 2**(attempts-1)),
|
425
|
+
sleep_t = [(@options[:reconnect_delay] * 2**(attempts - 1)),
|
391
426
|
@options[:reconnect_delay_max]].min
|
392
427
|
|
393
428
|
Kernel.sleep(sleep_t)
|
@@ -409,16 +444,14 @@ class Redis
|
|
409
444
|
|
410
445
|
defaults.keys.each do |key|
|
411
446
|
# Fill in defaults if needed
|
412
|
-
if defaults[key].respond_to?(:call)
|
413
|
-
defaults[key] = defaults[key].call
|
414
|
-
end
|
447
|
+
defaults[key] = defaults[key].call if defaults[key].respond_to?(:call)
|
415
448
|
|
416
449
|
# Symbolize only keys that are needed
|
417
|
-
options[key] = options[key.to_s] if options.
|
450
|
+
options[key] = options[key.to_s] if options.key?(key.to_s)
|
418
451
|
end
|
419
452
|
|
420
453
|
url = options[:url]
|
421
|
-
url = defaults[:url] if url
|
454
|
+
url = defaults[:url] if url.nil?
|
422
455
|
|
423
456
|
# Override defaults from URL if given
|
424
457
|
if url
|
@@ -427,12 +460,13 @@ class Redis
|
|
427
460
|
uri = URI(url)
|
428
461
|
|
429
462
|
if uri.scheme == "unix"
|
430
|
-
defaults[:path]
|
463
|
+
defaults[:path] = uri.path
|
431
464
|
elsif uri.scheme == "redis" || uri.scheme == "rediss"
|
432
465
|
defaults[:scheme] = uri.scheme
|
433
466
|
defaults[:host] = uri.host if uri.host
|
434
467
|
defaults[:port] = uri.port if uri.port
|
435
|
-
defaults[:
|
468
|
+
defaults[:username] = CGI.unescape(uri.user) if uri.user && !uri.user.empty?
|
469
|
+
defaults[:password] = CGI.unescape(uri.password) if uri.password && !uri.password.empty?
|
436
470
|
defaults[:db] = uri.path[1..-1].to_i if uri.path
|
437
471
|
defaults[:role] = :master
|
438
472
|
else
|
@@ -458,7 +492,7 @@ class Redis
|
|
458
492
|
options[:port] = options[:port].to_i
|
459
493
|
end
|
460
494
|
|
461
|
-
if options.
|
495
|
+
if options.key?(:timeout)
|
462
496
|
options[:connect_timeout] ||= options[:timeout]
|
463
497
|
options[:read_timeout] ||= options[:timeout]
|
464
498
|
options[:write_timeout] ||= options[:timeout]
|
@@ -477,7 +511,7 @@ class Redis
|
|
477
511
|
|
478
512
|
case options[:tcp_keepalive]
|
479
513
|
when Hash
|
480
|
-
[
|
514
|
+
%i[time intvl probes].each do |key|
|
481
515
|
unless options[:tcp_keepalive][key].is_a?(Integer)
|
482
516
|
raise "Expected the #{key.inspect} key in :tcp_keepalive to be an Integer"
|
483
517
|
end
|
@@ -485,13 +519,13 @@ class Redis
|
|
485
519
|
|
486
520
|
when Integer
|
487
521
|
if options[:tcp_keepalive] >= 60
|
488
|
-
options[:tcp_keepalive] = {:
|
522
|
+
options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 20, intvl: 10, probes: 2 }
|
489
523
|
|
490
524
|
elsif options[:tcp_keepalive] >= 30
|
491
|
-
options[:tcp_keepalive] = {:
|
525
|
+
options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 10, intvl: 5, probes: 2 }
|
492
526
|
|
493
527
|
elsif options[:tcp_keepalive] >= 5
|
494
|
-
options[:tcp_keepalive] = {:
|
528
|
+
options[:tcp_keepalive] = { time: options[:tcp_keepalive] - 2, intvl: 2, probes: 1 }
|
495
529
|
end
|
496
530
|
end
|
497
531
|
|
@@ -503,14 +537,14 @@ class Redis
|
|
503
537
|
def _parse_driver(driver)
|
504
538
|
driver = driver.to_s if driver.is_a?(Symbol)
|
505
539
|
|
506
|
-
if driver.
|
540
|
+
if driver.is_a?(String)
|
507
541
|
begin
|
508
542
|
require_relative "connection/#{driver}"
|
509
|
-
rescue LoadError, NameError
|
543
|
+
rescue LoadError, NameError
|
510
544
|
begin
|
511
|
-
require "connection/#{driver}"
|
512
|
-
rescue LoadError, NameError =>
|
513
|
-
raise
|
545
|
+
require "redis/connection/#{driver}"
|
546
|
+
rescue LoadError, NameError => error
|
547
|
+
raise "Cannot load driver #{driver.inspect}: #{error.message}"
|
514
548
|
end
|
515
549
|
end
|
516
550
|
|
@@ -529,8 +563,7 @@ class Redis
|
|
529
563
|
@options
|
530
564
|
end
|
531
565
|
|
532
|
-
def check(client)
|
533
|
-
end
|
566
|
+
def check(client); end
|
534
567
|
|
535
568
|
class Sentinel < Connector
|
536
569
|
def initialize(options)
|
@@ -539,7 +572,7 @@ class Redis
|
|
539
572
|
@options[:db] = DEFAULTS.fetch(:db)
|
540
573
|
|
541
574
|
@sentinels = @options.delete(:sentinels).dup
|
542
|
-
@role = @options
|
575
|
+
@role = (@options[:role] || "master").to_s
|
543
576
|
@master = @options[:host]
|
544
577
|
end
|
545
578
|
|
@@ -562,13 +595,13 @@ class Redis
|
|
562
595
|
|
563
596
|
def resolve
|
564
597
|
result = case @role
|
565
|
-
|
566
|
-
|
567
|
-
|
568
|
-
|
569
|
-
|
570
|
-
|
571
|
-
|
598
|
+
when "master"
|
599
|
+
resolve_master
|
600
|
+
when "slave"
|
601
|
+
resolve_slave
|
602
|
+
else
|
603
|
+
raise ArgumentError, "Unknown instance role #{@role}"
|
604
|
+
end
|
572
605
|
|
573
606
|
result || (raise ConnectionError, "Unable to fetch #{@role} via Sentinel.")
|
574
607
|
end
|
@@ -576,11 +609,12 @@ class Redis
|
|
576
609
|
def sentinel_detect
|
577
610
|
@sentinels.each do |sentinel|
|
578
611
|
client = Client.new(@options.merge({
|
579
|
-
|
580
|
-
|
581
|
-
|
582
|
-
|
583
|
-
|
612
|
+
host: sentinel[:host] || sentinel["host"],
|
613
|
+
port: sentinel[:port] || sentinel["port"],
|
614
|
+
username: sentinel[:username] || sentinel["username"],
|
615
|
+
password: sentinel[:password] || sentinel["password"],
|
616
|
+
reconnect_attempts: 0
|
617
|
+
}))
|
584
618
|
|
585
619
|
begin
|
586
620
|
if result = yield(client)
|
@@ -602,7 +636,7 @@ class Redis
|
|
602
636
|
def resolve_master
|
603
637
|
sentinel_detect do |client|
|
604
638
|
if reply = client.call(["sentinel", "get-master-addr-by-name", @master])
|
605
|
-
{:
|
639
|
+
{ host: reply[0], port: reply[1] }
|
606
640
|
end
|
607
641
|
end
|
608
642
|
end
|
@@ -620,7 +654,7 @@ class Redis
|
|
620
654
|
slave = slaves.sample
|
621
655
|
{
|
622
656
|
host: slave.fetch('ip'),
|
623
|
-
port: slave.fetch('port')
|
657
|
+
port: slave.fetch('port')
|
624
658
|
}
|
625
659
|
end
|
626
660
|
end
|
@@ -10,22 +10,21 @@ class Redis
|
|
10
10
|
module_function
|
11
11
|
|
12
12
|
def load(nodes)
|
13
|
-
details = {}
|
14
|
-
|
15
13
|
nodes.each do |node|
|
16
|
-
|
17
|
-
|
14
|
+
begin
|
15
|
+
return fetch_command_details(node)
|
16
|
+
rescue CannotConnectError, ConnectionError, CommandError
|
17
|
+
next # can retry on another node
|
18
|
+
end
|
18
19
|
end
|
19
20
|
|
20
|
-
|
21
|
+
raise CannotConnectError, 'Redis client could not connect to any cluster nodes'
|
21
22
|
end
|
22
23
|
|
23
24
|
def fetch_command_details(node)
|
24
25
|
node.call(%i[command]).map do |reply|
|
25
26
|
[reply[0], { arity: reply[1], flags: reply[2], first: reply[3], last: reply[4], step: reply[5] }]
|
26
27
|
end.to_h
|
27
|
-
rescue CannotConnectError, ConnectionError, CommandError
|
28
|
-
{} # can retry on another node
|
29
28
|
end
|
30
29
|
|
31
30
|
private_class_method :fetch_command_details
|
data/lib/redis/cluster/node.rb
CHANGED
@@ -39,6 +39,7 @@ class Redis
|
|
39
39
|
def call_master(command, &block)
|
40
40
|
try_map do |node_key, client|
|
41
41
|
next if slave?(node_key)
|
42
|
+
|
42
43
|
client.call(command, &block)
|
43
44
|
end.values
|
44
45
|
end
|
@@ -48,6 +49,7 @@ class Redis
|
|
48
49
|
|
49
50
|
try_map do |node_key, client|
|
50
51
|
next if master?(node_key)
|
52
|
+
|
51
53
|
client.call(command, &block)
|
52
54
|
end.values
|
53
55
|
end
|
@@ -74,8 +76,9 @@ class Redis
|
|
74
76
|
clients = options.map do |node_key, option|
|
75
77
|
next if replica_disabled? && slave?(node_key)
|
76
78
|
|
79
|
+
option = option.merge(readonly: true) if slave?(node_key)
|
80
|
+
|
77
81
|
client = Client.new(option)
|
78
|
-
client.call(%i[readonly]) if slave?(node_key)
|
79
82
|
[node_key, client]
|
80
83
|
end
|
81
84
|
|
@@ -97,6 +100,7 @@ class Redis
|
|
97
100
|
end
|
98
101
|
|
99
102
|
return results if errors.empty?
|
103
|
+
|
100
104
|
raise CommandErrorCollection, errors
|
101
105
|
end
|
102
106
|
end
|
data/lib/redis/cluster/option.rb
CHANGED
@@ -18,6 +18,7 @@ class Redis
|
|
18
18
|
@node_opts = build_node_options(node_addrs)
|
19
19
|
@replica = options.delete(:replica) == true
|
20
20
|
add_common_node_option_if_needed(options, @node_opts, :scheme)
|
21
|
+
add_common_node_option_if_needed(options, @node_opts, :username)
|
21
22
|
add_common_node_option_if_needed(options, @node_opts, :password)
|
22
23
|
@options = options
|
23
24
|
end
|
@@ -43,6 +44,7 @@ class Redis
|
|
43
44
|
|
44
45
|
def build_node_options(addrs)
|
45
46
|
raise InvalidClientOptionError, 'Redis option of `cluster` must be an Array' unless addrs.is_a?(Array)
|
47
|
+
|
46
48
|
addrs.map { |addr| parse_node_addr(addr) }
|
47
49
|
end
|
48
50
|
|
@@ -62,21 +64,25 @@ class Redis
|
|
62
64
|
raise InvalidClientOptionError, "Invalid uri scheme #{addr}" unless VALID_SCHEMES.include?(uri.scheme)
|
63
65
|
|
64
66
|
db = uri.path.split('/')[1]&.to_i
|
65
|
-
|
67
|
+
|
68
|
+
{ scheme: uri.scheme, username: uri.user, password: uri.password, host: uri.host, port: uri.port, db: db }
|
69
|
+
.reject { |_, v| v.nil? || v == '' }
|
66
70
|
rescue URI::InvalidURIError => err
|
67
71
|
raise InvalidClientOptionError, err.message
|
68
72
|
end
|
69
73
|
|
70
74
|
def parse_node_option(addr)
|
71
75
|
addr = addr.map { |k, v| [k.to_sym, v] }.to_h
|
72
|
-
|
76
|
+
if addr.values_at(:host, :port).any?(&:nil?)
|
77
|
+
raise InvalidClientOptionError, 'Redis option of `cluster` must includes `:host` and `:port` keys'
|
78
|
+
end
|
73
79
|
|
74
80
|
addr
|
75
81
|
end
|
76
82
|
|
77
83
|
# Redis cluster node returns only host and port information.
|
78
84
|
# So we should complement additional information such as:
|
79
|
-
# scheme, password and so on.
|
85
|
+
# scheme, username, password and so on.
|
80
86
|
def add_common_node_option_if_needed(options, node_opts, key)
|
81
87
|
return options if options[key].nil? && node_opts.first[key].nil?
|
82
88
|
|