redis 4.2.5 → 4.8.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +113 -0
- data/README.md +41 -21
- data/lib/redis/client.rb +52 -27
- data/lib/redis/cluster/command.rb +4 -6
- data/lib/redis/cluster/command_loader.rb +8 -9
- data/lib/redis/cluster/node.rb +14 -1
- data/lib/redis/cluster/node_loader.rb +8 -11
- data/lib/redis/cluster/option.rb +14 -4
- data/lib/redis/cluster/slot_loader.rb +9 -12
- data/lib/redis/cluster.rb +33 -13
- data/lib/redis/commands/bitmaps.rb +63 -0
- data/lib/redis/commands/cluster.rb +45 -0
- data/lib/redis/commands/connection.rb +58 -0
- data/lib/redis/commands/geo.rb +84 -0
- data/lib/redis/commands/hashes.rb +251 -0
- data/lib/redis/commands/hyper_log_log.rb +37 -0
- data/lib/redis/commands/keys.rb +455 -0
- data/lib/redis/commands/lists.rb +290 -0
- data/lib/redis/commands/pubsub.rb +72 -0
- data/lib/redis/commands/scripting.rb +114 -0
- data/lib/redis/commands/server.rb +188 -0
- data/lib/redis/commands/sets.rb +223 -0
- data/lib/redis/commands/sorted_sets.rb +812 -0
- data/lib/redis/commands/streams.rb +382 -0
- data/lib/redis/commands/strings.rb +313 -0
- data/lib/redis/commands/transactions.rb +139 -0
- data/lib/redis/commands.rb +240 -0
- data/lib/redis/connection/command_helper.rb +2 -0
- data/lib/redis/connection/hiredis.rb +3 -2
- data/lib/redis/connection/ruby.rb +19 -9
- data/lib/redis/connection/synchrony.rb +10 -8
- data/lib/redis/connection.rb +1 -1
- data/lib/redis/distributed.rb +124 -29
- data/lib/redis/errors.rb +9 -0
- data/lib/redis/pipeline.rb +128 -3
- data/lib/redis/version.rb +1 -1
- data/lib/redis.rb +139 -3377
- metadata +22 -5
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: b18788ff80698e8f79fb103e7419d9ba74fb0b6a5eb55c672422cd76abff985c
|
4
|
+
data.tar.gz: 1eed18a57039c677c894564ceaa1cf6bc9c0535be51501d078b09d75617a9d89
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5f2f7ce595d431f548c126a63c5ce697cc9e596e9b0eaa00f1e0186ae3853e73922c6e130b989ba9e026aec32f766a774433fdd1cff216420e837837db90659b
|
7
|
+
data.tar.gz: 02fb8debb0d11f7b9d04f7616093535426f0ee5a0994992fcf3187ab00aa890dcd2fb248f14da49837508a3ccbb5756be208fc5d3b8dcd18ec6aaf0fb1bb1193
|
data/CHANGELOG.md
CHANGED
@@ -1,5 +1,118 @@
|
|
1
1
|
# Unreleased
|
2
2
|
|
3
|
+
# 4.8.1
|
4
|
+
|
5
|
+
* Automatically reconnect after fork regardless of `reconnect_attempts`
|
6
|
+
|
7
|
+
# 4.8.0
|
8
|
+
|
9
|
+
* Introduce `sadd?` and `srem?` as boolean returning versions of `sadd` and `srem`.
|
10
|
+
* Deprecate `sadd` and `srem` returning a boolean when called with a single argument.
|
11
|
+
To enable the redis 5.0 behavior you can set `Redis.sadd_returns_boolean = false`.
|
12
|
+
* Deprecate passing `timeout` as a positional argument in blocking commands (`brpop`, `blop`, etc).
|
13
|
+
|
14
|
+
# 4.7.1
|
15
|
+
|
16
|
+
* Gracefully handle OpenSSL 3.0 EOF Errors (`OpenSSL::SSL::SSLError: SSL_read: unexpected eof while reading`). See #1106
|
17
|
+
This happens frequently on heroku-22.
|
18
|
+
|
19
|
+
# 4.7.0
|
20
|
+
|
21
|
+
* Support single endpoint architecture with SSL/TLS in cluster mode. See #1086.
|
22
|
+
* `zrem` and `zadd` act as noop when provided an empty list of keys. See #1097.
|
23
|
+
* Support IPv6 URLs.
|
24
|
+
* Add `Redis#with` for better compatibility with `connection_pool` usage.
|
25
|
+
* Fix the block form of `multi` called inside `pipelined`. Previously the `MUTLI/EXEC` wouldn't be sent. See #1073.
|
26
|
+
|
27
|
+
# 4.6.0
|
28
|
+
|
29
|
+
* Deprecate `Redis.current`.
|
30
|
+
* Deprecate calling commands on `Redis` inside `Redis#pipelined`. See #1059.
|
31
|
+
```ruby
|
32
|
+
redis.pipelined do
|
33
|
+
redis.get("key")
|
34
|
+
end
|
35
|
+
```
|
36
|
+
|
37
|
+
should be replaced by:
|
38
|
+
|
39
|
+
```ruby
|
40
|
+
redis.pipelined do |pipeline|
|
41
|
+
pipeline.get("key")
|
42
|
+
end
|
43
|
+
```
|
44
|
+
* Deprecate calling commands on `Redis` inside `Redis#multi`. See #1059.
|
45
|
+
```ruby
|
46
|
+
redis.multi do
|
47
|
+
redis.get("key")
|
48
|
+
end
|
49
|
+
```
|
50
|
+
|
51
|
+
should be replaced by:
|
52
|
+
|
53
|
+
```ruby
|
54
|
+
redis.multi do |transaction|
|
55
|
+
transaction.get("key")
|
56
|
+
end
|
57
|
+
```
|
58
|
+
* Deprecate `Redis#queue` and `Redis#commit`. See #1059.
|
59
|
+
|
60
|
+
* Fix `zpopmax` and `zpopmin` when called inside a pipeline. See #1055.
|
61
|
+
* `Redis#synchronize` is now private like it should always have been.
|
62
|
+
|
63
|
+
* Add `Redis.silence_deprecations=` to turn off deprecation warnings.
|
64
|
+
If you don't wish to see warnings yet, you can set `Redis.silence_deprecations = true`.
|
65
|
+
It is however heavily recommended to fix them instead when possible.
|
66
|
+
* Add `Redis.raise_deprecations=` to turn deprecation warnings into errors.
|
67
|
+
This makes it easier to identitify the source of deprecated APIs usage.
|
68
|
+
It is recommended to set `Redis.raise_deprecations = true` in development and test environments.
|
69
|
+
* Add new options to ZRANGE. See #1053.
|
70
|
+
* Add ZRANGESTORE command. See #1053.
|
71
|
+
* Add SCAN support for `Redis::Cluster`. See #1049.
|
72
|
+
* Add COPY command. See #1053. See #1048.
|
73
|
+
* Add ZDIFFSTORE command. See #1046.
|
74
|
+
* Add ZDIFF command. See #1044.
|
75
|
+
* Add ZUNION command. See #1042.
|
76
|
+
* Add HRANDFIELD command. See #1040.
|
77
|
+
|
78
|
+
# 4.5.1
|
79
|
+
|
80
|
+
* 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,
|
81
|
+
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.
|
82
|
+
This behavior is deprecated and will be removed in Redis 4.6.0. Fix #1038.
|
83
|
+
|
84
|
+
# 4.5.0
|
85
|
+
|
86
|
+
* Handle parts of the command using incompatible encodings. See #1037.
|
87
|
+
* Add GET option to SET command. See #1036.
|
88
|
+
* Add ZRANDMEMBER command. See #1035.
|
89
|
+
* Add LMOVE/BLMOVE commands. See #1034.
|
90
|
+
* Add ZMSCORE command. See #1032.
|
91
|
+
* Add LT/GT options to ZADD. See #1033.
|
92
|
+
* Add SMISMEMBER command. See #1031.
|
93
|
+
* Add EXAT/PXAT options to SET. See #1028.
|
94
|
+
* Add GETDEL/GETEX commands. See #1024.
|
95
|
+
* `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`.
|
96
|
+
* Fix Redis < 6 detection during connect. See #1025.
|
97
|
+
* Fix fetching command details in Redis cluster when the first node is unhealthy. See #1026.
|
98
|
+
|
99
|
+
# 4.4.0
|
100
|
+
|
101
|
+
* Redis cluster: fix cross-slot validation in pipelines. Fix ##1019.
|
102
|
+
* Add support for `XAUTOCLAIM`. See #1018.
|
103
|
+
* Properly issue `READONLY` when reconnecting to replicas. Fix #1017.
|
104
|
+
* Make `del` a noop if passed an empty list of keys. See #998.
|
105
|
+
* Add support for `ZINTER`. See #995.
|
106
|
+
|
107
|
+
# 4.3.1
|
108
|
+
|
109
|
+
* Fix password authentication against redis server 5 and older.
|
110
|
+
|
111
|
+
# 4.3.0
|
112
|
+
|
113
|
+
* Add the TYPE argument to scan and scan_each. See #985.
|
114
|
+
* Support AUTH command for ACL. See #967.
|
115
|
+
|
3
116
|
# 4.2.5
|
4
117
|
|
5
118
|
* Optimize the ruby connector write buffering. See #964.
|
data/README.md
CHANGED
@@ -1,4 +1,4 @@
|
|
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.
|
@@ -54,6 +54,12 @@ To connect to a password protected Redis instance, use:
|
|
54
54
|
redis = Redis.new(password: "mysecret")
|
55
55
|
```
|
56
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
|
+
|
57
63
|
The Redis class exports methods that are named identical to the commands
|
58
64
|
they execute. The arguments these methods accept are often identical to
|
59
65
|
the arguments specified on the [Redis website][redis-commands]. For
|
@@ -149,6 +155,21 @@ redis.mget('{key}1', '{key}2')
|
|
149
155
|
* The client support permanent node failures, and will reroute requests to promoted slaves.
|
150
156
|
* The client supports `MOVED` and `ASK` redirections transparently.
|
151
157
|
|
158
|
+
## Cluster mode with SSL/TLS
|
159
|
+
Since Redis can return FQDN of nodes in reply to client since `7.*` with CLUSTER commands, we can use cluster feature with SSL/TLS connection like this:
|
160
|
+
|
161
|
+
```ruby
|
162
|
+
Redis.new(cluster: %w[rediss://foo.example.com:6379])
|
163
|
+
```
|
164
|
+
|
165
|
+
On the other hand, in Redis versions prior to `6.*`, you can specify options like the following if cluster mode is enabled and client has to connect to nodes via single endpoint with SSL/TLS.
|
166
|
+
|
167
|
+
```ruby
|
168
|
+
Redis.new(cluster: %w[rediss://foo-endpoint.example.com:6379], fixed_hostname: 'foo-endpoint.example.com')
|
169
|
+
```
|
170
|
+
|
171
|
+
In case of the above architecture, if you don't pass the `fixed_hostname` option to the client and servers return IP addresses of nodes, the client may fail to verify certificates.
|
172
|
+
|
152
173
|
## Storing objects
|
153
174
|
|
154
175
|
Redis "string" types can be used to store serialized Ruby objects, for
|
@@ -178,9 +199,9 @@ commands to Redis and gathers their replies. These replies are returned
|
|
178
199
|
by the `#pipelined` method.
|
179
200
|
|
180
201
|
```ruby
|
181
|
-
redis.pipelined do
|
182
|
-
|
183
|
-
|
202
|
+
redis.pipelined do |pipeline|
|
203
|
+
pipeline.set "foo", "bar"
|
204
|
+
pipeline.incr "baz"
|
184
205
|
end
|
185
206
|
# => ["OK", 1]
|
186
207
|
```
|
@@ -194,9 +215,9 @@ the regular pipeline, the replies to the commands are returned by the
|
|
194
215
|
`#multi` method.
|
195
216
|
|
196
217
|
```ruby
|
197
|
-
redis.multi do
|
198
|
-
|
199
|
-
|
218
|
+
redis.multi do |transaction|
|
219
|
+
transaction.set "foo", "bar"
|
220
|
+
transaction.incr "baz"
|
200
221
|
end
|
201
222
|
# => ["OK", 1]
|
202
223
|
```
|
@@ -204,15 +225,15 @@ end
|
|
204
225
|
### Futures
|
205
226
|
|
206
227
|
Replies to commands in a pipeline can be accessed via the *futures* they
|
207
|
-
emit (since redis-rb 3.0). All calls
|
228
|
+
emit (since redis-rb 3.0). All calls on the pipeline object return a
|
208
229
|
`Future` object, which responds to the `#value` method. When the
|
209
230
|
pipeline has successfully executed, all futures are assigned their
|
210
231
|
respective replies and can be used.
|
211
232
|
|
212
233
|
```ruby
|
213
|
-
redis.pipelined do
|
214
|
-
@set =
|
215
|
-
@incr =
|
234
|
+
redis.pipelined do |pipeline|
|
235
|
+
@set = pipeline.set "foo", "bar"
|
236
|
+
@incr = pipeline.incr "baz"
|
216
237
|
end
|
217
238
|
|
218
239
|
@set.value
|
@@ -440,7 +461,7 @@ redis = Redis.new(:driver => :synchrony)
|
|
440
461
|
## Testing
|
441
462
|
|
442
463
|
This library is tested against recent Ruby and Redis versions.
|
443
|
-
Check [
|
464
|
+
Check [Github Actions][gh-actions-link] for the exact versions supported.
|
444
465
|
|
445
466
|
## See Also
|
446
467
|
|
@@ -459,12 +480,11 @@ client and evangelized Redis in Rubyland. Thank you, Ezra.
|
|
459
480
|
requests.
|
460
481
|
|
461
482
|
|
462
|
-
[inchpages-image]:
|
463
|
-
[inchpages-link]:
|
464
|
-
[redis-commands]:
|
465
|
-
[redis-home]:
|
466
|
-
[redis-url]:
|
467
|
-
[
|
468
|
-
[
|
469
|
-
[
|
470
|
-
[rubydoc]: http://www.rubydoc.info/gems/redis
|
483
|
+
[inchpages-image]: https://inch-ci.org/github/redis/redis-rb.svg
|
484
|
+
[inchpages-link]: https://inch-ci.org/github/redis/redis-rb
|
485
|
+
[redis-commands]: https://redis.io/commands
|
486
|
+
[redis-home]: https://redis.io
|
487
|
+
[redis-url]: http://www.iana.org/assignments/uri-schemes/prov/redis
|
488
|
+
[gh-actions-image]: https://github.com/redis/redis-rb/workflows/Test/badge.svg
|
489
|
+
[gh-actions-link]: https://github.com/redis/redis-rb/actions
|
490
|
+
[rubydoc]: http://www.rubydoc.info/gems/redis
|
data/lib/redis/client.rb
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative "errors"
|
4
3
|
require "socket"
|
5
4
|
require "cgi"
|
5
|
+
require "redis/errors"
|
6
6
|
|
7
7
|
class Redis
|
8
8
|
class Client
|
@@ -17,6 +17,7 @@ class Redis
|
|
17
17
|
write_timeout: nil,
|
18
18
|
connect_timeout: nil,
|
19
19
|
timeout: 5.0,
|
20
|
+
username: nil,
|
20
21
|
password: nil,
|
21
22
|
db: 0,
|
22
23
|
driver: nil,
|
@@ -31,7 +32,7 @@ class Redis
|
|
31
32
|
role: nil
|
32
33
|
}.freeze
|
33
34
|
|
34
|
-
attr_reader :options
|
35
|
+
attr_reader :options, :connection, :command_map
|
35
36
|
|
36
37
|
def scheme
|
37
38
|
@options[:scheme]
|
@@ -61,6 +62,10 @@ class Redis
|
|
61
62
|
@options[:read_timeout]
|
62
63
|
end
|
63
64
|
|
65
|
+
def username
|
66
|
+
@options[:username]
|
67
|
+
end
|
68
|
+
|
64
69
|
def password
|
65
70
|
@options[:password]
|
66
71
|
end
|
@@ -82,8 +87,6 @@ class Redis
|
|
82
87
|
end
|
83
88
|
|
84
89
|
attr_accessor :logger
|
85
|
-
attr_reader :connection
|
86
|
-
attr_reader :command_map
|
87
90
|
|
88
91
|
def initialize(options = {})
|
89
92
|
@options = _parse_options(options)
|
@@ -110,7 +113,34 @@ class Redis
|
|
110
113
|
# Don't try to reconnect when the connection is fresh
|
111
114
|
with_reconnect(false) do
|
112
115
|
establish_connection
|
113
|
-
|
116
|
+
if password
|
117
|
+
if username
|
118
|
+
begin
|
119
|
+
call [:auth, username, password]
|
120
|
+
rescue CommandError => err # Likely on Redis < 6
|
121
|
+
case err.message
|
122
|
+
when /ERR wrong number of arguments for 'auth' command/
|
123
|
+
call [:auth, password]
|
124
|
+
when /WRONGPASS invalid username-password pair/
|
125
|
+
begin
|
126
|
+
call [:auth, password]
|
127
|
+
rescue CommandError
|
128
|
+
raise err
|
129
|
+
end
|
130
|
+
::Redis.deprecate!(
|
131
|
+
"[redis-rb] The Redis connection was configured with username #{username.inspect}, but" \
|
132
|
+
" the provided password was for the default user. This will start failing in redis-rb 5.0.0."
|
133
|
+
)
|
134
|
+
else
|
135
|
+
raise
|
136
|
+
end
|
137
|
+
end
|
138
|
+
else
|
139
|
+
call [:auth, password]
|
140
|
+
end
|
141
|
+
end
|
142
|
+
|
143
|
+
call [:readonly] if @options[:readonly]
|
114
144
|
call [:select, db] if db != 0
|
115
145
|
call [:client, :setname, @options[:id]] if @options[:id]
|
116
146
|
@connector.check(self)
|
@@ -120,7 +150,7 @@ class Redis
|
|
120
150
|
end
|
121
151
|
|
122
152
|
def id
|
123
|
-
@options[:id] || "
|
153
|
+
@options[:id] || "#{@options[:ssl] ? 'rediss' : @options[:scheme]}://#{location}/#{db}"
|
124
154
|
end
|
125
155
|
|
126
156
|
def location
|
@@ -131,7 +161,7 @@ class Redis
|
|
131
161
|
reply = process([command]) { read }
|
132
162
|
raise reply if reply.is_a?(CommandError)
|
133
163
|
|
134
|
-
if block_given?
|
164
|
+
if block_given? && reply != 'QUEUED'
|
135
165
|
yield reply
|
136
166
|
else
|
137
167
|
reply
|
@@ -221,7 +251,8 @@ class Redis
|
|
221
251
|
result
|
222
252
|
end
|
223
253
|
|
224
|
-
def call_with_timeout(command,
|
254
|
+
def call_with_timeout(command, extra_timeout, &blk)
|
255
|
+
timeout = extra_timeout == 0 ? 0 : self.timeout + extra_timeout
|
225
256
|
with_socket_timeout(timeout) do
|
226
257
|
call(command, &blk)
|
227
258
|
end
|
@@ -271,7 +302,7 @@ class Redis
|
|
271
302
|
e2 = TimeoutError.new("Connection timed out")
|
272
303
|
e2.set_backtrace(e1.backtrace)
|
273
304
|
raise e2
|
274
|
-
rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED, Errno::EBADF, Errno::EINVAL => e
|
305
|
+
rescue Errno::ECONNRESET, Errno::EPIPE, Errno::ECONNABORTED, Errno::EBADF, Errno::EINVAL, EOFError => e
|
275
306
|
raise ConnectionError, "Connection lost (%s)" % [e.class.name.split("::").last]
|
276
307
|
end
|
277
308
|
|
@@ -368,23 +399,14 @@ class Redis
|
|
368
399
|
end
|
369
400
|
|
370
401
|
def ensure_connected
|
371
|
-
disconnect if @pending_reads > 0
|
402
|
+
disconnect if @pending_reads > 0 || (@pid != Process.pid && !inherit_socket?)
|
372
403
|
|
373
404
|
attempts = 0
|
374
405
|
|
375
406
|
begin
|
376
407
|
attempts += 1
|
377
408
|
|
378
|
-
|
379
|
-
unless inherit_socket? || Process.pid == @pid
|
380
|
-
raise InheritedError,
|
381
|
-
"Tried to use a connection from a child process without reconnecting. " \
|
382
|
-
"You need to reconnect to Redis after forking " \
|
383
|
-
"or set :inherit_socket to true."
|
384
|
-
end
|
385
|
-
else
|
386
|
-
connect
|
387
|
-
end
|
409
|
+
connect unless connected?
|
388
410
|
|
389
411
|
yield
|
390
412
|
rescue BaseConnectionError
|
@@ -411,7 +433,7 @@ class Redis
|
|
411
433
|
defaults = DEFAULTS.dup
|
412
434
|
options = options.dup
|
413
435
|
|
414
|
-
defaults.
|
436
|
+
defaults.each_key do |key|
|
415
437
|
# Fill in defaults if needed
|
416
438
|
defaults[key] = defaults[key].call if defaults[key].respond_to?(:call)
|
417
439
|
|
@@ -428,13 +450,15 @@ class Redis
|
|
428
450
|
|
429
451
|
uri = URI(url)
|
430
452
|
|
431
|
-
|
453
|
+
case uri.scheme
|
454
|
+
when "unix"
|
432
455
|
defaults[:path] = uri.path
|
433
|
-
|
456
|
+
when "redis", "rediss"
|
434
457
|
defaults[:scheme] = uri.scheme
|
435
|
-
defaults[:host] = uri.host if uri.host
|
458
|
+
defaults[:host] = uri.host.sub(/\A\[(.*)\]\z/, '\1') if uri.host
|
436
459
|
defaults[:port] = uri.port if uri.port
|
437
|
-
defaults[:
|
460
|
+
defaults[:username] = CGI.unescape(uri.user) if uri.user && !uri.user.empty?
|
461
|
+
defaults[:password] = CGI.unescape(uri.password) if uri.password && !uri.password.empty?
|
438
462
|
defaults[:db] = uri.path[1..-1].to_i if uri.path
|
439
463
|
defaults[:role] = :master
|
440
464
|
else
|
@@ -445,7 +469,7 @@ class Redis
|
|
445
469
|
end
|
446
470
|
|
447
471
|
# Use default when option is not specified or nil
|
448
|
-
defaults.
|
472
|
+
defaults.each_key do |key|
|
449
473
|
options[key] = defaults[key] if options[key].nil?
|
450
474
|
end
|
451
475
|
|
@@ -510,7 +534,7 @@ class Redis
|
|
510
534
|
require_relative "connection/#{driver}"
|
511
535
|
rescue LoadError, NameError
|
512
536
|
begin
|
513
|
-
require "connection/#{driver}"
|
537
|
+
require "redis/connection/#{driver}"
|
514
538
|
rescue LoadError, NameError => error
|
515
539
|
raise "Cannot load driver #{driver.inspect}: #{error.message}"
|
516
540
|
end
|
@@ -579,6 +603,7 @@ class Redis
|
|
579
603
|
client = Client.new(@options.merge({
|
580
604
|
host: sentinel[:host] || sentinel["host"],
|
581
605
|
port: sentinel[:port] || sentinel["port"],
|
606
|
+
username: sentinel[:username] || sentinel["username"],
|
582
607
|
password: sentinel[:password] || sentinel["password"],
|
583
608
|
reconnect_attempts: 0
|
584
609
|
}))
|
@@ -31,13 +31,13 @@ class Redis
|
|
31
31
|
private
|
32
32
|
|
33
33
|
def pick_details(details)
|
34
|
-
details.
|
35
|
-
|
34
|
+
details.transform_values do |detail|
|
35
|
+
{
|
36
36
|
first_key_position: detail[:first],
|
37
37
|
write: detail[:flags].include?('write'),
|
38
38
|
readonly: detail[:flags].include?('readonly')
|
39
|
-
}
|
40
|
-
end
|
39
|
+
}
|
40
|
+
end
|
41
41
|
end
|
42
42
|
|
43
43
|
def dig_details(command, key)
|
@@ -53,8 +53,6 @@ class Redis
|
|
53
53
|
when 'object' then 2
|
54
54
|
when 'memory'
|
55
55
|
command[1].to_s.casecmp('usage').zero? ? 2 : 0
|
56
|
-
when 'scan', 'sscan', 'hscan', 'zscan'
|
57
|
-
determine_optional_key_position(command, 'match')
|
58
56
|
when 'xread', 'xreadgroup'
|
59
57
|
determine_optional_key_position(command, 'streams')
|
60
58
|
else
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require 'redis/errors'
|
4
4
|
|
5
5
|
class Redis
|
6
6
|
class Cluster
|
@@ -10,22 +10,21 @@ class Redis
|
|
10
10
|
module_function
|
11
11
|
|
12
12
|
def load(nodes)
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
13
|
+
errors = nodes.map do |node|
|
14
|
+
begin
|
15
|
+
return fetch_command_details(node)
|
16
|
+
rescue CannotConnectError, ConnectionError, CommandError => error
|
17
|
+
error
|
18
|
+
end
|
18
19
|
end
|
19
20
|
|
20
|
-
|
21
|
+
raise InitialSetupError, errors
|
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
@@ -58,6 +58,18 @@ class Redis
|
|
58
58
|
try_map { |_, client| client.process(commands, &block) }.values
|
59
59
|
end
|
60
60
|
|
61
|
+
def scale_reading_clients
|
62
|
+
reading_clients = []
|
63
|
+
|
64
|
+
@clients.each do |node_key, client|
|
65
|
+
next unless replica_disabled? ? master?(node_key) : slave?(node_key)
|
66
|
+
|
67
|
+
reading_clients << client
|
68
|
+
end
|
69
|
+
|
70
|
+
reading_clients
|
71
|
+
end
|
72
|
+
|
61
73
|
private
|
62
74
|
|
63
75
|
def replica_disabled?
|
@@ -76,8 +88,9 @@ class Redis
|
|
76
88
|
clients = options.map do |node_key, option|
|
77
89
|
next if replica_disabled? && slave?(node_key)
|
78
90
|
|
91
|
+
option = option.merge(readonly: true) if slave?(node_key)
|
92
|
+
|
79
93
|
client = Client.new(option)
|
80
|
-
client.call(%i[readonly]) if slave?(node_key)
|
81
94
|
[node_key, client]
|
82
95
|
end
|
83
96
|
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
3
|
+
require 'redis/errors'
|
4
4
|
|
5
5
|
class Redis
|
6
6
|
class Cluster
|
@@ -9,16 +9,15 @@ class Redis
|
|
9
9
|
module_function
|
10
10
|
|
11
11
|
def load_flags(nodes)
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
12
|
+
errors = nodes.map do |node|
|
13
|
+
begin
|
14
|
+
return fetch_node_info(node)
|
15
|
+
rescue CannotConnectError, ConnectionError, CommandError => error
|
16
|
+
error
|
17
|
+
end
|
17
18
|
end
|
18
19
|
|
19
|
-
|
20
|
-
|
21
|
-
raise CannotConnectError, 'Redis client could not connect to any cluster nodes'
|
20
|
+
raise InitialSetupError, errors
|
22
21
|
end
|
23
22
|
|
24
23
|
def fetch_node_info(node)
|
@@ -27,8 +26,6 @@ class Redis
|
|
27
26
|
.map { |str| str.split(' ') }
|
28
27
|
.map { |arr| [arr[1].split('@').first, (arr[2].split(',') & %w[master slave]).first] }
|
29
28
|
.to_h
|
30
|
-
rescue CannotConnectError, ConnectionError, CommandError
|
31
|
-
{} # can retry on another node
|
32
29
|
end
|
33
30
|
|
34
31
|
private_class_method :fetch_node_info
|
data/lib/redis/cluster/option.rb
CHANGED
@@ -17,14 +17,20 @@ class Redis
|
|
17
17
|
node_addrs = options.delete(:cluster)
|
18
18
|
@node_opts = build_node_options(node_addrs)
|
19
19
|
@replica = options.delete(:replica) == true
|
20
|
+
@fixed_hostname = options.delete(:fixed_hostname)
|
20
21
|
add_common_node_option_if_needed(options, @node_opts, :scheme)
|
22
|
+
add_common_node_option_if_needed(options, @node_opts, :username)
|
21
23
|
add_common_node_option_if_needed(options, @node_opts, :password)
|
22
24
|
@options = options
|
23
25
|
end
|
24
26
|
|
25
27
|
def per_node_key
|
26
|
-
@node_opts.map
|
27
|
-
|
28
|
+
@node_opts.map do |opt|
|
29
|
+
node_key = NodeKey.build_from_host_port(opt[:host], opt[:port])
|
30
|
+
options = @options.merge(opt)
|
31
|
+
options = options.merge(host: @fixed_hostname) if @fixed_hostname && !@fixed_hostname.empty?
|
32
|
+
[node_key, options]
|
33
|
+
end.to_h
|
28
34
|
end
|
29
35
|
|
30
36
|
def use_replica?
|
@@ -63,7 +69,11 @@ class Redis
|
|
63
69
|
raise InvalidClientOptionError, "Invalid uri scheme #{addr}" unless VALID_SCHEMES.include?(uri.scheme)
|
64
70
|
|
65
71
|
db = uri.path.split('/')[1]&.to_i
|
66
|
-
|
72
|
+
username = uri.user ? URI.decode_www_form_component(uri.user) : nil
|
73
|
+
password = uri.password ? URI.decode_www_form_component(uri.password) : nil
|
74
|
+
|
75
|
+
{ scheme: uri.scheme, username: username, password: password, host: uri.host, port: uri.port, db: db }
|
76
|
+
.reject { |_, v| v.nil? || v == '' }
|
67
77
|
rescue URI::InvalidURIError => err
|
68
78
|
raise InvalidClientOptionError, err.message
|
69
79
|
end
|
@@ -79,7 +89,7 @@ class Redis
|
|
79
89
|
|
80
90
|
# Redis cluster node returns only host and port information.
|
81
91
|
# So we should complement additional information such as:
|
82
|
-
# scheme, password and so on.
|
92
|
+
# scheme, username, password and so on.
|
83
93
|
def add_common_node_option_if_needed(options, node_opts, key)
|
84
94
|
return options if options[key].nil? && node_opts.first[key].nil?
|
85
95
|
|
@@ -1,7 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
|
4
|
-
|
3
|
+
require 'redis/errors'
|
4
|
+
require 'redis/cluster/node_key'
|
5
5
|
|
6
6
|
class Redis
|
7
7
|
class Cluster
|
@@ -10,16 +10,15 @@ class Redis
|
|
10
10
|
module_function
|
11
11
|
|
12
12
|
def load(nodes)
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
13
|
+
errors = nodes.map do |node|
|
14
|
+
begin
|
15
|
+
return fetch_slot_info(node)
|
16
|
+
rescue CannotConnectError, ConnectionError, CommandError => error
|
17
|
+
error
|
18
|
+
end
|
18
19
|
end
|
19
20
|
|
20
|
-
|
21
|
-
|
22
|
-
raise CannotConnectError, 'Redis client could not connect to any cluster nodes'
|
21
|
+
raise InitialSetupError, errors
|
23
22
|
end
|
24
23
|
|
25
24
|
def fetch_slot_info(node)
|
@@ -27,8 +26,6 @@ class Redis
|
|
27
26
|
node.call(%i[cluster slots])
|
28
27
|
.flat_map { |arr| parse_slot_info(arr, default_ip: node.host) }
|
29
28
|
.each_with_object(hash_with_default_arr) { |arr, h| h[arr[0]] << arr[1] }
|
30
|
-
rescue CannotConnectError, ConnectionError, CommandError
|
31
|
-
{} # can retry on another node
|
32
29
|
end
|
33
30
|
|
34
31
|
def parse_slot_info(arr, default_ip:)
|