redis 4.4.0 → 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 +96 -0
- data/README.md +25 -10
- data/lib/redis/client.rb +31 -25
- data/lib/redis/cluster/command.rb +4 -6
- data/lib/redis/cluster/command_loader.rb +8 -9
- data/lib/redis/cluster/node.rb +12 -0
- data/lib/redis/cluster/node_loader.rb +8 -11
- data/lib/redis/cluster/option.rb +10 -3
- data/lib/redis/cluster/slot_loader.rb +9 -12
- data/lib/redis/cluster.rb +24 -0
- 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 +111 -23
- 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 +138 -3482
- 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,101 @@
|
|
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
|
+
|
3
99
|
# 4.4.0
|
4
100
|
|
5
101
|
* Redis cluster: fix cross-slot validation in pipelines. Fix ##1019.
|
data/README.md
CHANGED
@@ -155,6 +155,21 @@ redis.mget('{key}1', '{key}2')
|
|
155
155
|
* The client support permanent node failures, and will reroute requests to promoted slaves.
|
156
156
|
* The client supports `MOVED` and `ASK` redirections transparently.
|
157
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
|
+
|
158
173
|
## Storing objects
|
159
174
|
|
160
175
|
Redis "string" types can be used to store serialized Ruby objects, for
|
@@ -184,9 +199,9 @@ commands to Redis and gathers their replies. These replies are returned
|
|
184
199
|
by the `#pipelined` method.
|
185
200
|
|
186
201
|
```ruby
|
187
|
-
redis.pipelined do
|
188
|
-
|
189
|
-
|
202
|
+
redis.pipelined do |pipeline|
|
203
|
+
pipeline.set "foo", "bar"
|
204
|
+
pipeline.incr "baz"
|
190
205
|
end
|
191
206
|
# => ["OK", 1]
|
192
207
|
```
|
@@ -200,9 +215,9 @@ the regular pipeline, the replies to the commands are returned by the
|
|
200
215
|
`#multi` method.
|
201
216
|
|
202
217
|
```ruby
|
203
|
-
redis.multi do
|
204
|
-
|
205
|
-
|
218
|
+
redis.multi do |transaction|
|
219
|
+
transaction.set "foo", "bar"
|
220
|
+
transaction.incr "baz"
|
206
221
|
end
|
207
222
|
# => ["OK", 1]
|
208
223
|
```
|
@@ -210,15 +225,15 @@ end
|
|
210
225
|
### Futures
|
211
226
|
|
212
227
|
Replies to commands in a pipeline can be accessed via the *futures* they
|
213
|
-
emit (since redis-rb 3.0). All calls
|
228
|
+
emit (since redis-rb 3.0). All calls on the pipeline object return a
|
214
229
|
`Future` object, which responds to the `#value` method. When the
|
215
230
|
pipeline has successfully executed, all futures are assigned their
|
216
231
|
respective replies and can be used.
|
217
232
|
|
218
233
|
```ruby
|
219
|
-
redis.pipelined do
|
220
|
-
@set =
|
221
|
-
@incr =
|
234
|
+
redis.pipelined do |pipeline|
|
235
|
+
@set = pipeline.set "foo", "bar"
|
236
|
+
@incr = pipeline.incr "baz"
|
222
237
|
end
|
223
238
|
|
224
239
|
@set.value
|
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
|
@@ -32,7 +32,7 @@ class Redis
|
|
32
32
|
role: nil
|
33
33
|
}.freeze
|
34
34
|
|
35
|
-
attr_reader :options
|
35
|
+
attr_reader :options, :connection, :command_map
|
36
36
|
|
37
37
|
def scheme
|
38
38
|
@options[:scheme]
|
@@ -87,8 +87,6 @@ class Redis
|
|
87
87
|
end
|
88
88
|
|
89
89
|
attr_accessor :logger
|
90
|
-
attr_reader :connection
|
91
|
-
attr_reader :command_map
|
92
90
|
|
93
91
|
def initialize(options = {})
|
94
92
|
@options = _parse_options(options)
|
@@ -119,8 +117,23 @@ class Redis
|
|
119
117
|
if username
|
120
118
|
begin
|
121
119
|
call [:auth, username, password]
|
122
|
-
rescue CommandError # Likely on Redis < 6
|
123
|
-
|
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
|
124
137
|
end
|
125
138
|
else
|
126
139
|
call [:auth, password]
|
@@ -137,7 +150,7 @@ class Redis
|
|
137
150
|
end
|
138
151
|
|
139
152
|
def id
|
140
|
-
@options[:id] || "
|
153
|
+
@options[:id] || "#{@options[:ssl] ? 'rediss' : @options[:scheme]}://#{location}/#{db}"
|
141
154
|
end
|
142
155
|
|
143
156
|
def location
|
@@ -238,7 +251,8 @@ class Redis
|
|
238
251
|
result
|
239
252
|
end
|
240
253
|
|
241
|
-
def call_with_timeout(command,
|
254
|
+
def call_with_timeout(command, extra_timeout, &blk)
|
255
|
+
timeout = extra_timeout == 0 ? 0 : self.timeout + extra_timeout
|
242
256
|
with_socket_timeout(timeout) do
|
243
257
|
call(command, &blk)
|
244
258
|
end
|
@@ -288,7 +302,7 @@ class Redis
|
|
288
302
|
e2 = TimeoutError.new("Connection timed out")
|
289
303
|
e2.set_backtrace(e1.backtrace)
|
290
304
|
raise e2
|
291
|
-
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
|
292
306
|
raise ConnectionError, "Connection lost (%s)" % [e.class.name.split("::").last]
|
293
307
|
end
|
294
308
|
|
@@ -385,23 +399,14 @@ class Redis
|
|
385
399
|
end
|
386
400
|
|
387
401
|
def ensure_connected
|
388
|
-
disconnect if @pending_reads > 0
|
402
|
+
disconnect if @pending_reads > 0 || (@pid != Process.pid && !inherit_socket?)
|
389
403
|
|
390
404
|
attempts = 0
|
391
405
|
|
392
406
|
begin
|
393
407
|
attempts += 1
|
394
408
|
|
395
|
-
|
396
|
-
unless inherit_socket? || Process.pid == @pid
|
397
|
-
raise InheritedError,
|
398
|
-
"Tried to use a connection from a child process without reconnecting. " \
|
399
|
-
"You need to reconnect to Redis after forking " \
|
400
|
-
"or set :inherit_socket to true."
|
401
|
-
end
|
402
|
-
else
|
403
|
-
connect
|
404
|
-
end
|
409
|
+
connect unless connected?
|
405
410
|
|
406
411
|
yield
|
407
412
|
rescue BaseConnectionError
|
@@ -428,7 +433,7 @@ class Redis
|
|
428
433
|
defaults = DEFAULTS.dup
|
429
434
|
options = options.dup
|
430
435
|
|
431
|
-
defaults.
|
436
|
+
defaults.each_key do |key|
|
432
437
|
# Fill in defaults if needed
|
433
438
|
defaults[key] = defaults[key].call if defaults[key].respond_to?(:call)
|
434
439
|
|
@@ -445,11 +450,12 @@ class Redis
|
|
445
450
|
|
446
451
|
uri = URI(url)
|
447
452
|
|
448
|
-
|
453
|
+
case uri.scheme
|
454
|
+
when "unix"
|
449
455
|
defaults[:path] = uri.path
|
450
|
-
|
456
|
+
when "redis", "rediss"
|
451
457
|
defaults[:scheme] = uri.scheme
|
452
|
-
defaults[:host] = uri.host if uri.host
|
458
|
+
defaults[:host] = uri.host.sub(/\A\[(.*)\]\z/, '\1') if uri.host
|
453
459
|
defaults[:port] = uri.port if uri.port
|
454
460
|
defaults[:username] = CGI.unescape(uri.user) if uri.user && !uri.user.empty?
|
455
461
|
defaults[:password] = CGI.unescape(uri.password) if uri.password && !uri.password.empty?
|
@@ -463,7 +469,7 @@ class Redis
|
|
463
469
|
end
|
464
470
|
|
465
471
|
# Use default when option is not specified or nil
|
466
|
-
defaults.
|
472
|
+
defaults.each_key do |key|
|
467
473
|
options[key] = defaults[key] if options[key].nil?
|
468
474
|
end
|
469
475
|
|
@@ -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?
|
@@ -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,6 +17,7 @@ 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)
|
21
22
|
add_common_node_option_if_needed(options, @node_opts, :username)
|
22
23
|
add_common_node_option_if_needed(options, @node_opts, :password)
|
@@ -24,8 +25,12 @@ class Redis
|
|
24
25
|
end
|
25
26
|
|
26
27
|
def per_node_key
|
27
|
-
@node_opts.map
|
28
|
-
|
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
|
29
34
|
end
|
30
35
|
|
31
36
|
def use_replica?
|
@@ -64,8 +69,10 @@ class Redis
|
|
64
69
|
raise InvalidClientOptionError, "Invalid uri scheme #{addr}" unless VALID_SCHEMES.include?(uri.scheme)
|
65
70
|
|
66
71
|
db = uri.path.split('/')[1]&.to_i
|
72
|
+
username = uri.user ? URI.decode_www_form_component(uri.user) : nil
|
73
|
+
password = uri.password ? URI.decode_www_form_component(uri.password) : nil
|
67
74
|
|
68
|
-
{ scheme: uri.scheme, username:
|
75
|
+
{ scheme: uri.scheme, username: username, password: password, host: uri.host, port: uri.port, db: db }
|
69
76
|
.reject { |_, v| v.nil? || v == '' }
|
70
77
|
rescue URI::InvalidURIError => err
|
71
78
|
raise InvalidClientOptionError, err.message
|
@@ -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:)
|
data/lib/redis/cluster.rb
CHANGED
@@ -137,6 +137,7 @@ class Redis
|
|
137
137
|
when 'wait' then @node.call_master(command, &block).reduce(:+)
|
138
138
|
when 'keys' then @node.call_slave(command, &block).flatten.sort
|
139
139
|
when 'dbsize' then @node.call_slave(command, &block).reduce(:+)
|
140
|
+
when 'scan' then _scan(command, &block)
|
140
141
|
when 'lastsave' then @node.call_all(command, &block).sort
|
141
142
|
when 'role' then @node.call_all(command, &block)
|
142
143
|
when 'config' then send_config_command(command, &block)
|
@@ -238,6 +239,29 @@ class Redis
|
|
238
239
|
raise
|
239
240
|
end
|
240
241
|
|
242
|
+
def _scan(command, &block)
|
243
|
+
input_cursor = Integer(command[1])
|
244
|
+
|
245
|
+
client_index = input_cursor % 256
|
246
|
+
raw_cursor = input_cursor >> 8
|
247
|
+
|
248
|
+
clients = @node.scale_reading_clients
|
249
|
+
|
250
|
+
client = clients[client_index]
|
251
|
+
return ['0', []] unless client
|
252
|
+
|
253
|
+
command[1] = raw_cursor.to_s
|
254
|
+
|
255
|
+
result_cursor, result_keys = client.call(command, &block)
|
256
|
+
result_cursor = Integer(result_cursor)
|
257
|
+
|
258
|
+
if result_cursor == 0
|
259
|
+
client_index += 1
|
260
|
+
end
|
261
|
+
|
262
|
+
[((result_cursor << 8) + client_index).to_s, result_keys]
|
263
|
+
end
|
264
|
+
|
241
265
|
def assign_redirection_node(err_msg)
|
242
266
|
_, slot, node_key = err_msg.split(' ')
|
243
267
|
slot = slot.to_i
|
@@ -0,0 +1,63 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Redis
|
4
|
+
module Commands
|
5
|
+
module Bitmaps
|
6
|
+
# Sets or clears the bit at offset in the string value stored at key.
|
7
|
+
#
|
8
|
+
# @param [String] key
|
9
|
+
# @param [Integer] offset bit offset
|
10
|
+
# @param [Integer] value bit value `0` or `1`
|
11
|
+
# @return [Integer] the original bit value stored at `offset`
|
12
|
+
def setbit(key, offset, value)
|
13
|
+
send_command([:setbit, key, offset, value])
|
14
|
+
end
|
15
|
+
|
16
|
+
# Returns the bit value at offset in the string value stored at key.
|
17
|
+
#
|
18
|
+
# @param [String] key
|
19
|
+
# @param [Integer] offset bit offset
|
20
|
+
# @return [Integer] `0` or `1`
|
21
|
+
def getbit(key, offset)
|
22
|
+
send_command([:getbit, key, offset])
|
23
|
+
end
|
24
|
+
|
25
|
+
# Count the number of set bits in a range of the string value stored at key.
|
26
|
+
#
|
27
|
+
# @param [String] key
|
28
|
+
# @param [Integer] start start index
|
29
|
+
# @param [Integer] stop stop index
|
30
|
+
# @return [Integer] the number of bits set to 1
|
31
|
+
def bitcount(key, start = 0, stop = -1)
|
32
|
+
send_command([:bitcount, key, start, stop])
|
33
|
+
end
|
34
|
+
|
35
|
+
# Perform a bitwise operation between strings and store the resulting string in a key.
|
36
|
+
#
|
37
|
+
# @param [String] operation e.g. `and`, `or`, `xor`, `not`
|
38
|
+
# @param [String] destkey destination key
|
39
|
+
# @param [String, Array<String>] keys one or more source keys to perform `operation`
|
40
|
+
# @return [Integer] the length of the string stored in `destkey`
|
41
|
+
def bitop(operation, destkey, *keys)
|
42
|
+
send_command([:bitop, operation, destkey, *keys])
|
43
|
+
end
|
44
|
+
|
45
|
+
# Return the position of the first bit set to 1 or 0 in a string.
|
46
|
+
#
|
47
|
+
# @param [String] key
|
48
|
+
# @param [Integer] bit whether to look for the first 1 or 0 bit
|
49
|
+
# @param [Integer] start start index
|
50
|
+
# @param [Integer] stop stop index
|
51
|
+
# @return [Integer] the position of the first 1/0 bit.
|
52
|
+
# -1 if looking for 1 and it is not found or start and stop are given.
|
53
|
+
def bitpos(key, bit, start = nil, stop = nil)
|
54
|
+
raise(ArgumentError, 'stop parameter specified without start parameter') if stop && !start
|
55
|
+
|
56
|
+
command = [:bitpos, key, bit]
|
57
|
+
command << start if start
|
58
|
+
command << stop if stop
|
59
|
+
send_command(command)
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Redis
|
4
|
+
module Commands
|
5
|
+
module Cluster
|
6
|
+
# Sends `CLUSTER *` command to random node and returns its reply.
|
7
|
+
#
|
8
|
+
# @see https://redis.io/commands#cluster Reference of cluster command
|
9
|
+
#
|
10
|
+
# @param subcommand [String, Symbol] the subcommand of cluster command
|
11
|
+
# e.g. `:slots`, `:nodes`, `:slaves`, `:info`
|
12
|
+
#
|
13
|
+
# @return [Object] depends on the subcommand
|
14
|
+
def cluster(subcommand, *args)
|
15
|
+
subcommand = subcommand.to_s.downcase
|
16
|
+
block = case subcommand
|
17
|
+
when 'slots'
|
18
|
+
HashifyClusterSlots
|
19
|
+
when 'nodes'
|
20
|
+
HashifyClusterNodes
|
21
|
+
when 'slaves'
|
22
|
+
HashifyClusterSlaves
|
23
|
+
when 'info'
|
24
|
+
HashifyInfo
|
25
|
+
else
|
26
|
+
Noop
|
27
|
+
end
|
28
|
+
|
29
|
+
# @see https://github.com/antirez/redis/blob/unstable/src/redis-trib.rb#L127 raw reply expected
|
30
|
+
block = Noop unless @cluster_mode
|
31
|
+
|
32
|
+
send_command([:cluster, subcommand] + args, &block)
|
33
|
+
end
|
34
|
+
|
35
|
+
# Sends `ASKING` command to random node and returns its reply.
|
36
|
+
#
|
37
|
+
# @see https://redis.io/topics/cluster-spec#ask-redirection ASK redirection
|
38
|
+
#
|
39
|
+
# @return [String] `'OK'`
|
40
|
+
def asking
|
41
|
+
send_command(%i[asking])
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|