redis-cluster-client 0.3.14 → 0.4.0
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
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 780e5f1653b6633dbd4bb1bdc4f4da174d70176a5287bf6156ad5784f0612cf1
|
4
|
+
data.tar.gz: ca1681357111afdc82bdf32a18985963dba85f89fd49961ac62c56075daa0cc8
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4111e4b1305d955d3eb56df912debd0cfbbd388bae1d841cf1b9274a21d2e121fb511f1630d85f6c503d53ebf514503303b94402d3416517886b93f615584a22
|
7
|
+
data.tar.gz: 1f6e2be52bcd834482d17bebfeead2b8d1346c954d03b61251dd973ab19f27275e53571d73ba6425dceedc34dc07cd5e6dbb98fa46de00281b6d00eeb1c87f08
|
@@ -43,7 +43,7 @@ class RedisClient
|
|
43
43
|
clients.each_slice(::RedisClient::Cluster::Node::MAX_THREADS).each_with_object({}) do |chuncked_clients, acc|
|
44
44
|
threads = chuncked_clients.map do |k, v|
|
45
45
|
Thread.new(k, v) do |node_key, client|
|
46
|
-
Thread.current
|
46
|
+
Thread.current[:node_key] = node_key
|
47
47
|
|
48
48
|
min = DUMMY_LATENCY_NSEC
|
49
49
|
MEASURE_ATTEMPT_COUNT.times do
|
@@ -53,15 +53,15 @@ class RedisClient
|
|
53
53
|
min = duration if duration < min
|
54
54
|
end
|
55
55
|
|
56
|
-
Thread.current
|
56
|
+
Thread.current[:latency] = min
|
57
57
|
rescue StandardError
|
58
|
-
Thread.current
|
58
|
+
Thread.current[:latency] = DUMMY_LATENCY_NSEC
|
59
59
|
end
|
60
60
|
end
|
61
61
|
|
62
62
|
threads.each do |t|
|
63
63
|
t.join
|
64
|
-
acc[t
|
64
|
+
acc[t[:node_key]] = t[:latency]
|
65
65
|
end
|
66
66
|
end
|
67
67
|
end
|
@@ -18,7 +18,6 @@ class RedisClient
|
|
18
18
|
MAX_STARTUP_SAMPLE = 37
|
19
19
|
MAX_THREADS = Integer(ENV.fetch('REDIS_CLIENT_MAX_THREADS', 5))
|
20
20
|
IGNORE_GENERIC_CONFIG_KEYS = %i[url host port path].freeze
|
21
|
-
SLOT_OPTIMIZATION_STRING = '0' * SLOT_SIZE
|
22
21
|
|
23
22
|
ReloadNeeded = Class.new(::RedisClient::Error)
|
24
23
|
|
@@ -37,27 +36,36 @@ class RedisClient
|
|
37
36
|
end
|
38
37
|
end
|
39
38
|
|
40
|
-
|
39
|
+
class CharArray
|
40
|
+
BASE = ''
|
41
|
+
PADDING = '0'
|
42
|
+
|
43
|
+
def initialize(size, elements)
|
44
|
+
@elements = elements
|
45
|
+
@string = String.new(BASE, encoding: Encoding::BINARY, capacity: size)
|
46
|
+
size.times { @string << PADDING }
|
47
|
+
end
|
48
|
+
|
41
49
|
def [](index)
|
42
50
|
raise IndexError if index < 0
|
43
|
-
return if index >= string.bytesize
|
51
|
+
return if index >= @string.bytesize
|
44
52
|
|
45
|
-
elements[string.getbyte(index)]
|
53
|
+
@elements[@string.getbyte(index)]
|
46
54
|
end
|
47
55
|
|
48
56
|
def []=(index, element)
|
49
57
|
raise IndexError if index < 0
|
50
|
-
return if index >= string.bytesize
|
58
|
+
return if index >= @string.bytesize
|
51
59
|
|
52
|
-
pos = elements.find_index(element) # O(N)
|
60
|
+
pos = @elements.find_index(element) # O(N)
|
53
61
|
if pos.nil?
|
54
|
-
raise(RangeError, 'full of elements') if elements.size >= 256
|
62
|
+
raise(RangeError, 'full of elements') if @elements.size >= 256
|
55
63
|
|
56
|
-
pos = elements.size
|
57
|
-
elements << element
|
64
|
+
pos = @elements.size
|
65
|
+
@elements << element
|
58
66
|
end
|
59
67
|
|
60
|
-
string.setbyte(index, pos)
|
68
|
+
@string.setbyte(index, pos)
|
61
69
|
end
|
62
70
|
end
|
63
71
|
|
@@ -85,11 +93,11 @@ class RedisClient
|
|
85
93
|
startup_nodes.each_slice(MAX_THREADS).with_index do |chuncked_startup_nodes, chuncked_idx|
|
86
94
|
threads = chuncked_startup_nodes.each_with_index.map do |raw_client, idx|
|
87
95
|
Thread.new(raw_client, (MAX_THREADS * chuncked_idx) + idx) do |cli, i|
|
88
|
-
Thread.current
|
96
|
+
Thread.current[:index] = i
|
89
97
|
reply = cli.call('CLUSTER', 'NODES')
|
90
|
-
Thread.current
|
98
|
+
Thread.current[:info] = parse_cluster_node_reply(reply)
|
91
99
|
rescue StandardError => e
|
92
|
-
Thread.current
|
100
|
+
Thread.current[:error] = e
|
93
101
|
ensure
|
94
102
|
cli&.close
|
95
103
|
end
|
@@ -97,12 +105,12 @@ class RedisClient
|
|
97
105
|
|
98
106
|
threads.each do |t|
|
99
107
|
t.join
|
100
|
-
if t.
|
108
|
+
if t.key?(:info)
|
101
109
|
node_info_list ||= Array.new(startup_size)
|
102
|
-
node_info_list[t
|
103
|
-
elsif t.
|
110
|
+
node_info_list[t[:index]] = t[:info]
|
111
|
+
elsif t.key?(:error)
|
104
112
|
errors ||= Array.new(startup_size)
|
105
|
-
errors[t
|
113
|
+
errors[t[:index]] = t[:error]
|
106
114
|
end
|
107
115
|
end
|
108
116
|
end
|
@@ -268,10 +276,8 @@ class RedisClient
|
|
268
276
|
def make_array_for_slot_node_mappings(node_info_list)
|
269
277
|
return Array.new(SLOT_SIZE) if node_info_list.count(&:primary?) > 256
|
270
278
|
|
271
|
-
|
272
|
-
|
273
|
-
elements: node_info_list.select(&:primary?).map(&:node_key)
|
274
|
-
)
|
279
|
+
primary_node_keys = node_info_list.select(&:primary?).map(&:node_key)
|
280
|
+
::RedisClient::Cluster::Node::CharArray.new(SLOT_SIZE, primary_node_keys)
|
275
281
|
end
|
276
282
|
|
277
283
|
def build_replication_mappings(node_info_list) # rubocop:disable Metrics/AbcSize
|
@@ -285,7 +291,7 @@ class RedisClient
|
|
285
291
|
|
286
292
|
def call_multiple_nodes(clients, method, command, args, &block)
|
287
293
|
results, errors = try_map(clients) do |_, client|
|
288
|
-
client.
|
294
|
+
client.public_send(method, *args, command, &block)
|
289
295
|
end
|
290
296
|
|
291
297
|
[results&.values, errors]
|
@@ -303,22 +309,22 @@ class RedisClient
|
|
303
309
|
clients.each_slice(MAX_THREADS) do |chuncked_clients|
|
304
310
|
threads = chuncked_clients.map do |k, v|
|
305
311
|
Thread.new(k, v) do |node_key, client|
|
306
|
-
Thread.current
|
312
|
+
Thread.current[:node_key] = node_key
|
307
313
|
reply = yield(node_key, client)
|
308
|
-
Thread.current
|
314
|
+
Thread.current[:result] = reply
|
309
315
|
rescue StandardError => e
|
310
|
-
Thread.current
|
316
|
+
Thread.current[:error] = e
|
311
317
|
end
|
312
318
|
end
|
313
319
|
|
314
320
|
threads.each do |t|
|
315
321
|
t.join
|
316
|
-
if t.
|
322
|
+
if t.key?(:result)
|
317
323
|
results ||= {}
|
318
|
-
results[t
|
319
|
-
elsif t.
|
324
|
+
results[t[:node_key]] = t[:result]
|
325
|
+
elsif t.key?(:error)
|
320
326
|
errors ||= {}
|
321
|
-
errors[t
|
327
|
+
errors[t[:node_key]] = t[:error]
|
322
328
|
end
|
323
329
|
end
|
324
330
|
end
|
@@ -150,34 +150,34 @@ class RedisClient
|
|
150
150
|
@pipelines&.each_slice(MAX_THREADS) do |chuncked_pipelines|
|
151
151
|
threads = chuncked_pipelines.map do |node_key, pipeline|
|
152
152
|
Thread.new(node_key, pipeline) do |nk, pl|
|
153
|
-
Thread.current
|
153
|
+
Thread.current[:node_key] = nk
|
154
154
|
replies = do_pipelining(@router.find_node(nk), pl)
|
155
155
|
raise ReplySizeError, "commands: #{pl._size}, replies: #{replies.size}" if pl._size != replies.size
|
156
156
|
|
157
|
-
Thread.current
|
157
|
+
Thread.current[:replies] = replies
|
158
158
|
rescue ::RedisClient::Cluster::Pipeline::RedirectionNeeded => e
|
159
|
-
Thread.current
|
159
|
+
Thread.current[:redirection_needed] = e
|
160
160
|
rescue StandardError => e
|
161
|
-
Thread.current
|
161
|
+
Thread.current[:error] = e
|
162
162
|
end
|
163
163
|
end
|
164
164
|
|
165
165
|
threads.each(&:join)
|
166
166
|
threads.each do |t|
|
167
|
-
if t.
|
167
|
+
if t.key?(:replies)
|
168
168
|
all_replies ||= Array.new(@size)
|
169
|
-
@pipelines[t
|
169
|
+
@pipelines[t[:node_key]]
|
170
170
|
.outer_indices
|
171
|
-
.each_with_index { |outer, inner| all_replies[outer] = t
|
172
|
-
elsif t.
|
171
|
+
.each_with_index { |outer, inner| all_replies[outer] = t[:replies][inner] }
|
172
|
+
elsif t.key?(:redirection_needed)
|
173
173
|
all_replies ||= Array.new(@size)
|
174
|
-
pipeline = @pipelines[t
|
175
|
-
err = t
|
174
|
+
pipeline = @pipelines[t[:node_key]]
|
175
|
+
err = t[:redirection_needed]
|
176
176
|
err.indices.each { |i| err.replies[i] = handle_redirection(err.replies[i], pipeline, i) }
|
177
177
|
pipeline.outer_indices.each_with_index { |outer, inner| all_replies[outer] = err.replies[inner] }
|
178
|
-
elsif t.
|
178
|
+
elsif t.key?(:error)
|
179
179
|
errors ||= {}
|
180
|
-
errors[t
|
180
|
+
errors[t[:node_key]] = t[:error]
|
181
181
|
end
|
182
182
|
end
|
183
183
|
end
|
@@ -1,6 +1,7 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'redis_client'
|
4
|
+
require 'redis_client/circuit_breaker'
|
4
5
|
require 'redis_client/cluster/command'
|
5
6
|
require 'redis_client/cluster/errors'
|
6
7
|
require 'redis_client/cluster/key_slot_converter'
|
@@ -54,13 +55,18 @@ class RedisClient
|
|
54
55
|
node = assign_node(command)
|
55
56
|
try_send(node, method, command, args, &block)
|
56
57
|
end
|
58
|
+
rescue ::RedisClient::CircuitBreaker::OpenCircuitError
|
59
|
+
raise
|
57
60
|
rescue ::RedisClient::Cluster::Node::ReloadNeeded
|
58
61
|
update_cluster_info!
|
59
62
|
raise ::RedisClient::Cluster::NodeMightBeDown
|
60
63
|
rescue ::RedisClient::Cluster::ErrorCollection => e
|
64
|
+
raise if e.errors.any?(::RedisClient::CircuitBreaker::OpenCircuitError)
|
65
|
+
|
61
66
|
update_cluster_info! if e.errors.values.any? do |err|
|
62
67
|
err.message.start_with?('CLUSTERDOWN Hash slot not served')
|
63
68
|
end
|
69
|
+
|
64
70
|
raise
|
65
71
|
end
|
66
72
|
|
@@ -68,10 +74,12 @@ class RedisClient
|
|
68
74
|
def try_send(node, method, command, args, retry_count: 3, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
69
75
|
if args.empty?
|
70
76
|
# prevent memory allocation for variable-length args
|
71
|
-
node.
|
77
|
+
node.public_send(method, command, &block)
|
72
78
|
else
|
73
|
-
node.
|
79
|
+
node.public_send(method, *args, command, &block)
|
74
80
|
end
|
81
|
+
rescue ::RedisClient::CircuitBreaker::OpenCircuitError
|
82
|
+
raise
|
75
83
|
rescue ::RedisClient::CommandError => e
|
76
84
|
raise if retry_count <= 0
|
77
85
|
|
@@ -101,7 +109,9 @@ class RedisClient
|
|
101
109
|
end
|
102
110
|
|
103
111
|
def try_delegate(node, method, *args, retry_count: 3, **kwargs, &block) # rubocop:disable Metrics/AbcSize
|
104
|
-
node.
|
112
|
+
node.public_send(method, *args, **kwargs, &block)
|
113
|
+
rescue ::RedisClient::CircuitBreaker::OpenCircuitError
|
114
|
+
raise
|
105
115
|
rescue ::RedisClient::CommandError => e
|
106
116
|
raise if retry_count <= 0
|
107
117
|
|
@@ -197,9 +207,10 @@ class RedisClient
|
|
197
207
|
|
198
208
|
private
|
199
209
|
|
200
|
-
def send_wait_command(method, command, args, retry_count: 3, &block)
|
210
|
+
def send_wait_command(method, command, args, retry_count: 3, &block) # rubocop:disable Metrics/AbcSize
|
201
211
|
@node.call_primaries(method, command, args, &block).select { |r| r.is_a?(Integer) }.sum
|
202
212
|
rescue ::RedisClient::Cluster::ErrorCollection => e
|
213
|
+
raise if e.errors.any?(::RedisClient::CircuitBreaker::OpenCircuitError)
|
203
214
|
raise if retry_count <= 0
|
204
215
|
raise if e.errors.values.none? do |err|
|
205
216
|
err.message.include?('WAIT cannot be used with replica instances')
|
@@ -214,7 +225,7 @@ class RedisClient
|
|
214
225
|
case ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_subcommand(command)
|
215
226
|
when 'resetstat', 'rewrite', 'set'
|
216
227
|
@node.call_all(method, command, args, &block).first
|
217
|
-
else assign_node(command).
|
228
|
+
else assign_node(command).public_send(method, *args, command, &block)
|
218
229
|
end
|
219
230
|
end
|
220
231
|
|
@@ -222,7 +233,7 @@ class RedisClient
|
|
222
233
|
case ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_subcommand(command)
|
223
234
|
when 'stats' then @node.call_all(method, command, args, &block)
|
224
235
|
when 'purge' then @node.call_all(method, command, args, &block).first
|
225
|
-
else assign_node(command).
|
236
|
+
else assign_node(command).public_send(method, *args, command, &block)
|
226
237
|
end
|
227
238
|
end
|
228
239
|
|
@@ -231,7 +242,7 @@ class RedisClient
|
|
231
242
|
when 'list' then @node.call_all(method, command, args, &block).flatten
|
232
243
|
when 'pause', 'reply', 'setname'
|
233
244
|
@node.call_all(method, command, args, &block).first
|
234
|
-
else assign_node(command).
|
245
|
+
else assign_node(command).public_send(method, *args, command, &block)
|
235
246
|
end
|
236
247
|
end
|
237
248
|
|
@@ -244,8 +255,8 @@ class RedisClient
|
|
244
255
|
when 'getkeysinslot'
|
245
256
|
raise ArgumentError, command.join(' ') if command.size != 4
|
246
257
|
|
247
|
-
find_node(@node.find_node_key_of_replica(command[2])).
|
248
|
-
else assign_node(command).
|
258
|
+
find_node(@node.find_node_key_of_replica(command[2])).public_send(method, *args, command, &block)
|
259
|
+
else assign_node(command).public_send(method, *args, command, &block)
|
249
260
|
end
|
250
261
|
end
|
251
262
|
|
@@ -255,7 +266,9 @@ class RedisClient
|
|
255
266
|
@node.call_all(method, command, args, &block).first
|
256
267
|
when 'flush', 'load'
|
257
268
|
@node.call_primaries(method, command, args, &block).first
|
258
|
-
|
269
|
+
when 'exists'
|
270
|
+
@node.call_all(method, command, args, &block).transpose.map { |arr| arr.any?(&:zero?) ? 0 : 1 }
|
271
|
+
else assign_node(command).public_send(method, *args, command, &block)
|
259
272
|
end
|
260
273
|
end
|
261
274
|
|
@@ -266,7 +279,7 @@ class RedisClient
|
|
266
279
|
@node.call_all(method, command, args, &block).reject(&:empty?).map { |e| Hash[*e] }
|
267
280
|
.reduce({}) { |a, e| a.merge(e) { |_, v1, v2| v1 + v2 } }
|
268
281
|
when 'numpat' then @node.call_all(method, command, args, &block).select { |e| e.is_a?(Integer) }.sum
|
269
|
-
else assign_node(command).
|
282
|
+
else assign_node(command).public_send(method, *args, command, &block)
|
270
283
|
end
|
271
284
|
end
|
272
285
|
|
@@ -98,7 +98,7 @@ class RedisClient
|
|
98
98
|
|
99
99
|
def build_node_configs(addrs)
|
100
100
|
configs = Array[addrs].flatten.filter_map { |addr| parse_node_addr(addr) }
|
101
|
-
raise InvalidClientConfigError, '`nodes` option is empty' if configs.
|
101
|
+
raise InvalidClientConfigError, '`nodes` option is empty' if configs.empty?
|
102
102
|
|
103
103
|
configs
|
104
104
|
end
|
@@ -150,7 +150,7 @@ class RedisClient
|
|
150
150
|
end
|
151
151
|
|
152
152
|
def merge_generic_config(client_config, node_configs)
|
153
|
-
return client_config if node_configs.
|
153
|
+
return client_config if node_configs.empty?
|
154
154
|
|
155
155
|
cfg = node_configs.first
|
156
156
|
MERGE_CONFIG_KEYS.each { |k| client_config[k] = cfg[k] if cfg.key?(k) }
|
metadata
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redis-cluster-client
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.4.0
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Taishi Kasuga
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2023-01-16 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis-client
|
15
15
|
requirement: !ruby/object:Gem::Requirement
|
16
16
|
requirements:
|
17
|
-
- - "
|
17
|
+
- - "~>"
|
18
18
|
- !ruby/object:Gem::Version
|
19
|
-
version: '0.
|
19
|
+
version: '0.12'
|
20
20
|
type: :runtime
|
21
21
|
prerelease: false
|
22
22
|
version_requirements: !ruby/object:Gem::Requirement
|
23
23
|
requirements:
|
24
|
-
- - "
|
24
|
+
- - "~>"
|
25
25
|
- !ruby/object:Gem::Version
|
26
|
-
version: '0.
|
26
|
+
version: '0.12'
|
27
27
|
description:
|
28
28
|
email:
|
29
29
|
- proxy0721@gmail.com
|
@@ -69,7 +69,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
69
69
|
- !ruby/object:Gem::Version
|
70
70
|
version: '0'
|
71
71
|
requirements: []
|
72
|
-
rubygems_version: 3.3.
|
72
|
+
rubygems_version: 3.3.23
|
73
73
|
signing_key:
|
74
74
|
specification_version: 4
|
75
75
|
summary: A Redis cluster client for Ruby
|