redis-cluster-client 0.11.0 → 0.13.5
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/lib/redis_client/cluster/command.rb +47 -75
- data/lib/redis_client/cluster/concurrent_worker/on_demand.rb +2 -0
- data/lib/redis_client/cluster/concurrent_worker/pooled.rb +2 -0
- data/lib/redis_client/cluster/concurrent_worker.rb +4 -6
- data/lib/redis_client/cluster/errors.rb +39 -19
- data/lib/redis_client/cluster/key_slot_converter.rb +3 -1
- data/lib/redis_client/cluster/node/base_topology.rb +3 -1
- data/lib/redis_client/cluster/node/latency_replica.rb +3 -1
- data/lib/redis_client/cluster/node.rb +77 -14
- data/lib/redis_client/cluster/node_key.rb +2 -0
- data/lib/redis_client/cluster/noop_command_builder.rb +13 -0
- data/lib/redis_client/cluster/optimistic_locking.rb +25 -10
- data/lib/redis_client/cluster/pipeline.rb +53 -18
- data/lib/redis_client/cluster/pub_sub.rb +70 -31
- data/lib/redis_client/cluster/router.rb +282 -134
- data/lib/redis_client/cluster/transaction.rb +22 -11
- data/lib/redis_client/cluster.rb +24 -15
- data/lib/redis_client/cluster_config.rb +44 -11
- metadata +7 -11
- data/lib/redis_client/cluster/normalized_cmd_name.rb +0 -69
@@ -7,7 +7,6 @@ require 'redis_client/cluster/errors'
|
|
7
7
|
require 'redis_client/cluster/key_slot_converter'
|
8
8
|
require 'redis_client/cluster/node'
|
9
9
|
require 'redis_client/cluster/node_key'
|
10
|
-
require 'redis_client/cluster/normalized_cmd_name'
|
11
10
|
require 'redis_client/cluster/transaction'
|
12
11
|
require 'redis_client/cluster/optimistic_locking'
|
13
12
|
require 'redis_client/cluster/pipeline'
|
@@ -18,71 +17,121 @@ class RedisClient
|
|
18
17
|
class Router
|
19
18
|
ZERO_CURSOR_FOR_SCAN = '0'
|
20
19
|
TSF = ->(f, x) { f.nil? ? x : f.call(x) }.curry
|
20
|
+
Ractor.make_shareable(TSF) if Object.const_defined?(:Ractor, false) && Ractor.respond_to?(:make_shareable)
|
21
|
+
DEDICATED_ACTIONS = lambda do # rubocop:disable Metrics/BlockLength
|
22
|
+
action = Struct.new('RedisCommandRoutingAction', :method_name, :reply_transformer, keyword_init: true)
|
23
|
+
pick_first = ->(reply) { reply.first } # rubocop:disable Style/SymbolProc
|
24
|
+
flatten_strings = ->(reply) { reply.flatten.sort_by(&:to_s) }
|
25
|
+
sum_num = ->(reply) { reply.select { |e| e.is_a?(Integer) }.sum }
|
26
|
+
sort_numbers = ->(reply) { reply.sort_by(&:to_i) }
|
27
|
+
if Object.const_defined?(:Ractor, false) && Ractor.respond_to?(:make_shareable)
|
28
|
+
Ractor.make_shareable(pick_first)
|
29
|
+
Ractor.make_shareable(flatten_strings)
|
30
|
+
Ractor.make_shareable(sum_num)
|
31
|
+
Ractor.make_shareable(sort_numbers)
|
32
|
+
end
|
33
|
+
multiple_key_action = action.new(method_name: :send_multiple_keys_command)
|
34
|
+
all_node_first_action = action.new(method_name: :send_command_to_all_nodes, reply_transformer: pick_first)
|
35
|
+
primary_first_action = action.new(method_name: :send_command_to_primaries, reply_transformer: pick_first)
|
36
|
+
not_supported_action = action.new(method_name: :fail_not_supported_command)
|
37
|
+
keyless_action = action.new(method_name: :fail_keyless_command)
|
38
|
+
{
|
39
|
+
'ping' => action.new(method_name: :send_ping_command, reply_transformer: pick_first),
|
40
|
+
'wait' => action.new(method_name: :send_wait_command),
|
41
|
+
'keys' => action.new(method_name: :send_command_to_replicas, reply_transformer: flatten_strings),
|
42
|
+
'dbsize' => action.new(method_name: :send_command_to_replicas, reply_transformer: sum_num),
|
43
|
+
'scan' => action.new(method_name: :send_scan_command),
|
44
|
+
'lastsave' => action.new(method_name: :send_command_to_all_nodes, reply_transformer: sort_numbers),
|
45
|
+
'role' => action.new(method_name: :send_command_to_all_nodes),
|
46
|
+
'config' => action.new(method_name: :send_config_command),
|
47
|
+
'client' => action.new(method_name: :send_client_command),
|
48
|
+
'cluster' => action.new(method_name: :send_cluster_command),
|
49
|
+
'memory' => action.new(method_name: :send_memory_command),
|
50
|
+
'script' => action.new(method_name: :send_script_command),
|
51
|
+
'pubsub' => action.new(method_name: :send_pubsub_command),
|
52
|
+
'watch' => action.new(method_name: :send_watch_command),
|
53
|
+
'mget' => multiple_key_action,
|
54
|
+
'mset' => multiple_key_action,
|
55
|
+
'del' => multiple_key_action,
|
56
|
+
'acl' => all_node_first_action,
|
57
|
+
'auth' => all_node_first_action,
|
58
|
+
'bgrewriteaof' => all_node_first_action,
|
59
|
+
'bgsave' => all_node_first_action,
|
60
|
+
'quit' => all_node_first_action,
|
61
|
+
'save' => all_node_first_action,
|
62
|
+
'flushall' => primary_first_action,
|
63
|
+
'flushdb' => primary_first_action,
|
64
|
+
'readonly' => not_supported_action,
|
65
|
+
'readwrite' => not_supported_action,
|
66
|
+
'shutdown' => not_supported_action,
|
67
|
+
'discard' => keyless_action,
|
68
|
+
'exec' => keyless_action,
|
69
|
+
'multi' => keyless_action,
|
70
|
+
'unwatch' => keyless_action
|
71
|
+
}.each_with_object({}) do |(k, v), acc|
|
72
|
+
acc[k] = v.freeze
|
73
|
+
acc[k.upcase] = v.freeze
|
74
|
+
end
|
75
|
+
end.call.freeze
|
76
|
+
|
77
|
+
private_constant :ZERO_CURSOR_FOR_SCAN, :TSF, :DEDICATED_ACTIONS
|
78
|
+
|
79
|
+
attr_reader :config
|
21
80
|
|
22
81
|
def initialize(config, concurrent_worker, pool: nil, **kwargs)
|
23
|
-
@config = config
|
24
|
-
@original_config = config.dup if config.connect_with_original_config
|
25
|
-
@connect_with_original_config = config.connect_with_original_config
|
82
|
+
@config = config
|
26
83
|
@concurrent_worker = concurrent_worker
|
27
84
|
@pool = pool
|
28
85
|
@client_kwargs = kwargs
|
29
86
|
@node = ::RedisClient::Cluster::Node.new(concurrent_worker, config: config, pool: pool, **kwargs)
|
30
|
-
|
87
|
+
@node.reload!
|
31
88
|
@command = ::RedisClient::Cluster::Command.load(@node.replica_clients.shuffle, slow_command_timeout: config.slow_command_timeout)
|
32
89
|
@command_builder = @config.command_builder
|
90
|
+
rescue ::RedisClient::Cluster::InitialSetupError => e
|
91
|
+
e.with_config(config)
|
92
|
+
raise
|
33
93
|
end
|
34
94
|
|
35
95
|
def send_command(method, command, *args, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
when 'lastsave' then @node.call_all(method, command, args).sort_by(&:to_i).then(&TSF.call(block))
|
44
|
-
when 'role' then @node.call_all(method, command, args, &block)
|
45
|
-
when 'config' then send_config_command(method, command, args, &block)
|
46
|
-
when 'client' then send_client_command(method, command, args, &block)
|
47
|
-
when 'cluster' then send_cluster_command(method, command, args, &block)
|
48
|
-
when 'memory' then send_memory_command(method, command, args, &block)
|
49
|
-
when 'script' then send_script_command(method, command, args, &block)
|
50
|
-
when 'pubsub' then send_pubsub_command(method, command, args, &block)
|
51
|
-
when 'watch' then send_watch_command(command, &block)
|
52
|
-
when 'mset', 'mget', 'del'
|
53
|
-
send_multiple_keys_command(cmd, method, command, args, &block)
|
54
|
-
when 'acl', 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save'
|
55
|
-
@node.call_all(method, command, args).first.then(&TSF.call(block))
|
56
|
-
when 'flushall', 'flushdb'
|
57
|
-
@node.call_primaries(method, command, args).first.then(&TSF.call(block))
|
58
|
-
when 'readonly', 'readwrite', 'shutdown'
|
59
|
-
raise ::RedisClient::Cluster::OrchestrationCommandNotSupported, cmd
|
60
|
-
when 'discard', 'exec', 'multi', 'unwatch'
|
61
|
-
raise ::RedisClient::Cluster::AmbiguousNodeError, cmd
|
62
|
-
else
|
63
|
-
node = assign_node(command)
|
64
|
-
try_send(node, method, command, args, &block)
|
65
|
-
end
|
96
|
+
return assign_node_and_send_command(method, command, args, &block) unless DEDICATED_ACTIONS.key?(command.first)
|
97
|
+
|
98
|
+
action = DEDICATED_ACTIONS[command.first]
|
99
|
+
return send(action.method_name, method, command, args, &block) if action.reply_transformer.nil?
|
100
|
+
|
101
|
+
reply = send(action.method_name, method, command, args)
|
102
|
+
action.reply_transformer.call(reply).then(&TSF.call(block))
|
66
103
|
rescue ::RedisClient::CircuitBreaker::OpenCircuitError
|
67
104
|
raise
|
68
105
|
rescue ::RedisClient::Cluster::Node::ReloadNeeded
|
69
|
-
|
70
|
-
raise ::RedisClient::Cluster::NodeMightBeDown
|
106
|
+
renew_cluster_state
|
107
|
+
raise ::RedisClient::Cluster::NodeMightBeDown.new.with_config(@config)
|
108
|
+
rescue ::RedisClient::ConnectionError
|
109
|
+
renew_cluster_state
|
110
|
+
raise
|
111
|
+
rescue ::RedisClient::CommandError => e
|
112
|
+
renew_cluster_state if e.message.start_with?('CLUSTERDOWN')
|
113
|
+
raise
|
71
114
|
rescue ::RedisClient::Cluster::ErrorCollection => e
|
115
|
+
e.with_config(@config)
|
72
116
|
raise if e.errors.any?(::RedisClient::CircuitBreaker::OpenCircuitError)
|
73
117
|
|
74
|
-
|
118
|
+
renew_cluster_state if e.errors.values.any? do |err|
|
75
119
|
next false if ::RedisClient::Cluster::ErrorIdentification.identifiable?(err) && @node.none? { |c| ::RedisClient::Cluster::ErrorIdentification.client_owns_error?(err, c) }
|
76
120
|
|
77
|
-
err.message.start_with?('CLUSTERDOWN
|
121
|
+
err.message.start_with?('CLUSTERDOWN') || err.is_a?(::RedisClient::ConnectionError)
|
78
122
|
end
|
79
123
|
|
80
124
|
raise
|
81
125
|
end
|
82
126
|
|
83
127
|
# @see https://redis.io/docs/reference/cluster-spec/#redirection-and-resharding Redirection and resharding
|
84
|
-
def
|
85
|
-
|
128
|
+
def assign_node_and_send_command(method, command, args, retry_count: 3, &block)
|
129
|
+
node = assign_node(command)
|
130
|
+
send_command_to_node(node, method, command, args, retry_count: retry_count, &block)
|
131
|
+
end
|
132
|
+
|
133
|
+
def send_command_to_node(node, method, command, args, retry_count: 3, &block)
|
134
|
+
handle_redirection(node, command, retry_count: retry_count) do |on_node|
|
86
135
|
if args.empty?
|
87
136
|
# prevent memory allocation for variable-length args
|
88
137
|
on_node.public_send(method, command, &block)
|
@@ -92,50 +141,50 @@ class RedisClient
|
|
92
141
|
end
|
93
142
|
end
|
94
143
|
|
95
|
-
def
|
96
|
-
handle_redirection(node, retry_count: retry_count) do |on_node|
|
97
|
-
on_node.public_send(method, *args, **kwargs, &block)
|
98
|
-
end
|
99
|
-
end
|
100
|
-
|
101
|
-
def handle_redirection(node, retry_count:) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
144
|
+
def handle_redirection(node, command, retry_count:) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
102
145
|
yield node
|
103
146
|
rescue ::RedisClient::CircuitBreaker::OpenCircuitError
|
104
147
|
raise
|
105
148
|
rescue ::RedisClient::CommandError => e
|
106
149
|
raise unless ::RedisClient::Cluster::ErrorIdentification.client_owns_error?(e, node)
|
107
150
|
|
151
|
+
retry_count -= 1
|
108
152
|
if e.message.start_with?('MOVED')
|
109
153
|
node = assign_redirection_node(e.message)
|
110
|
-
retry_count -= 1
|
111
154
|
retry if retry_count >= 0
|
112
155
|
elsif e.message.start_with?('ASK')
|
113
156
|
node = assign_asking_node(e.message)
|
114
|
-
retry_count -= 1
|
115
157
|
if retry_count >= 0
|
116
|
-
node.call('
|
158
|
+
node.call('asking')
|
117
159
|
retry
|
118
160
|
end
|
119
|
-
elsif e.message.start_with?('CLUSTERDOWN
|
120
|
-
|
121
|
-
retry_count -= 1
|
161
|
+
elsif e.message.start_with?('CLUSTERDOWN')
|
162
|
+
renew_cluster_state
|
122
163
|
retry if retry_count >= 0
|
123
164
|
end
|
165
|
+
|
124
166
|
raise
|
125
167
|
rescue ::RedisClient::ConnectionError => e
|
126
168
|
raise unless ::RedisClient::Cluster::ErrorIdentification.client_owns_error?(e, node)
|
127
169
|
|
128
|
-
update_cluster_info!
|
129
|
-
|
130
|
-
raise if retry_count <= 0
|
131
|
-
|
132
170
|
retry_count -= 1
|
133
|
-
|
134
|
-
|
171
|
+
renew_cluster_state
|
172
|
+
|
173
|
+
if retry_count >= 0
|
174
|
+
# Find the node to use for this command - if this fails for some reason, though, re-use
|
175
|
+
# the old node.
|
176
|
+
begin
|
177
|
+
node = find_node(find_node_key(command)) if command
|
178
|
+
rescue StandardError # rubocop:disable Lint/SuppressedException
|
179
|
+
end
|
180
|
+
retry
|
181
|
+
end
|
135
182
|
|
136
|
-
|
137
|
-
|
183
|
+
retry if retry_count >= 0
|
184
|
+
raise
|
185
|
+
end
|
138
186
|
|
187
|
+
def scan(command, seed: nil) # rubocop:disable Metrics/AbcSize
|
139
188
|
command[1] = ZERO_CURSOR_FOR_SCAN if command.size == 1
|
140
189
|
input_cursor = Integer(command[1])
|
141
190
|
|
@@ -155,25 +204,47 @@ class RedisClient
|
|
155
204
|
client_index += 1 if result_cursor == 0
|
156
205
|
|
157
206
|
[((result_cursor << 8) + client_index).to_s, result_keys]
|
207
|
+
rescue ::RedisClient::ConnectionError
|
208
|
+
renew_cluster_state
|
209
|
+
raise
|
210
|
+
end
|
211
|
+
|
212
|
+
def scan_single_key(command, arity:, &block)
|
213
|
+
node = assign_node(command)
|
214
|
+
loop do
|
215
|
+
cursor, values = handle_redirection(node, nil, retry_count: 3) { |n| n.call_v(command) }
|
216
|
+
command[2] = cursor
|
217
|
+
arity < 2 ? values.each(&block) : values.each_slice(arity, &block)
|
218
|
+
break if cursor == ZERO_CURSOR_FOR_SCAN
|
219
|
+
end
|
158
220
|
end
|
159
221
|
|
160
222
|
def assign_node(command)
|
161
|
-
|
162
|
-
|
223
|
+
handle_node_reload_error do
|
224
|
+
node_key = find_node_key(command)
|
225
|
+
@node.find_by(node_key)
|
226
|
+
end
|
163
227
|
end
|
164
228
|
|
165
229
|
def find_node_key_by_key(key, seed: nil, primary: false)
|
166
230
|
if key && !key.empty?
|
167
231
|
slot = ::RedisClient::Cluster::KeySlotConverter.convert(key)
|
168
|
-
primary ? @node.find_node_key_of_primary(slot) : @node.find_node_key_of_replica(slot)
|
232
|
+
node_key = primary ? @node.find_node_key_of_primary(slot) : @node.find_node_key_of_replica(slot)
|
233
|
+
if node_key.nil?
|
234
|
+
renew_cluster_state
|
235
|
+
raise ::RedisClient::Cluster::NodeMightBeDown.new.with_config(@config)
|
236
|
+
end
|
237
|
+
node_key
|
169
238
|
else
|
170
239
|
primary ? @node.any_primary_node_key(seed: seed) : @node.any_replica_node_key(seed: seed)
|
171
240
|
end
|
172
241
|
end
|
173
242
|
|
174
243
|
def find_primary_node_by_slot(slot)
|
175
|
-
|
176
|
-
|
244
|
+
handle_node_reload_error do
|
245
|
+
node_key = @node.find_node_key_of_primary(slot)
|
246
|
+
@node.find_by(node_key)
|
247
|
+
end
|
177
248
|
end
|
178
249
|
|
179
250
|
def find_node_key(command, seed: nil)
|
@@ -198,14 +269,8 @@ class RedisClient
|
|
198
269
|
::RedisClient::Cluster::KeySlotConverter.convert(key)
|
199
270
|
end
|
200
271
|
|
201
|
-
def find_node(node_key
|
202
|
-
@node.find_by(node_key)
|
203
|
-
rescue ::RedisClient::Cluster::Node::ReloadNeeded
|
204
|
-
raise ::RedisClient::Cluster::NodeMightBeDown if retry_count <= 0
|
205
|
-
|
206
|
-
update_cluster_info!
|
207
|
-
retry_count -= 1
|
208
|
-
retry
|
272
|
+
def find_node(node_key)
|
273
|
+
handle_node_reload_error { @node.find_by(node_key) }
|
209
274
|
end
|
210
275
|
|
211
276
|
def command_exists?(name)
|
@@ -216,109 +281,178 @@ class RedisClient
|
|
216
281
|
_, slot, node_key = err_msg.split
|
217
282
|
slot = slot.to_i
|
218
283
|
@node.update_slot(slot, node_key)
|
219
|
-
|
284
|
+
handle_node_reload_error { @node.find_by(node_key) }
|
220
285
|
end
|
221
286
|
|
222
287
|
def assign_asking_node(err_msg)
|
223
288
|
_, _, node_key = err_msg.split
|
224
|
-
|
289
|
+
handle_node_reload_error { @node.find_by(node_key) }
|
225
290
|
end
|
226
291
|
|
227
292
|
def node_keys
|
228
293
|
@node.node_keys
|
229
294
|
end
|
230
295
|
|
296
|
+
def renew_cluster_state
|
297
|
+
@node.reload!
|
298
|
+
rescue ::RedisClient::Cluster::InitialSetupError
|
299
|
+
# ignore
|
300
|
+
end
|
301
|
+
|
231
302
|
def close
|
232
303
|
@node.each(&:close)
|
233
304
|
end
|
234
305
|
|
235
306
|
private
|
236
307
|
|
237
|
-
def
|
308
|
+
def send_command_to_all_nodes(method, command, args, &block)
|
309
|
+
@node.call_all(method, command, args, &block)
|
310
|
+
end
|
311
|
+
|
312
|
+
def send_command_to_primaries(method, command, args, &block)
|
313
|
+
@node.call_primaries(method, command, args, &block)
|
314
|
+
end
|
315
|
+
|
316
|
+
def send_command_to_replicas(method, command, args, &block)
|
317
|
+
@node.call_replicas(method, command, args, &block)
|
318
|
+
end
|
319
|
+
|
320
|
+
def send_ping_command(method, command, args, &block)
|
321
|
+
@node.send_ping(method, command, args, &block)
|
322
|
+
end
|
323
|
+
|
324
|
+
def send_scan_command(_method, command, _args, &_block)
|
325
|
+
scan(command, seed: 1)
|
326
|
+
end
|
327
|
+
|
328
|
+
def fail_not_supported_command(_method, command, _args, &_block)
|
329
|
+
raise ::RedisClient::Cluster::OrchestrationCommandNotSupported.from_command(command.first).with_config(@config)
|
330
|
+
end
|
331
|
+
|
332
|
+
def fail_keyless_command(_method, command, _args, &_block)
|
333
|
+
raise ::RedisClient::Cluster::AmbiguousNodeError.from_command(command.first).with_config(@config)
|
334
|
+
end
|
335
|
+
|
336
|
+
def send_wait_command(method, command, args, retry_count: 1, &block) # rubocop:disable Metrics/AbcSize
|
238
337
|
@node.call_primaries(method, command, args).select { |r| r.is_a?(Integer) }.sum.then(&TSF.call(block))
|
239
338
|
rescue ::RedisClient::Cluster::ErrorCollection => e
|
240
339
|
raise if e.errors.any?(::RedisClient::CircuitBreaker::OpenCircuitError)
|
241
340
|
raise if retry_count <= 0
|
242
|
-
raise if e.errors.values.none?
|
243
|
-
err.message.include?('WAIT cannot be used with replica instances')
|
244
|
-
end
|
341
|
+
raise if e.errors.values.none? { |err| err.message.include?('WAIT cannot be used with replica instances') }
|
245
342
|
|
246
|
-
update_cluster_info!
|
247
343
|
retry_count -= 1
|
344
|
+
renew_cluster_state
|
248
345
|
retry
|
249
346
|
end
|
250
347
|
|
251
|
-
def send_config_command(method, command, args, &block)
|
252
|
-
|
253
|
-
|
348
|
+
def send_config_command(method, command, args, &block) # rubocop:disable Metrics/AbcSize
|
349
|
+
if command[1].casecmp('resetstat').zero?
|
350
|
+
@node.call_all(method, command, args).first.then(&TSF.call(block))
|
351
|
+
elsif command[1].casecmp('rewrite').zero?
|
254
352
|
@node.call_all(method, command, args).first.then(&TSF.call(block))
|
255
|
-
|
353
|
+
elsif command[1].casecmp('set').zero?
|
354
|
+
@node.call_all(method, command, args).first.then(&TSF.call(block))
|
355
|
+
else
|
356
|
+
assign_node(command).public_send(method, *args, command, &block)
|
256
357
|
end
|
257
358
|
end
|
258
359
|
|
259
360
|
def send_memory_command(method, command, args, &block)
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
|
361
|
+
if command[1].casecmp('stats').zero?
|
362
|
+
@node.call_all(method, command, args, &block)
|
363
|
+
elsif command[1].casecmp('purge').zero?
|
364
|
+
@node.call_all(method, command, args).first.then(&TSF.call(block))
|
365
|
+
else
|
366
|
+
assign_node(command).public_send(method, *args, command, &block)
|
264
367
|
end
|
265
368
|
end
|
266
369
|
|
267
|
-
def send_client_command(method, command, args, &block)
|
268
|
-
|
269
|
-
|
270
|
-
|
370
|
+
def send_client_command(method, command, args, &block) # rubocop:disable Metrics/AbcSize
|
371
|
+
if command[1].casecmp('list').zero?
|
372
|
+
@node.call_all(method, command, args, &block).flatten
|
373
|
+
elsif command[1].casecmp('pause').zero?
|
374
|
+
@node.call_all(method, command, args).first.then(&TSF.call(block))
|
375
|
+
elsif command[1].casecmp('reply').zero?
|
271
376
|
@node.call_all(method, command, args).first.then(&TSF.call(block))
|
272
|
-
|
377
|
+
elsif command[1].casecmp('setname').zero?
|
378
|
+
@node.call_all(method, command, args).first.then(&TSF.call(block))
|
379
|
+
else
|
380
|
+
assign_node(command).public_send(method, *args, command, &block)
|
273
381
|
end
|
274
382
|
end
|
275
383
|
|
276
|
-
def send_cluster_command(method, command, args, &block)
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
|
384
|
+
def send_cluster_command(method, command, args, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
385
|
+
if command[1].casecmp('addslots').zero?
|
386
|
+
fail_not_supported_command(method, command, args, &block)
|
387
|
+
elsif command[1].casecmp('delslots').zero?
|
388
|
+
fail_not_supported_command(method, command, args, &block)
|
389
|
+
elsif command[1].casecmp('failover').zero?
|
390
|
+
fail_not_supported_command(method, command, args, &block)
|
391
|
+
elsif command[1].casecmp('forget').zero?
|
392
|
+
fail_not_supported_command(method, command, args, &block)
|
393
|
+
elsif command[1].casecmp('meet').zero?
|
394
|
+
fail_not_supported_command(method, command, args, &block)
|
395
|
+
elsif command[1].casecmp('replicate').zero?
|
396
|
+
fail_not_supported_command(method, command, args, &block)
|
397
|
+
elsif command[1].casecmp('reset').zero?
|
398
|
+
fail_not_supported_command(method, command, args, &block)
|
399
|
+
elsif command[1].casecmp('set-config-epoch').zero?
|
400
|
+
fail_not_supported_command(method, command, args, &block)
|
401
|
+
elsif command[1].casecmp('setslot').zero?
|
402
|
+
fail_not_supported_command(method, command, args, &block)
|
403
|
+
elsif command[1].casecmp('saveconfig').zero?
|
404
|
+
@node.call_all(method, command, args).first.then(&TSF.call(block))
|
405
|
+
elsif command[1].casecmp('getkeysinslot').zero?
|
283
406
|
raise ArgumentError, command.join(' ') if command.size != 4
|
284
407
|
|
285
|
-
|
286
|
-
|
408
|
+
handle_node_reload_error do
|
409
|
+
node_key = @node.find_node_key_of_replica(command[2])
|
410
|
+
@node.find_by(node_key).public_send(method, *args, command, &block)
|
411
|
+
end
|
412
|
+
else
|
413
|
+
assign_node(command).public_send(method, *args, command, &block)
|
287
414
|
end
|
288
415
|
end
|
289
416
|
|
290
|
-
def send_script_command(method, command, args, &block) # rubocop:disable Metrics/AbcSize
|
291
|
-
|
292
|
-
|
417
|
+
def send_script_command(method, command, args, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
418
|
+
if command[1].casecmp('debug').zero?
|
419
|
+
@node.call_all(method, command, args).first.then(&TSF.call(block))
|
420
|
+
elsif command[1].casecmp('kill').zero?
|
293
421
|
@node.call_all(method, command, args).first.then(&TSF.call(block))
|
294
|
-
|
422
|
+
elsif command[1].casecmp('flush').zero?
|
295
423
|
@node.call_primaries(method, command, args).first.then(&TSF.call(block))
|
296
|
-
|
424
|
+
elsif command[1].casecmp('load').zero?
|
425
|
+
@node.call_primaries(method, command, args).first.then(&TSF.call(block))
|
426
|
+
elsif command[1].casecmp('exists').zero?
|
297
427
|
@node.call_all(method, command, args).transpose.map { |arr| arr.any?(&:zero?) ? 0 : 1 }.then(&TSF.call(block))
|
298
|
-
else
|
428
|
+
else
|
429
|
+
assign_node(command).public_send(method, *args, command, &block)
|
299
430
|
end
|
300
431
|
end
|
301
432
|
|
302
433
|
def send_pubsub_command(method, command, args, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
303
|
-
|
304
|
-
when 'channels'
|
434
|
+
if command[1].casecmp('channels').zero?
|
305
435
|
@node.call_all(method, command, args).flatten.uniq.sort_by(&:to_s).then(&TSF.call(block))
|
306
|
-
|
436
|
+
elsif command[1].casecmp('shardchannels').zero?
|
307
437
|
@node.call_replicas(method, command, args).flatten.uniq.sort_by(&:to_s).then(&TSF.call(block))
|
308
|
-
|
438
|
+
elsif command[1].casecmp('numpat').zero?
|
309
439
|
@node.call_all(method, command, args).select { |e| e.is_a?(Integer) }.sum.then(&TSF.call(block))
|
310
|
-
|
440
|
+
elsif command[1].casecmp('numsub').zero?
|
311
441
|
@node.call_all(method, command, args).reject(&:empty?).map { |e| Hash[*e] }
|
312
442
|
.reduce({}) { |a, e| a.merge(e) { |_, v1, v2| v1 + v2 } }.then(&TSF.call(block))
|
313
|
-
|
443
|
+
elsif command[1].casecmp('shardnumsub').zero?
|
314
444
|
@node.call_replicas(method, command, args).reject(&:empty?).map { |e| Hash[*e] }
|
315
445
|
.reduce({}) { |a, e| a.merge(e) { |_, v1, v2| v1 + v2 } }.then(&TSF.call(block))
|
316
|
-
else
|
446
|
+
else
|
447
|
+
assign_node(command).public_send(method, *args, command, &block)
|
317
448
|
end
|
318
449
|
end
|
319
450
|
|
320
|
-
def send_watch_command(command)
|
321
|
-
|
451
|
+
def send_watch_command(_method, command, _args, &_block)
|
452
|
+
unless block_given?
|
453
|
+
msg = 'A block required. And you need to use the block argument as a client for the transaction.'
|
454
|
+
raise ::RedisClient::Cluster::Transaction::ConsistencyError.new(msg).with_config(@config)
|
455
|
+
end
|
322
456
|
|
323
457
|
::RedisClient::Cluster::OptimisticLocking.new(self).watch(command[1..]) do |c, slot, asking|
|
324
458
|
transaction = ::RedisClient::Cluster::Transaction.new(
|
@@ -329,17 +463,23 @@ class RedisClient
|
|
329
463
|
end
|
330
464
|
end
|
331
465
|
|
332
|
-
|
333
|
-
'mget' => ['get', 1].freeze,
|
334
|
-
'mset' => ['set', 2].freeze,
|
335
|
-
'del' => ['del', 1].freeze
|
336
|
-
}.freeze
|
337
|
-
|
338
|
-
def send_multiple_keys_command(cmd, method, command, args, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
466
|
+
def send_multiple_keys_command(method, command, args, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
339
467
|
# This implementation is prioritized performance rather than readability or so.
|
340
|
-
|
468
|
+
cmd = command.first
|
469
|
+
if cmd.casecmp('mget').zero?
|
470
|
+
single_key_cmd = 'get'
|
471
|
+
keys_step = 1
|
472
|
+
elsif cmd.casecmp('mset').zero?
|
473
|
+
single_key_cmd = 'set'
|
474
|
+
keys_step = 2
|
475
|
+
elsif cmd.casecmp('del').zero?
|
476
|
+
single_key_cmd = 'del'
|
477
|
+
keys_step = 1
|
478
|
+
else
|
479
|
+
raise NotImplementedError, cmd
|
480
|
+
end
|
341
481
|
|
342
|
-
return
|
482
|
+
return assign_node_and_send_command(method, command, args, &block) if command.size <= keys_step + 1 || ::RedisClient::Cluster::KeySlotConverter.hash_tag_included?(command[1])
|
343
483
|
|
344
484
|
seed = @config.use_replica? && @config.replica_affinity == :random ? nil : Random.new_seed
|
345
485
|
pipeline = ::RedisClient::Cluster::Pipeline.new(self, @command_builder, @concurrent_worker, exception: true, seed: seed)
|
@@ -359,16 +499,24 @@ class RedisClient
|
|
359
499
|
end
|
360
500
|
|
361
501
|
replies = pipeline.execute
|
362
|
-
result =
|
363
|
-
|
364
|
-
|
365
|
-
|
502
|
+
result = if cmd.casecmp('mset').zero?
|
503
|
+
replies.first
|
504
|
+
elsif cmd.casecmp('del').zero?
|
505
|
+
replies.sum
|
506
|
+
else
|
507
|
+
replies
|
366
508
|
end
|
367
509
|
block_given? ? yield(result) : result
|
368
510
|
end
|
369
511
|
|
370
|
-
def
|
371
|
-
|
512
|
+
def handle_node_reload_error(retry_count: 1)
|
513
|
+
yield
|
514
|
+
rescue ::RedisClient::Cluster::Node::ReloadNeeded
|
515
|
+
raise ::RedisClient::Cluster::NodeMightBeDown.new.with_config(@config) if retry_count <= 0
|
516
|
+
|
517
|
+
retry_count -= 1
|
518
|
+
renew_cluster_state
|
519
|
+
retry
|
372
520
|
end
|
373
521
|
end
|
374
522
|
end
|