redis 4.2.5 → 4.8.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 +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:)
|