redis 4.8.1 → 5.1.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +65 -0
- data/README.md +101 -161
- data/lib/redis/client.rb +82 -616
- data/lib/redis/commands/bitmaps.rb +14 -4
- data/lib/redis/commands/cluster.rb +1 -18
- data/lib/redis/commands/connection.rb +5 -10
- data/lib/redis/commands/geo.rb +3 -3
- data/lib/redis/commands/hashes.rb +9 -6
- data/lib/redis/commands/hyper_log_log.rb +1 -1
- data/lib/redis/commands/keys.rb +21 -23
- data/lib/redis/commands/lists.rb +74 -25
- data/lib/redis/commands/pubsub.rb +28 -25
- data/lib/redis/commands/server.rb +15 -15
- data/lib/redis/commands/sets.rb +31 -40
- data/lib/redis/commands/sorted_sets.rb +84 -12
- data/lib/redis/commands/streams.rb +39 -19
- data/lib/redis/commands/strings.rb +18 -17
- data/lib/redis/commands/transactions.rb +7 -31
- data/lib/redis/commands.rb +4 -7
- data/lib/redis/distributed.rb +128 -68
- data/lib/redis/errors.rb +15 -50
- data/lib/redis/hash_ring.rb +26 -26
- data/lib/redis/pipeline.rb +43 -222
- data/lib/redis/subscribe.rb +50 -14
- data/lib/redis/version.rb +1 -1
- data/lib/redis.rb +76 -184
- metadata +10 -54
- data/lib/redis/cluster/command.rb +0 -79
- data/lib/redis/cluster/command_loader.rb +0 -33
- data/lib/redis/cluster/key_slot_converter.rb +0 -72
- data/lib/redis/cluster/node.rb +0 -120
- data/lib/redis/cluster/node_key.rb +0 -31
- data/lib/redis/cluster/node_loader.rb +0 -34
- data/lib/redis/cluster/option.rb +0 -100
- data/lib/redis/cluster/slot.rb +0 -86
- data/lib/redis/cluster/slot_loader.rb +0 -46
- data/lib/redis/cluster.rb +0 -315
- data/lib/redis/connection/command_helper.rb +0 -41
- data/lib/redis/connection/hiredis.rb +0 -68
- data/lib/redis/connection/registry.rb +0 -13
- data/lib/redis/connection/ruby.rb +0 -437
- data/lib/redis/connection/synchrony.rb +0 -148
- data/lib/redis/connection.rb +0 -11
data/lib/redis.rb
CHANGED
@@ -6,26 +6,10 @@ require "redis/commands"
|
|
6
6
|
|
7
7
|
class Redis
|
8
8
|
BASE_PATH = __dir__
|
9
|
-
@exists_returns_integer = true
|
10
|
-
@sadd_returns_boolean = true
|
11
|
-
|
12
9
|
Deprecated = Class.new(StandardError)
|
13
10
|
|
14
11
|
class << self
|
15
|
-
|
16
|
-
attr_accessor :silence_deprecations, :raise_deprecations, :sadd_returns_boolean
|
17
|
-
|
18
|
-
def exists_returns_integer=(value)
|
19
|
-
unless value
|
20
|
-
deprecate!(
|
21
|
-
"`Redis#exists(key)` will return an Integer by default in redis-rb 4.3. The option to explicitly " \
|
22
|
-
"disable this behaviour via `Redis.exists_returns_integer` will be removed in 5.0. You should use " \
|
23
|
-
"`exists?` instead."
|
24
|
-
)
|
25
|
-
end
|
26
|
-
|
27
|
-
@exists_returns_integer = value
|
28
|
-
end
|
12
|
+
attr_accessor :silence_deprecations, :raise_deprecations
|
29
13
|
|
30
14
|
def deprecate!(message)
|
31
15
|
unless silence_deprecations
|
@@ -36,20 +20,22 @@ class Redis
|
|
36
20
|
end
|
37
21
|
end
|
38
22
|
end
|
23
|
+
end
|
39
24
|
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
@current = redis
|
25
|
+
# soft-deprecated
|
26
|
+
# We added this back for older sidekiq releases
|
27
|
+
module Connection
|
28
|
+
class << self
|
29
|
+
def drivers
|
30
|
+
[RedisClient.default_driver]
|
31
|
+
end
|
48
32
|
end
|
49
33
|
end
|
50
34
|
|
51
35
|
include Commands
|
52
36
|
|
37
|
+
SERVER_URL_OPTIONS = %i(url host port path).freeze
|
38
|
+
|
53
39
|
# Create a new client instance
|
54
40
|
#
|
55
41
|
# @param [Hash] options
|
@@ -59,56 +45,49 @@ class Redis
|
|
59
45
|
# @option options [String] :host ("127.0.0.1") server hostname
|
60
46
|
# @option options [Integer] :port (6379) server port
|
61
47
|
# @option options [String] :path path to server socket (overrides host and port)
|
62
|
-
# @option options [Float] :timeout (
|
48
|
+
# @option options [Float] :timeout (1.0) timeout in seconds
|
63
49
|
# @option options [Float] :connect_timeout (same as timeout) timeout for initial connect in seconds
|
64
50
|
# @option options [String] :username Username to authenticate against server
|
65
51
|
# @option options [String] :password Password to authenticate against server
|
66
|
-
# @option options [Integer] :db (0) Database to select after
|
67
|
-
# @option options [Symbol] :driver Driver to use, currently supported: `:ruby`, `:hiredis
|
52
|
+
# @option options [Integer] :db (0) Database to select after connect and on reconnects
|
53
|
+
# @option options [Symbol] :driver Driver to use, currently supported: `:ruby`, `:hiredis`
|
68
54
|
# @option options [String] :id ID for the client connection, assigns name to current connection by sending
|
69
55
|
# `CLIENT SETNAME`
|
70
|
-
# @option options [
|
71
|
-
#
|
72
|
-
# @option options [Integer] :reconnect_attempts Number of attempts trying to connect
|
56
|
+
# @option options [Integer, Array<Integer, Float>] :reconnect_attempts Number of attempts trying to connect,
|
57
|
+
# or a list of sleep duration between attempts.
|
73
58
|
# @option options [Boolean] :inherit_socket (false) Whether to use socket in forked process or not
|
59
|
+
# @option options [String] :name The name of the server group to connect to.
|
74
60
|
# @option options [Array] :sentinels List of sentinels to contact
|
75
|
-
# @option options [Symbol] :role (:master) Role to fetch via Sentinel, either `:master` or `:slave`
|
76
|
-
# @option options [Array<String, Hash{Symbol => String, Integer}>] :cluster List of cluster nodes to contact
|
77
|
-
# @option options [Boolean] :replica Whether to use readonly replica nodes in Redis Cluster or not
|
78
|
-
# @option options [String] :fixed_hostname Specify a FQDN if cluster mode enabled and
|
79
|
-
# client has to connect nodes via single endpoint with SSL/TLS
|
80
|
-
# @option options [Class] :connector Class of custom connector
|
81
61
|
#
|
82
62
|
# @return [Redis] a new client instance
|
83
63
|
def initialize(options = {})
|
84
|
-
@options = options.dup
|
85
|
-
@cluster_mode = options.key?(:cluster)
|
86
|
-
client = @cluster_mode ? Cluster : Client
|
87
|
-
@original_client = @client = client.new(options)
|
88
|
-
@queue = Hash.new { |h, k| h[k] = [] }
|
89
64
|
@monitor = Monitor.new
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
synchronize do |client|
|
95
|
-
client.with_reconnect(val, &blk)
|
65
|
+
@options = options.dup
|
66
|
+
@options[:reconnect_attempts] = 1 unless @options.key?(:reconnect_attempts)
|
67
|
+
if ENV["REDIS_URL"] && SERVER_URL_OPTIONS.none? { |o| @options.key?(o) }
|
68
|
+
@options[:url] = ENV["REDIS_URL"]
|
96
69
|
end
|
70
|
+
inherit_socket = @options.delete(:inherit_socket)
|
71
|
+
@subscription_client = nil
|
72
|
+
|
73
|
+
@client = initialize_client(@options)
|
74
|
+
@client.inherit_socket! if inherit_socket
|
97
75
|
end
|
98
76
|
|
99
77
|
# Run code without the client reconnecting
|
100
|
-
def without_reconnect(&
|
101
|
-
|
78
|
+
def without_reconnect(&block)
|
79
|
+
@client.disable_reconnection(&block)
|
102
80
|
end
|
103
81
|
|
104
82
|
# Test whether or not the client is connected
|
105
83
|
def connected?
|
106
|
-
@
|
84
|
+
@client.connected? || @subscription_client&.connected?
|
107
85
|
end
|
108
86
|
|
109
87
|
# Disconnect the client as quickly and silently as possible.
|
110
88
|
def close
|
111
|
-
@
|
89
|
+
@client.close
|
90
|
+
@subscription_client&.close
|
112
91
|
end
|
113
92
|
alias disconnect! close
|
114
93
|
|
@@ -116,127 +95,20 @@ class Redis
|
|
116
95
|
yield self
|
117
96
|
end
|
118
97
|
|
119
|
-
# @deprecated Queues a command for pipelining.
|
120
|
-
#
|
121
|
-
# Commands in the queue are executed with the Redis#commit method.
|
122
|
-
#
|
123
|
-
# See http://redis.io/topics/pipelining for more details.
|
124
|
-
#
|
125
|
-
def queue(*command)
|
126
|
-
::Redis.deprecate!(
|
127
|
-
"Redis#queue is deprecated and will be removed in Redis 5.0.0. Use Redis#pipelined instead." \
|
128
|
-
"(called from: #{caller(1, 1).first})"
|
129
|
-
)
|
130
|
-
|
131
|
-
synchronize do
|
132
|
-
@queue[Thread.current.object_id] << command
|
133
|
-
end
|
134
|
-
end
|
135
|
-
|
136
|
-
# @deprecated Sends all commands in the queue.
|
137
|
-
#
|
138
|
-
# See http://redis.io/topics/pipelining for more details.
|
139
|
-
#
|
140
|
-
def commit
|
141
|
-
::Redis.deprecate!(
|
142
|
-
"Redis#commit is deprecated and will be removed in Redis 5.0.0. Use Redis#pipelined instead. " \
|
143
|
-
"(called from: #{Kernel.caller(1, 1).first})"
|
144
|
-
)
|
145
|
-
|
146
|
-
synchronize do |client|
|
147
|
-
begin
|
148
|
-
pipeline = Pipeline.new(client)
|
149
|
-
@queue[Thread.current.object_id].each do |command|
|
150
|
-
pipeline.call(command)
|
151
|
-
end
|
152
|
-
|
153
|
-
client.call_pipelined(pipeline)
|
154
|
-
ensure
|
155
|
-
@queue.delete(Thread.current.object_id)
|
156
|
-
end
|
157
|
-
end
|
158
|
-
end
|
159
|
-
|
160
98
|
def _client
|
161
99
|
@client
|
162
100
|
end
|
163
101
|
|
164
|
-
def pipelined
|
165
|
-
|
166
|
-
|
167
|
-
|
168
|
-
deprecation_displayed = true
|
169
|
-
end
|
170
|
-
|
171
|
-
synchronize do |prior_client|
|
172
|
-
begin
|
173
|
-
pipeline = Pipeline.new(prior_client)
|
174
|
-
@client = deprecation_displayed ? pipeline : DeprecatedPipeline.new(pipeline)
|
175
|
-
pipelined_connection = PipelinedConnection.new(pipeline)
|
176
|
-
yield pipelined_connection
|
177
|
-
prior_client.call_pipeline(pipeline)
|
178
|
-
ensure
|
179
|
-
@client = prior_client
|
180
|
-
end
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
# Mark the start of a transaction block.
|
185
|
-
#
|
186
|
-
# Passing a block is optional.
|
187
|
-
#
|
188
|
-
# @example With a block
|
189
|
-
# redis.multi do |multi|
|
190
|
-
# multi.set("key", "value")
|
191
|
-
# multi.incr("counter")
|
192
|
-
# end # => ["OK", 6]
|
193
|
-
#
|
194
|
-
# @example Without a block
|
195
|
-
# redis.multi
|
196
|
-
# # => "OK"
|
197
|
-
# redis.set("key", "value")
|
198
|
-
# # => "QUEUED"
|
199
|
-
# redis.incr("counter")
|
200
|
-
# # => "QUEUED"
|
201
|
-
# redis.exec
|
202
|
-
# # => ["OK", 6]
|
203
|
-
#
|
204
|
-
# @yield [multi] the commands that are called inside this block are cached
|
205
|
-
# and written to the server upon returning from it
|
206
|
-
# @yieldparam [Redis] multi `self`
|
207
|
-
#
|
208
|
-
# @return [String, Array<...>]
|
209
|
-
# - when a block is not given, `OK`
|
210
|
-
# - when a block is given, an array with replies
|
211
|
-
#
|
212
|
-
# @see #watch
|
213
|
-
# @see #unwatch
|
214
|
-
def multi(&block)
|
215
|
-
if block_given?
|
216
|
-
deprecation_displayed = false
|
217
|
-
if block&.arity == 0
|
218
|
-
Pipeline.deprecation_warning("multi", Kernel.caller_locations(1, 5))
|
219
|
-
deprecation_displayed = true
|
220
|
-
end
|
221
|
-
|
222
|
-
synchronize do |prior_client|
|
223
|
-
begin
|
224
|
-
pipeline = Pipeline::Multi.new(prior_client)
|
225
|
-
@client = deprecation_displayed ? pipeline : DeprecatedMulti.new(pipeline)
|
226
|
-
pipelined_connection = PipelinedConnection.new(pipeline)
|
227
|
-
yield pipelined_connection
|
228
|
-
prior_client.call_pipeline(pipeline)
|
229
|
-
ensure
|
230
|
-
@client = prior_client
|
231
|
-
end
|
102
|
+
def pipelined
|
103
|
+
synchronize do |client|
|
104
|
+
client.pipelined do |raw_pipeline|
|
105
|
+
yield PipelinedConnection.new(raw_pipeline)
|
232
106
|
end
|
233
|
-
else
|
234
|
-
send_command([:multi])
|
235
107
|
end
|
236
108
|
end
|
237
109
|
|
238
110
|
def id
|
239
|
-
@
|
111
|
+
@client.id || @client.server_url
|
240
112
|
end
|
241
113
|
|
242
114
|
def inspect
|
@@ -248,54 +120,74 @@ class Redis
|
|
248
120
|
end
|
249
121
|
|
250
122
|
def connection
|
251
|
-
return @original_client.connection_info if @cluster_mode
|
252
|
-
|
253
123
|
{
|
254
|
-
host: @
|
255
|
-
port: @
|
256
|
-
db: @
|
257
|
-
id:
|
258
|
-
location: @
|
124
|
+
host: @client.host,
|
125
|
+
port: @client.port,
|
126
|
+
db: @client.db,
|
127
|
+
id: id,
|
128
|
+
location: "#{@client.host}:#{@client.port}"
|
259
129
|
}
|
260
130
|
end
|
261
131
|
|
262
132
|
private
|
263
133
|
|
134
|
+
def initialize_client(options)
|
135
|
+
if options.key?(:cluster)
|
136
|
+
raise "Redis Cluster support was moved to the `redis-clustering` gem."
|
137
|
+
end
|
138
|
+
|
139
|
+
if options.key?(:sentinels)
|
140
|
+
Client.sentinel(**options).new_client
|
141
|
+
else
|
142
|
+
Client.config(**options).new_client
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
264
146
|
def synchronize
|
265
147
|
@monitor.synchronize { yield(@client) }
|
266
148
|
end
|
267
149
|
|
268
150
|
def send_command(command, &block)
|
269
151
|
@monitor.synchronize do
|
270
|
-
@client.
|
152
|
+
@client.call_v(command, &block)
|
271
153
|
end
|
154
|
+
rescue ::RedisClient::Error => error
|
155
|
+
Client.translate_error!(error)
|
272
156
|
end
|
273
157
|
|
274
158
|
def send_blocking_command(command, timeout, &block)
|
275
159
|
@monitor.synchronize do
|
276
|
-
@client.
|
160
|
+
@client.blocking_call_v(timeout, command, &block)
|
277
161
|
end
|
278
162
|
end
|
279
163
|
|
280
164
|
def _subscription(method, timeout, channels, block)
|
281
|
-
|
165
|
+
if block
|
166
|
+
if @subscription_client
|
167
|
+
raise SubscriptionError, "This client is already subscribed"
|
168
|
+
end
|
282
169
|
|
283
|
-
|
284
|
-
|
285
|
-
|
286
|
-
|
287
|
-
|
288
|
-
|
170
|
+
begin
|
171
|
+
@subscription_client = SubscribedClient.new(@client.pubsub)
|
172
|
+
if timeout > 0
|
173
|
+
@subscription_client.send(method, timeout, *channels, &block)
|
174
|
+
else
|
175
|
+
@subscription_client.send(method, *channels, &block)
|
176
|
+
end
|
177
|
+
ensure
|
178
|
+
@subscription_client = nil
|
179
|
+
end
|
180
|
+
else
|
181
|
+
unless @subscription_client
|
182
|
+
raise SubscriptionError, "This client is not subscribed"
|
289
183
|
end
|
290
|
-
|
291
|
-
@
|
184
|
+
|
185
|
+
@subscription_client.call_v([method].concat(channels))
|
292
186
|
end
|
293
187
|
end
|
294
188
|
end
|
295
189
|
|
296
190
|
require "redis/version"
|
297
|
-
require "redis/connection"
|
298
191
|
require "redis/client"
|
299
|
-
require "redis/cluster"
|
300
192
|
require "redis/pipeline"
|
301
193
|
require "redis/subscribe"
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 5.1.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ezra Zygmuntowicz
|
@@ -16,50 +16,22 @@ authors:
|
|
16
16
|
autorequire:
|
17
17
|
bindir: bin
|
18
18
|
cert_chain: []
|
19
|
-
date:
|
19
|
+
date: 2024-02-09 00:00:00.000000000 Z
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
22
|
-
name:
|
22
|
+
name: redis-client
|
23
23
|
requirement: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
25
|
- - ">="
|
26
26
|
- !ruby/object:Gem::Version
|
27
|
-
version:
|
28
|
-
type: :
|
27
|
+
version: 0.17.0
|
28
|
+
type: :runtime
|
29
29
|
prerelease: false
|
30
30
|
version_requirements: !ruby/object:Gem::Requirement
|
31
31
|
requirements:
|
32
32
|
- - ">="
|
33
33
|
- !ruby/object:Gem::Version
|
34
|
-
version:
|
35
|
-
- !ruby/object:Gem::Dependency
|
36
|
-
name: hiredis
|
37
|
-
requirement: !ruby/object:Gem::Requirement
|
38
|
-
requirements:
|
39
|
-
- - ">="
|
40
|
-
- !ruby/object:Gem::Version
|
41
|
-
version: '0'
|
42
|
-
type: :development
|
43
|
-
prerelease: false
|
44
|
-
version_requirements: !ruby/object:Gem::Requirement
|
45
|
-
requirements:
|
46
|
-
- - ">="
|
47
|
-
- !ruby/object:Gem::Version
|
48
|
-
version: '0'
|
49
|
-
- !ruby/object:Gem::Dependency
|
50
|
-
name: mocha
|
51
|
-
requirement: !ruby/object:Gem::Requirement
|
52
|
-
requirements:
|
53
|
-
- - ">="
|
54
|
-
- !ruby/object:Gem::Version
|
55
|
-
version: '0'
|
56
|
-
type: :development
|
57
|
-
prerelease: false
|
58
|
-
version_requirements: !ruby/object:Gem::Requirement
|
59
|
-
requirements:
|
60
|
-
- - ">="
|
61
|
-
- !ruby/object:Gem::Version
|
62
|
-
version: '0'
|
34
|
+
version: 0.17.0
|
63
35
|
description: |2
|
64
36
|
A Ruby client that tries to match Redis' API one-to-one, while still
|
65
37
|
providing an idiomatic interface.
|
@@ -74,16 +46,6 @@ files:
|
|
74
46
|
- README.md
|
75
47
|
- lib/redis.rb
|
76
48
|
- lib/redis/client.rb
|
77
|
-
- lib/redis/cluster.rb
|
78
|
-
- lib/redis/cluster/command.rb
|
79
|
-
- lib/redis/cluster/command_loader.rb
|
80
|
-
- lib/redis/cluster/key_slot_converter.rb
|
81
|
-
- lib/redis/cluster/node.rb
|
82
|
-
- lib/redis/cluster/node_key.rb
|
83
|
-
- lib/redis/cluster/node_loader.rb
|
84
|
-
- lib/redis/cluster/option.rb
|
85
|
-
- lib/redis/cluster/slot.rb
|
86
|
-
- lib/redis/cluster/slot_loader.rb
|
87
49
|
- lib/redis/commands.rb
|
88
50
|
- lib/redis/commands/bitmaps.rb
|
89
51
|
- lib/redis/commands/cluster.rb
|
@@ -101,12 +63,6 @@ files:
|
|
101
63
|
- lib/redis/commands/streams.rb
|
102
64
|
- lib/redis/commands/strings.rb
|
103
65
|
- lib/redis/commands/transactions.rb
|
104
|
-
- lib/redis/connection.rb
|
105
|
-
- lib/redis/connection/command_helper.rb
|
106
|
-
- lib/redis/connection/hiredis.rb
|
107
|
-
- lib/redis/connection/registry.rb
|
108
|
-
- lib/redis/connection/ruby.rb
|
109
|
-
- lib/redis/connection/synchrony.rb
|
110
66
|
- lib/redis/distributed.rb
|
111
67
|
- lib/redis/errors.rb
|
112
68
|
- lib/redis/hash_ring.rb
|
@@ -119,9 +75,9 @@ licenses:
|
|
119
75
|
metadata:
|
120
76
|
bug_tracker_uri: https://github.com/redis/redis-rb/issues
|
121
77
|
changelog_uri: https://github.com/redis/redis-rb/blob/master/CHANGELOG.md
|
122
|
-
documentation_uri: https://www.rubydoc.info/gems/redis/
|
78
|
+
documentation_uri: https://www.rubydoc.info/gems/redis/5.1.0
|
123
79
|
homepage_uri: https://github.com/redis/redis-rb
|
124
|
-
source_code_uri: https://github.com/redis/redis-rb/tree/
|
80
|
+
source_code_uri: https://github.com/redis/redis-rb/tree/v5.1.0
|
125
81
|
post_install_message:
|
126
82
|
rdoc_options: []
|
127
83
|
require_paths:
|
@@ -130,14 +86,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
130
86
|
requirements:
|
131
87
|
- - ">="
|
132
88
|
- !ruby/object:Gem::Version
|
133
|
-
version: 2.
|
89
|
+
version: 2.5.0
|
134
90
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
135
91
|
requirements:
|
136
92
|
- - ">="
|
137
93
|
- !ruby/object:Gem::Version
|
138
94
|
version: '0'
|
139
95
|
requirements: []
|
140
|
-
rubygems_version: 3.
|
96
|
+
rubygems_version: 3.3.7
|
141
97
|
signing_key:
|
142
98
|
specification_version: 4
|
143
99
|
summary: A Ruby client library for Redis
|
@@ -1,79 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative '../errors'
|
4
|
-
|
5
|
-
class Redis
|
6
|
-
class Cluster
|
7
|
-
# Keep details about Redis commands for Redis Cluster Client.
|
8
|
-
# @see https://redis.io/commands/command
|
9
|
-
class Command
|
10
|
-
def initialize(details)
|
11
|
-
@details = pick_details(details)
|
12
|
-
end
|
13
|
-
|
14
|
-
def extract_first_key(command)
|
15
|
-
i = determine_first_key_position(command)
|
16
|
-
return '' if i == 0
|
17
|
-
|
18
|
-
key = command[i].to_s
|
19
|
-
hash_tag = extract_hash_tag(key)
|
20
|
-
hash_tag.empty? ? key : hash_tag
|
21
|
-
end
|
22
|
-
|
23
|
-
def should_send_to_master?(command)
|
24
|
-
dig_details(command, :write)
|
25
|
-
end
|
26
|
-
|
27
|
-
def should_send_to_slave?(command)
|
28
|
-
dig_details(command, :readonly)
|
29
|
-
end
|
30
|
-
|
31
|
-
private
|
32
|
-
|
33
|
-
def pick_details(details)
|
34
|
-
details.transform_values do |detail|
|
35
|
-
{
|
36
|
-
first_key_position: detail[:first],
|
37
|
-
write: detail[:flags].include?('write'),
|
38
|
-
readonly: detail[:flags].include?('readonly')
|
39
|
-
}
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
def dig_details(command, key)
|
44
|
-
name = command.first.to_s
|
45
|
-
return unless @details.key?(name)
|
46
|
-
|
47
|
-
@details.fetch(name).fetch(key)
|
48
|
-
end
|
49
|
-
|
50
|
-
def determine_first_key_position(command)
|
51
|
-
case command.first.to_s.downcase
|
52
|
-
when 'eval', 'evalsha', 'migrate', 'zinterstore', 'zunionstore' then 3
|
53
|
-
when 'object' then 2
|
54
|
-
when 'memory'
|
55
|
-
command[1].to_s.casecmp('usage').zero? ? 2 : 0
|
56
|
-
when 'xread', 'xreadgroup'
|
57
|
-
determine_optional_key_position(command, 'streams')
|
58
|
-
else
|
59
|
-
dig_details(command, :first_key_position).to_i
|
60
|
-
end
|
61
|
-
end
|
62
|
-
|
63
|
-
def determine_optional_key_position(command, option_name)
|
64
|
-
idx = command.map(&:to_s).map(&:downcase).index(option_name)
|
65
|
-
idx.nil? ? 0 : idx + 1
|
66
|
-
end
|
67
|
-
|
68
|
-
# @see https://redis.io/topics/cluster-spec#keys-hash-tags Keys hash tags
|
69
|
-
def extract_hash_tag(key)
|
70
|
-
s = key.index('{')
|
71
|
-
e = key.index('}', s.to_i + 1)
|
72
|
-
|
73
|
-
return '' if s.nil? || e.nil?
|
74
|
-
|
75
|
-
key[s + 1..e - 1]
|
76
|
-
end
|
77
|
-
end
|
78
|
-
end
|
79
|
-
end
|
@@ -1,33 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'redis/errors'
|
4
|
-
|
5
|
-
class Redis
|
6
|
-
class Cluster
|
7
|
-
# Load details about Redis commands for Redis Cluster Client
|
8
|
-
# @see https://redis.io/commands/command
|
9
|
-
module CommandLoader
|
10
|
-
module_function
|
11
|
-
|
12
|
-
def load(nodes)
|
13
|
-
errors = nodes.map do |node|
|
14
|
-
begin
|
15
|
-
return fetch_command_details(node)
|
16
|
-
rescue CannotConnectError, ConnectionError, CommandError => error
|
17
|
-
error
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
raise InitialSetupError, errors
|
22
|
-
end
|
23
|
-
|
24
|
-
def fetch_command_details(node)
|
25
|
-
node.call(%i[command]).map do |reply|
|
26
|
-
[reply[0], { arity: reply[1], flags: reply[2], first: reply[3], last: reply[4], step: reply[5] }]
|
27
|
-
end.to_h
|
28
|
-
end
|
29
|
-
|
30
|
-
private_class_method :fetch_command_details
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
@@ -1,72 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class Redis
|
4
|
-
class Cluster
|
5
|
-
# Key to slot converter for Redis Cluster Client
|
6
|
-
#
|
7
|
-
# We can test it by `CLUSTER KEYSLOT` command.
|
8
|
-
#
|
9
|
-
# @see https://github.com/antirez/redis-rb-cluster
|
10
|
-
# Reference implementation in Ruby
|
11
|
-
# @see https://redis.io/topics/cluster-spec#appendix
|
12
|
-
# Reference implementation in ANSI C
|
13
|
-
# @see https://redis.io/commands/cluster-keyslot
|
14
|
-
# CLUSTER KEYSLOT command reference
|
15
|
-
#
|
16
|
-
# Copyright (C) 2013 Salvatore Sanfilippo <antirez@gmail.com>
|
17
|
-
module KeySlotConverter
|
18
|
-
XMODEM_CRC16_LOOKUP = [
|
19
|
-
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
|
20
|
-
0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
|
21
|
-
0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
|
22
|
-
0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
|
23
|
-
0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
|
24
|
-
0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
|
25
|
-
0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
|
26
|
-
0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
|
27
|
-
0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
|
28
|
-
0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
|
29
|
-
0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
|
30
|
-
0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
|
31
|
-
0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
|
32
|
-
0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
|
33
|
-
0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
|
34
|
-
0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
|
35
|
-
0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
|
36
|
-
0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
|
37
|
-
0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
|
38
|
-
0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
|
39
|
-
0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
|
40
|
-
0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
|
41
|
-
0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
|
42
|
-
0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
|
43
|
-
0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
|
44
|
-
0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
|
45
|
-
0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
|
46
|
-
0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
|
47
|
-
0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
|
48
|
-
0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
|
49
|
-
0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
|
50
|
-
0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
|
51
|
-
].freeze
|
52
|
-
|
53
|
-
HASH_SLOTS = 16_384
|
54
|
-
|
55
|
-
module_function
|
56
|
-
|
57
|
-
# Convert key into slot.
|
58
|
-
#
|
59
|
-
# @param key [String] the key of the redis command
|
60
|
-
#
|
61
|
-
# @return [Integer] slot number
|
62
|
-
def convert(key)
|
63
|
-
crc = 0
|
64
|
-
key.each_byte do |b|
|
65
|
-
crc = ((crc << 8) & 0xffff) ^ XMODEM_CRC16_LOOKUP[((crc >> 8) ^ b) & 0xff]
|
66
|
-
end
|
67
|
-
|
68
|
-
crc % HASH_SLOTS
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|
72
|
-
end
|