redis-cluster-client 0.3.4 → 0.3.6
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 +3 -6
- data/lib/redis_client/cluster/node/latency_replica.rb +10 -10
- data/lib/redis_client/cluster/node.rb +34 -16
- data/lib/redis_client/cluster/normalized_cmd_name.rb +1 -0
- data/lib/redis_client/cluster/pipeline.rb +185 -33
- data/lib/redis_client/cluster/router.rb +18 -18
- data/lib/redis_client/cluster.rb +1 -1
- data/lib/redis_client/cluster_config.rb +1 -1
- metadata +2 -2
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 24e153973525e8cd3905e92e851194a5c1f4349cecd71ca4a71db34fa678d1d8
|
|
4
|
+
data.tar.gz: 963ac53058e861d50c55160f55f9b064e853c49b29811075ece80ce34fa7b0f8
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 841bdd5aa580dcd7c3169fe88f36b8ed888ffffd3cf50b9f75449365c430afb41d79d88f2265183119f0ed012ace418868679cad468bcf39b25aed30b6207da1
|
|
7
|
+
data.tar.gz: 5f36751289ba73d5471cfe44d9e3d10cb70fd517249ce48ad7ea4254de0595c869fb900fecd3b0abd690fe977a732650ebf8da80a5d95ed1464a3345b79243fa
|
|
@@ -10,7 +10,7 @@ class RedisClient
|
|
|
10
10
|
EMPTY_STRING = ''
|
|
11
11
|
|
|
12
12
|
class << self
|
|
13
|
-
def load(nodes)
|
|
13
|
+
def load(nodes)
|
|
14
14
|
errors = []
|
|
15
15
|
cmd = nil
|
|
16
16
|
nodes&.each do |node|
|
|
@@ -32,10 +32,7 @@ class RedisClient
|
|
|
32
32
|
|
|
33
33
|
def parse_command_details(rows)
|
|
34
34
|
rows&.reject { |row| row[0].nil? }.to_h do |row|
|
|
35
|
-
[
|
|
36
|
-
::RedisClient::Cluster::NormalizedCmdName.instance.get_by_name(row[0]),
|
|
37
|
-
{ arity: row[1], flags: row[2], first: row[3], last: row[4], step: row[5] }
|
|
38
|
-
]
|
|
35
|
+
[row[0].downcase, { arity: row[1], flags: row[2], first: row[3], last: row[4], step: row[5] }]
|
|
39
36
|
end
|
|
40
37
|
end
|
|
41
38
|
end
|
|
@@ -85,7 +82,7 @@ class RedisClient
|
|
|
85
82
|
@details.fetch(name).fetch(key)
|
|
86
83
|
end
|
|
87
84
|
|
|
88
|
-
def determine_first_key_position(command) # rubocop:disable Metrics/CyclomaticComplexity
|
|
85
|
+
def determine_first_key_position(command) # rubocop:disable Metrics/CyclomaticComplexity
|
|
89
86
|
case ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_command(command)
|
|
90
87
|
when 'eval', 'evalsha', 'zinterstore', 'zunionstore' then 3
|
|
91
88
|
when 'object' then 2
|
|
@@ -34,18 +34,17 @@ class RedisClient
|
|
|
34
34
|
|
|
35
35
|
def any_replica_node_key(seed: nil)
|
|
36
36
|
random = seed.nil? ? Random : Random.new(seed)
|
|
37
|
-
@existed_replicas.sample(random: random)
|
|
37
|
+
@existed_replicas.sample(random: random)&.first
|
|
38
38
|
end
|
|
39
39
|
|
|
40
40
|
private
|
|
41
41
|
|
|
42
|
-
def measure_latencies(clients) # rubocop:disable Metrics/
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
clients.each_slice(::RedisClient::Cluster::Node::MAX_THREADS) do |chuncked_clients|
|
|
42
|
+
def measure_latencies(clients) # rubocop:disable Metrics/AbcSize
|
|
43
|
+
clients.each_slice(::RedisClient::Cluster::Node::MAX_THREADS).each_with_object({}) do |chuncked_clients, acc|
|
|
46
44
|
threads = chuncked_clients.map do |k, v|
|
|
47
45
|
Thread.new(k, v) do |node_key, client|
|
|
48
46
|
Thread.pass
|
|
47
|
+
Thread.current.thread_variable_set(:node_key, node_key)
|
|
49
48
|
|
|
50
49
|
min = DUMMY_LATENCY_NSEC
|
|
51
50
|
MEASURE_ATTEMPT_COUNT.times do
|
|
@@ -55,16 +54,17 @@ class RedisClient
|
|
|
55
54
|
min = duration if duration < min
|
|
56
55
|
end
|
|
57
56
|
|
|
58
|
-
|
|
57
|
+
Thread.current.thread_variable_set(:latency, min)
|
|
59
58
|
rescue StandardError
|
|
60
|
-
|
|
59
|
+
Thread.current.thread_variable_set(:latency, DUMMY_LATENCY_NSEC)
|
|
61
60
|
end
|
|
62
61
|
end
|
|
63
62
|
|
|
64
|
-
threads.each
|
|
63
|
+
threads.each do |t|
|
|
64
|
+
t.join
|
|
65
|
+
acc[t.thread_variable_get(:node_key)] = t.thread_variable_get(:latency)
|
|
66
|
+
end
|
|
65
67
|
end
|
|
66
|
-
|
|
67
|
-
latencies
|
|
68
68
|
end
|
|
69
69
|
|
|
70
70
|
def select_replica_clients(replications, clients)
|
|
@@ -37,29 +37,38 @@ class RedisClient
|
|
|
37
37
|
end
|
|
38
38
|
|
|
39
39
|
class << self
|
|
40
|
-
def load_info(options, **kwargs) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/
|
|
40
|
+
def load_info(options, **kwargs) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
41
41
|
startup_size = options.size > MAX_STARTUP_SAMPLE ? MAX_STARTUP_SAMPLE : options.size
|
|
42
|
-
node_info_list =
|
|
43
|
-
errors = Array.new(startup_size)
|
|
42
|
+
node_info_list = errors = nil
|
|
44
43
|
startup_options = options.to_a.sample(MAX_STARTUP_SAMPLE).to_h
|
|
45
44
|
startup_nodes = ::RedisClient::Cluster::Node.new(startup_options, **kwargs)
|
|
46
45
|
startup_nodes.each_slice(MAX_THREADS).with_index do |chuncked_startup_nodes, chuncked_idx|
|
|
47
46
|
threads = chuncked_startup_nodes.each_with_index.map do |raw_client, idx|
|
|
48
47
|
Thread.new(raw_client, (MAX_THREADS * chuncked_idx) + idx) do |cli, i|
|
|
49
48
|
Thread.pass
|
|
49
|
+
Thread.current.thread_variable_set(:index, i)
|
|
50
50
|
reply = cli.call('CLUSTER', 'NODES')
|
|
51
|
-
|
|
51
|
+
Thread.current.thread_variable_set(:info, parse_node_info(reply))
|
|
52
52
|
rescue StandardError => e
|
|
53
|
-
|
|
53
|
+
Thread.current.thread_variable_set(:error, e)
|
|
54
54
|
ensure
|
|
55
55
|
cli&.close
|
|
56
56
|
end
|
|
57
57
|
end
|
|
58
58
|
|
|
59
|
-
threads.each
|
|
59
|
+
threads.each do |t|
|
|
60
|
+
t.join
|
|
61
|
+
if t.thread_variable?(:info)
|
|
62
|
+
node_info_list ||= Array.new(startup_size)
|
|
63
|
+
node_info_list[t.thread_variable_get(:index)] = t.thread_variable_get(:info)
|
|
64
|
+
elsif t.thread_variable?(:error)
|
|
65
|
+
errors ||= Array.new(startup_size)
|
|
66
|
+
errors[t.thread_variable_get(:index)] = t.thread_variable_get(:error)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
60
69
|
end
|
|
61
70
|
|
|
62
|
-
raise ::RedisClient::Cluster::InitialSetupError, errors if node_info_list.
|
|
71
|
+
raise ::RedisClient::Cluster::InitialSetupError, errors if node_info_list.nil?
|
|
63
72
|
|
|
64
73
|
grouped = node_info_list.compact.group_by do |rows|
|
|
65
74
|
rows.sort_by { |row| row[:id] }
|
|
@@ -74,7 +83,7 @@ class RedisClient
|
|
|
74
83
|
|
|
75
84
|
# @see https://redis.io/commands/cluster-nodes/
|
|
76
85
|
# @see https://github.com/redis/redis/blob/78960ad57b8a5e6af743d789ed8fd767e37d42b8/src/cluster.c#L4660-L4683
|
|
77
|
-
def parse_node_info(info) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
86
|
+
def parse_node_info(info) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
78
87
|
rows = info.split("\n").map(&:split)
|
|
79
88
|
rows.each { |arr| arr[2] = arr[2].split(',') }
|
|
80
89
|
rows.select! { |arr| arr[7] == 'connected' && (arr[2] & %w[fail? fail handshake noaddr noflags]).empty? }
|
|
@@ -147,7 +156,7 @@ class RedisClient
|
|
|
147
156
|
|
|
148
157
|
def send_ping(method, command, args, &block)
|
|
149
158
|
result_values, errors = call_multiple_nodes(@topology.clients, method, command, args, &block)
|
|
150
|
-
return result_values if errors.empty?
|
|
159
|
+
return result_values if errors.nil? || errors.empty?
|
|
151
160
|
|
|
152
161
|
raise ReloadNeeded if errors.values.any?(::RedisClient::ConnectionError)
|
|
153
162
|
|
|
@@ -228,26 +237,35 @@ class RedisClient
|
|
|
228
237
|
|
|
229
238
|
def call_multiple_nodes!(clients, method, command, args, &block)
|
|
230
239
|
result_values, errors = call_multiple_nodes(clients, method, command, args, &block)
|
|
231
|
-
return result_values if errors.empty?
|
|
240
|
+
return result_values if errors.nil? || errors.empty?
|
|
232
241
|
|
|
233
242
|
raise ::RedisClient::Cluster::ErrorCollection, errors
|
|
234
243
|
end
|
|
235
244
|
|
|
236
|
-
def try_map(clients) # rubocop:disable Metrics/
|
|
237
|
-
results =
|
|
238
|
-
errors = {}
|
|
245
|
+
def try_map(clients) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
246
|
+
results = errors = nil
|
|
239
247
|
clients.each_slice(MAX_THREADS) do |chuncked_clients|
|
|
240
248
|
threads = chuncked_clients.map do |k, v|
|
|
241
249
|
Thread.new(k, v) do |node_key, client|
|
|
242
250
|
Thread.pass
|
|
251
|
+
Thread.current.thread_variable_set(:node_key, node_key)
|
|
243
252
|
reply = yield(node_key, client)
|
|
244
|
-
|
|
253
|
+
Thread.current.thread_variable_set(:result, reply)
|
|
245
254
|
rescue StandardError => e
|
|
246
|
-
|
|
255
|
+
Thread.current.thread_variable_set(:error, e)
|
|
247
256
|
end
|
|
248
257
|
end
|
|
249
258
|
|
|
250
|
-
threads.each
|
|
259
|
+
threads.each do |t|
|
|
260
|
+
t.join
|
|
261
|
+
if t.thread_variable?(:result)
|
|
262
|
+
results ||= {}
|
|
263
|
+
results[t.thread_variable_get(:node_key)] = t.thread_variable_get(:result)
|
|
264
|
+
elsif t.thread_variable?(:error)
|
|
265
|
+
errors ||= {}
|
|
266
|
+
errors[t.thread_variable_get(:node_key)] = t.thread_variable_get(:error)
|
|
267
|
+
end
|
|
268
|
+
end
|
|
251
269
|
end
|
|
252
270
|
|
|
253
271
|
[results, errors]
|
|
@@ -2,100 +2,252 @@
|
|
|
2
2
|
|
|
3
3
|
require 'redis_client'
|
|
4
4
|
require 'redis_client/cluster/errors'
|
|
5
|
+
require 'redis_client/connection_mixin'
|
|
6
|
+
require 'redis_client/middlewares'
|
|
7
|
+
require 'redis_client/pooled'
|
|
5
8
|
|
|
6
9
|
class RedisClient
|
|
7
10
|
class Cluster
|
|
8
11
|
class Pipeline
|
|
12
|
+
class Extended < ::RedisClient::Pipeline
|
|
13
|
+
attr_reader :outer_indices
|
|
14
|
+
|
|
15
|
+
def initialize(command_builder)
|
|
16
|
+
super
|
|
17
|
+
@outer_indices = nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def add_outer_index(index)
|
|
21
|
+
@outer_indices ||= []
|
|
22
|
+
@outer_indices << index
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def get_inner_index(outer_index)
|
|
26
|
+
@outer_indices&.find_index(outer_index)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def get_callee_method(inner_index)
|
|
30
|
+
if @timeouts.is_a?(Array) && !@timeouts[inner_index].nil?
|
|
31
|
+
:blocking_call_v
|
|
32
|
+
elsif _retryable?
|
|
33
|
+
:call_once_v
|
|
34
|
+
else
|
|
35
|
+
:call_v
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def get_command(inner_index)
|
|
40
|
+
@commands.is_a?(Array) ? @commands[inner_index] : nil
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def get_timeout(inner_index)
|
|
44
|
+
@timeouts.is_a?(Array) ? @timeouts[inner_index] : nil
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def get_block(inner_index)
|
|
48
|
+
@blocks.is_a?(Array) ? @blocks[inner_index] : nil
|
|
49
|
+
end
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
::RedisClient::ConnectionMixin.module_eval do
|
|
53
|
+
def call_pipelined_aware_of_redirection(commands, timeouts) # rubocop:disable Metrics/AbcSize
|
|
54
|
+
size = commands.size
|
|
55
|
+
results = Array.new(commands.size)
|
|
56
|
+
@pending_reads += size
|
|
57
|
+
write_multi(commands)
|
|
58
|
+
|
|
59
|
+
redirection_indices = nil
|
|
60
|
+
size.times do |index|
|
|
61
|
+
timeout = timeouts && timeouts[index]
|
|
62
|
+
result = read(timeout)
|
|
63
|
+
@pending_reads -= 1
|
|
64
|
+
if result.is_a?(CommandError)
|
|
65
|
+
result._set_command(commands[index])
|
|
66
|
+
if result.message.start_with?('MOVED', 'ASK')
|
|
67
|
+
redirection_indices ||= []
|
|
68
|
+
redirection_indices << index
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
|
|
72
|
+
results[index] = result
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
return results if redirection_indices.nil?
|
|
76
|
+
|
|
77
|
+
err = ::RedisClient::Cluster::Pipeline::RedirectionNeeded.new
|
|
78
|
+
err.replies = results
|
|
79
|
+
err.indices = redirection_indices
|
|
80
|
+
raise err
|
|
81
|
+
end
|
|
82
|
+
end
|
|
83
|
+
|
|
9
84
|
ReplySizeError = Class.new(::RedisClient::Error)
|
|
85
|
+
|
|
86
|
+
class RedirectionNeeded < ::RedisClient::Error
|
|
87
|
+
attr_accessor :replies, :indices
|
|
88
|
+
end
|
|
89
|
+
|
|
10
90
|
MAX_THREADS = Integer(ENV.fetch('REDIS_CLIENT_MAX_THREADS', 5))
|
|
11
91
|
|
|
12
92
|
def initialize(router, command_builder, seed: Random.new_seed)
|
|
13
93
|
@router = router
|
|
14
94
|
@command_builder = command_builder
|
|
15
|
-
@grouped = {}
|
|
16
|
-
@size = 0
|
|
17
95
|
@seed = seed
|
|
96
|
+
@pipelines = nil
|
|
97
|
+
@size = 0
|
|
18
98
|
end
|
|
19
99
|
|
|
20
100
|
def call(*args, **kwargs, &block)
|
|
21
101
|
command = @command_builder.generate(args, kwargs)
|
|
22
102
|
node_key = @router.find_node_key(command, seed: @seed)
|
|
23
|
-
|
|
103
|
+
append_pipeline(node_key).call_v(command, &block)
|
|
24
104
|
end
|
|
25
105
|
|
|
26
106
|
def call_v(args, &block)
|
|
27
107
|
command = @command_builder.generate(args)
|
|
28
108
|
node_key = @router.find_node_key(command, seed: @seed)
|
|
29
|
-
|
|
109
|
+
append_pipeline(node_key).call_v(command, &block)
|
|
30
110
|
end
|
|
31
111
|
|
|
32
112
|
def call_once(*args, **kwargs, &block)
|
|
33
113
|
command = @command_builder.generate(args, kwargs)
|
|
34
114
|
node_key = @router.find_node_key(command, seed: @seed)
|
|
35
|
-
|
|
115
|
+
append_pipeline(node_key).call_once_v(command, &block)
|
|
36
116
|
end
|
|
37
117
|
|
|
38
118
|
def call_once_v(args, &block)
|
|
39
119
|
command = @command_builder.generate(args)
|
|
40
120
|
node_key = @router.find_node_key(command, seed: @seed)
|
|
41
|
-
|
|
121
|
+
append_pipeline(node_key).call_once_v(command, &block)
|
|
42
122
|
end
|
|
43
123
|
|
|
44
124
|
def blocking_call(timeout, *args, **kwargs, &block)
|
|
45
125
|
command = @command_builder.generate(args, kwargs)
|
|
46
126
|
node_key = @router.find_node_key(command, seed: @seed)
|
|
47
|
-
|
|
127
|
+
append_pipeline(node_key).blocking_call_v(timeout, command, &block)
|
|
48
128
|
end
|
|
49
129
|
|
|
50
130
|
def blocking_call_v(timeout, args, &block)
|
|
51
131
|
command = @command_builder.generate(args)
|
|
52
132
|
node_key = @router.find_node_key(command, seed: @seed)
|
|
53
|
-
|
|
133
|
+
append_pipeline(node_key).blocking_call_v(timeout, command, &block)
|
|
54
134
|
end
|
|
55
135
|
|
|
56
136
|
def empty?
|
|
57
137
|
@size.zero?
|
|
58
138
|
end
|
|
59
139
|
|
|
60
|
-
#
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
threads = chuncked_grouped.map do |k, v|
|
|
66
|
-
Thread.new(@router, k, v) do |router, node_key, rows|
|
|
140
|
+
def execute # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
141
|
+
all_replies = errors = nil
|
|
142
|
+
@pipelines&.each_slice(MAX_THREADS) do |chuncked_pipelines|
|
|
143
|
+
threads = chuncked_pipelines.map do |node_key, pipeline|
|
|
144
|
+
Thread.new(node_key, pipeline) do |nk, pl|
|
|
67
145
|
Thread.pass
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
when 4 then pipeline.send(row[1], row[2], &row[3])
|
|
72
|
-
when 5 then pipeline.send(row[1], row[2], row[3], &row[4])
|
|
73
|
-
end
|
|
74
|
-
end
|
|
75
|
-
end
|
|
76
|
-
|
|
77
|
-
raise ReplySizeError, "commands: #{rows.size}, replies: #{replies.size}" if rows.size != replies.size
|
|
146
|
+
Thread.current.thread_variable_set(:node_key, nk)
|
|
147
|
+
replies = do_pipelining(@router.find_node(nk), pl)
|
|
148
|
+
raise ReplySizeError, "commands: #{pl._size}, replies: #{replies.size}" if pl._size != replies.size
|
|
78
149
|
|
|
79
|
-
|
|
150
|
+
Thread.current.thread_variable_set(:replies, replies)
|
|
151
|
+
rescue ::RedisClient::Cluster::Pipeline::RedirectionNeeded => e
|
|
152
|
+
Thread.current.thread_variable_set(:redirection_needed, e)
|
|
80
153
|
rescue StandardError => e
|
|
81
|
-
|
|
154
|
+
Thread.current.thread_variable_set(:error, e)
|
|
82
155
|
end
|
|
83
156
|
end
|
|
84
157
|
|
|
85
|
-
threads.each
|
|
158
|
+
threads.each do |t|
|
|
159
|
+
t.join
|
|
160
|
+
|
|
161
|
+
if t.thread_variable?(:replies)
|
|
162
|
+
all_replies ||= Array.new(@size)
|
|
163
|
+
@pipelines[t.thread_variable_get(:node_key)]
|
|
164
|
+
.outer_indices
|
|
165
|
+
.each_with_index { |outer, inner| all_replies[outer] = t.thread_variable_get(:replies)[inner] }
|
|
166
|
+
elsif t.thread_variable?(:redirection_needed)
|
|
167
|
+
all_replies ||= Array.new(@size)
|
|
168
|
+
pipeline = @pipelines[t.thread_variable_get(:node_key)]
|
|
169
|
+
err = t.thread_variable_get(:redirection_needed)
|
|
170
|
+
err.indices.each { |i| err.replies[i] = handle_redirection(err.replies[i], pipeline, i) }
|
|
171
|
+
pipeline.outer_indices.each_with_index { |outer, inner| all_replies[outer] = err.replies[inner] }
|
|
172
|
+
elsif t.thread_variable?(:error)
|
|
173
|
+
errors ||= {}
|
|
174
|
+
errors[t.thread_variable_get(:node_key)] = t.thread_variable_get(:error)
|
|
175
|
+
end
|
|
176
|
+
end
|
|
86
177
|
end
|
|
87
178
|
|
|
88
|
-
|
|
179
|
+
raise ::RedisClient::Cluster::ErrorCollection, errors unless errors.nil?
|
|
89
180
|
|
|
90
|
-
|
|
181
|
+
all_replies
|
|
91
182
|
end
|
|
92
183
|
|
|
93
184
|
private
|
|
94
185
|
|
|
95
|
-
def
|
|
96
|
-
@
|
|
97
|
-
@
|
|
186
|
+
def append_pipeline(node_key)
|
|
187
|
+
@pipelines ||= {}
|
|
188
|
+
@pipelines[node_key] ||= ::RedisClient::Cluster::Pipeline::Extended.new(@command_builder)
|
|
189
|
+
@pipelines[node_key].add_outer_index(@size)
|
|
98
190
|
@size += 1
|
|
191
|
+
@pipelines[node_key]
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def do_pipelining(client, pipeline)
|
|
195
|
+
case client
|
|
196
|
+
when ::RedisClient then send_pipeline(client, pipeline)
|
|
197
|
+
when ::RedisClient::Pooled then client.with { |cli| send_pipeline(cli, pipeline) }
|
|
198
|
+
else raise NotImplementedError, "#{client.class.name}#pipelined for cluster client"
|
|
199
|
+
end
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
def send_pipeline(client, pipeline)
|
|
203
|
+
results = client.send(:ensure_connected, retryable: pipeline._retryable?) do |connection|
|
|
204
|
+
commands = pipeline._commands
|
|
205
|
+
::RedisClient::Middlewares.call_pipelined(commands, client.config) do
|
|
206
|
+
connection.call_pipelined_aware_of_redirection(commands, pipeline._timeouts)
|
|
207
|
+
end
|
|
208
|
+
end
|
|
209
|
+
|
|
210
|
+
pipeline._coerce!(results)
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
def handle_redirection(err, pipeline, inner_index)
|
|
214
|
+
return err unless err.is_a?(::RedisClient::CommandError)
|
|
215
|
+
|
|
216
|
+
if err.message.start_with?('MOVED')
|
|
217
|
+
node = @router.assign_redirection_node(err.message)
|
|
218
|
+
try_redirection(node, pipeline, inner_index)
|
|
219
|
+
elsif err.message.start_with?('ASK')
|
|
220
|
+
node = @router.assign_asking_node(err.message)
|
|
221
|
+
try_asking(node) ? try_redirection(node, pipeline, inner_index) : err
|
|
222
|
+
else
|
|
223
|
+
err
|
|
224
|
+
end
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
def try_redirection(node, pipeline, inner_index)
|
|
228
|
+
redirect_command(node, pipeline, inner_index)
|
|
229
|
+
rescue StandardError => e
|
|
230
|
+
e
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def redirect_command(node, pipeline, inner_index)
|
|
234
|
+
method = pipeline.get_callee_method(inner_index)
|
|
235
|
+
command = pipeline.get_command(inner_index)
|
|
236
|
+
timeout = pipeline.get_timeout(inner_index)
|
|
237
|
+
block = pipeline.get_block(inner_index)
|
|
238
|
+
args = timeout.nil? ? [] : [timeout]
|
|
239
|
+
|
|
240
|
+
if block.nil?
|
|
241
|
+
@router.try_send(node, method, command, args)
|
|
242
|
+
else
|
|
243
|
+
@router.try_send(node, method, command, args, &block)
|
|
244
|
+
end
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
def try_asking(node)
|
|
248
|
+
node.call('ASKING') == 'OK'
|
|
249
|
+
rescue StandardError
|
|
250
|
+
false
|
|
99
251
|
end
|
|
100
252
|
end
|
|
101
253
|
end
|
|
@@ -25,7 +25,7 @@ class RedisClient
|
|
|
25
25
|
@command_builder = @config.command_builder
|
|
26
26
|
end
|
|
27
27
|
|
|
28
|
-
def send_command(method, command, *args, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/
|
|
28
|
+
def send_command(method, command, *args, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
29
29
|
cmd = ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_command(command)
|
|
30
30
|
case cmd
|
|
31
31
|
when 'acl', 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save'
|
|
@@ -65,7 +65,7 @@ class RedisClient
|
|
|
65
65
|
|
|
66
66
|
# @see https://redis.io/topics/cluster-spec#redirection-and-resharding
|
|
67
67
|
# Redirection and resharding
|
|
68
|
-
def try_send(node, method, command, args, retry_count: 3, &block) # rubocop:disable Metrics/
|
|
68
|
+
def try_send(node, method, command, args, retry_count: 3, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
69
69
|
if args.empty?
|
|
70
70
|
# prevent memory allocation for variable-length args
|
|
71
71
|
node.send(method, command, &block)
|
|
@@ -100,7 +100,7 @@ class RedisClient
|
|
|
100
100
|
retry
|
|
101
101
|
end
|
|
102
102
|
|
|
103
|
-
def try_delegate(node, method, *args, retry_count: 3, **kwargs, &block) # rubocop:disable Metrics/
|
|
103
|
+
def try_delegate(node, method, *args, retry_count: 3, **kwargs, &block) # rubocop:disable Metrics/AbcSize
|
|
104
104
|
node.send(method, *args, **kwargs, &block)
|
|
105
105
|
rescue ::RedisClient::CommandError => e
|
|
106
106
|
raise if retry_count <= 0
|
|
@@ -129,7 +129,7 @@ class RedisClient
|
|
|
129
129
|
retry
|
|
130
130
|
end
|
|
131
131
|
|
|
132
|
-
def scan(*command, seed: nil, **kwargs) # rubocop:disable Metrics/
|
|
132
|
+
def scan(*command, seed: nil, **kwargs) # rubocop:disable Metrics/AbcSize
|
|
133
133
|
command = @command_builder.generate(command, kwargs)
|
|
134
134
|
|
|
135
135
|
command[1] = ZERO_CURSOR_FOR_SCAN if command.size == 1
|
|
@@ -172,7 +172,7 @@ class RedisClient
|
|
|
172
172
|
def find_node(node_key, retry_count: 3)
|
|
173
173
|
@node.find_by(node_key)
|
|
174
174
|
rescue ::RedisClient::Cluster::Node::ReloadNeeded
|
|
175
|
-
raise ::
|
|
175
|
+
raise ::RedisClient::Cluster::NodeMightBeDown if retry_count <= 0
|
|
176
176
|
|
|
177
177
|
update_cluster_info!
|
|
178
178
|
retry_count -= 1
|
|
@@ -183,6 +183,18 @@ class RedisClient
|
|
|
183
183
|
@command.exists?(name)
|
|
184
184
|
end
|
|
185
185
|
|
|
186
|
+
def assign_redirection_node(err_msg)
|
|
187
|
+
_, slot, node_key = err_msg.split
|
|
188
|
+
slot = slot.to_i
|
|
189
|
+
@node.update_slot(slot, node_key)
|
|
190
|
+
find_node(node_key)
|
|
191
|
+
end
|
|
192
|
+
|
|
193
|
+
def assign_asking_node(err_msg)
|
|
194
|
+
_, _, node_key = err_msg.split
|
|
195
|
+
find_node(node_key)
|
|
196
|
+
end
|
|
197
|
+
|
|
186
198
|
private
|
|
187
199
|
|
|
188
200
|
def send_wait_command(method, command, args, retry_count: 3, &block)
|
|
@@ -258,19 +270,7 @@ class RedisClient
|
|
|
258
270
|
end
|
|
259
271
|
end
|
|
260
272
|
|
|
261
|
-
def
|
|
262
|
-
_, slot, node_key = err_msg.split
|
|
263
|
-
slot = slot.to_i
|
|
264
|
-
@node.update_slot(slot, node_key)
|
|
265
|
-
find_node(node_key)
|
|
266
|
-
end
|
|
267
|
-
|
|
268
|
-
def assign_asking_node(err_msg)
|
|
269
|
-
_, _, node_key = err_msg.split
|
|
270
|
-
find_node(node_key)
|
|
271
|
-
end
|
|
272
|
-
|
|
273
|
-
def fetch_cluster_info(config, pool: nil, **kwargs) # rubocop:disable Metrics/MethodLength
|
|
273
|
+
def fetch_cluster_info(config, pool: nil, **kwargs)
|
|
274
274
|
node_info = ::RedisClient::Cluster::Node.load_info(config.per_node_key, **kwargs)
|
|
275
275
|
node_addrs = node_info.map { |info| ::RedisClient::Cluster::NodeKey.hashify(info[:node_key]) }
|
|
276
276
|
config.update_node(node_addrs)
|
data/lib/redis_client/cluster.rb
CHANGED
|
@@ -81,7 +81,7 @@ class RedisClient
|
|
|
81
81
|
seed = @config.use_replica? && @config.replica_affinity == :random ? nil : Random.new_seed
|
|
82
82
|
pipeline = ::RedisClient::Cluster::Pipeline.new(@router, @command_builder, seed: seed)
|
|
83
83
|
yield pipeline
|
|
84
|
-
return [] if pipeline.empty?
|
|
84
|
+
return [] if pipeline.empty?
|
|
85
85
|
|
|
86
86
|
pipeline.execute
|
|
87
87
|
end
|
|
@@ -114,7 +114,7 @@ class RedisClient
|
|
|
114
114
|
end
|
|
115
115
|
end
|
|
116
116
|
|
|
117
|
-
def parse_node_url(addr) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
117
|
+
def parse_node_url(addr) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
|
118
118
|
return if addr.empty?
|
|
119
119
|
|
|
120
120
|
uri = URI(addr)
|
metadata
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: redis-cluster-client
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.3.
|
|
4
|
+
version: 0.3.6
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Taishi Kasuga
|
|
8
8
|
autorequire:
|
|
9
9
|
bindir: bin
|
|
10
10
|
cert_chain: []
|
|
11
|
-
date: 2022-09-
|
|
11
|
+
date: 2022-09-22 00:00:00.000000000 Z
|
|
12
12
|
dependencies:
|
|
13
13
|
- !ruby/object:Gem::Dependency
|
|
14
14
|
name: redis-client
|