redis-cluster-client 0.0.7 → 0.0.8
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: d13300ba8c7f294d3e4c86932d13775ad22060c172900c01d521b1ed8c2278d6
|
4
|
+
data.tar.gz: d5b457978bb312a718ba57b48b56fac1ceb07a463889835c8190794007ecbf7d
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 37fb06428eed5a9cca5b42d27c034f057dd7d6f0c5d0c17e4b42c60a8fa3711127c5ed81f06e0565331f64aa762ccc30ee6674fb541e56a8b322ee50ab7a75ce
|
7
|
+
data.tar.gz: 06d46f93c8c7b7116378d2ef9e4380ccce3aac74c071b709cbacd677ec0b302302b52160c04b2c3523771f69446b96d84e8b4f729765073386c5e5f1f3f27f5b
|
@@ -68,12 +68,14 @@ class RedisClient
|
|
68
68
|
@details.fetch(name).fetch(key)
|
69
69
|
end
|
70
70
|
|
71
|
-
def determine_first_key_position(command) # rubocop:disable Metrics/CyclomaticComplexity
|
71
|
+
def determine_first_key_position(command) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/MethodLength
|
72
72
|
case command&.flatten&.first.to_s.downcase
|
73
|
-
when 'eval', 'evalsha', '
|
73
|
+
when 'eval', 'evalsha', 'zinterstore', 'zunionstore' then 3
|
74
74
|
when 'object' then 2
|
75
75
|
when 'memory'
|
76
76
|
command[1].to_s.casecmp('usage').zero? ? 2 : 0
|
77
|
+
when 'migrate'
|
78
|
+
command[3] == '""' ? determine_optional_key_position(command, 'keys') : 3
|
77
79
|
when 'xread', 'xreadgroup'
|
78
80
|
determine_optional_key_position(command, 'streams')
|
79
81
|
else
|
@@ -51,5 +51,15 @@ class RedisClient
|
|
51
51
|
super("Cluster client doesn't know which node the #{command} command should be sent to.")
|
52
52
|
end
|
53
53
|
end
|
54
|
+
|
55
|
+
class NodeMightBeDown < ::RedisClient::Error
|
56
|
+
def initialize(_ = '')
|
57
|
+
super(
|
58
|
+
'The client is trying to fetch the latest cluster state '\
|
59
|
+
'because a subset of nodes might be down. '\
|
60
|
+
'It might continue to raise errors for a while.'
|
61
|
+
)
|
62
|
+
end
|
63
|
+
end
|
54
64
|
end
|
55
65
|
end
|
@@ -12,6 +12,7 @@ class RedisClient
|
|
12
12
|
SLOT_SIZE = 16_384
|
13
13
|
MIN_SLOT = 0
|
14
14
|
MAX_SLOT = SLOT_SIZE - 1
|
15
|
+
MAX_STARTUP_SAMPLE = 37
|
15
16
|
IGNORE_GENERIC_CONFIG_KEYS = %i[url host port path].freeze
|
16
17
|
|
17
18
|
ReloadNeeded = Class.new(::RedisClient::Error)
|
@@ -32,19 +33,33 @@ class RedisClient
|
|
32
33
|
end
|
33
34
|
|
34
35
|
class << self
|
35
|
-
def load_info(options, **kwargs)
|
36
|
-
|
37
|
-
|
38
|
-
errors =
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
36
|
+
def load_info(options, **kwargs) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
37
|
+
startup_size = options.size > MAX_STARTUP_SAMPLE ? MAX_STARTUP_SAMPLE : options.size
|
38
|
+
node_info_list = Array.new(startup_size)
|
39
|
+
errors = Array.new(startup_size)
|
40
|
+
startup_options = options.to_a.sample(MAX_STARTUP_SAMPLE).to_h
|
41
|
+
startup_nodes = ::RedisClient::Cluster::Node.new(startup_options, **kwargs)
|
42
|
+
threads = startup_nodes.each_with_index.map do |raw_client, idx|
|
43
|
+
Thread.new(raw_client, idx) do |cli, i|
|
44
|
+
Thread.pass
|
45
|
+
reply = cli.call('CLUSTER', 'NODES')
|
46
|
+
node_info_list[i] = parse_node_info(reply)
|
47
|
+
rescue StandardError => e
|
48
|
+
errors[i] = e
|
49
|
+
ensure
|
50
|
+
cli&.close
|
51
|
+
end
|
52
|
+
end
|
53
|
+
threads.each(&:join)
|
54
|
+
raise ::RedisClient::Cluster::InitialSetupError, errors if node_info_list.all?(&:nil?)
|
55
|
+
|
56
|
+
grouped = node_info_list.compact.group_by do |rows|
|
57
|
+
rows.sort_by { |row| row[:id] }
|
58
|
+
.map { |r| "#{r[:id]}#{r[:node_key]}#{r[:role]}#{r[:primary_id]}#{r[:config_epoch]}" }
|
59
|
+
.join
|
43
60
|
end
|
44
61
|
|
45
|
-
|
46
|
-
ensure
|
47
|
-
startup_nodes&.each(&:close)
|
62
|
+
grouped.max_by { |_, v| v.size }[1].first
|
48
63
|
end
|
49
64
|
|
50
65
|
private
|
@@ -109,32 +124,58 @@ class RedisClient
|
|
109
124
|
end
|
110
125
|
|
111
126
|
def find_by(node_key)
|
127
|
+
raise ReloadNeeded if node_key.nil? || !@clients.key?(node_key)
|
128
|
+
|
112
129
|
@clients.fetch(node_key)
|
113
|
-
rescue KeyError
|
114
|
-
raise ReloadNeeded
|
115
130
|
end
|
116
131
|
|
117
132
|
def call_all(method, *command, **kwargs, &block)
|
118
|
-
try_map
|
133
|
+
results, errors = try_map do |_, client|
|
134
|
+
client.send(method, *command, **kwargs, &block)
|
135
|
+
end
|
136
|
+
|
137
|
+
return results.values if errors.empty?
|
138
|
+
|
139
|
+
raise ::RedisClient::Cluster::ErrorCollection, errors
|
119
140
|
end
|
120
141
|
|
121
142
|
def call_primaries(method, *command, **kwargs, &block)
|
122
|
-
try_map do |node_key, client|
|
143
|
+
results, errors = try_map do |node_key, client|
|
123
144
|
next if replica?(node_key)
|
124
145
|
|
125
146
|
client.send(method, *command, **kwargs, &block)
|
126
|
-
end
|
147
|
+
end
|
148
|
+
|
149
|
+
return results.values if errors.empty?
|
150
|
+
|
151
|
+
raise ::RedisClient::Cluster::ErrorCollection, errors
|
127
152
|
end
|
128
153
|
|
129
154
|
def call_replicas(method, *command, **kwargs, &block)
|
130
155
|
return call_primaries(method, *command, **kwargs, &block) if replica_disabled?
|
131
156
|
|
132
157
|
replica_node_keys = @replications.values.map(&:sample)
|
133
|
-
try_map do |node_key, client|
|
158
|
+
results, errors = try_map do |node_key, client|
|
134
159
|
next if primary?(node_key) || !replica_node_keys.include?(node_key)
|
135
160
|
|
136
161
|
client.send(method, *command, **kwargs, &block)
|
137
|
-
end
|
162
|
+
end
|
163
|
+
|
164
|
+
return results.values if errors.empty?
|
165
|
+
|
166
|
+
raise ::RedisClient::Cluster::ErrorCollection, errors
|
167
|
+
end
|
168
|
+
|
169
|
+
def send_ping(method, *command, **kwargs, &block)
|
170
|
+
results, errors = try_map do |_, client|
|
171
|
+
client.send(method, *command, **kwargs, &block)
|
172
|
+
end
|
173
|
+
|
174
|
+
return results.values if errors.empty?
|
175
|
+
|
176
|
+
raise ReloadNeeded if errors.values.any?(::RedisClient::ConnectionError)
|
177
|
+
|
178
|
+
raise ::RedisClient::Cluster::ErrorCollection, errors
|
138
179
|
end
|
139
180
|
|
140
181
|
def scale_reading_clients
|
@@ -144,14 +185,9 @@ class RedisClient
|
|
144
185
|
end
|
145
186
|
end
|
146
187
|
|
147
|
-
def slot_exists?(slot)
|
148
|
-
slot = Integer(slot)
|
149
|
-
return false if slot < MIN_SLOT || slot > MAX_SLOT
|
150
|
-
|
151
|
-
!@slots[slot].nil?
|
152
|
-
end
|
153
|
-
|
154
188
|
def find_node_key_of_primary(slot)
|
189
|
+
return if slot.nil?
|
190
|
+
|
155
191
|
slot = Integer(slot)
|
156
192
|
return if slot < MIN_SLOT || slot > MAX_SLOT
|
157
193
|
|
@@ -159,6 +195,8 @@ class RedisClient
|
|
159
195
|
end
|
160
196
|
|
161
197
|
def find_node_key_of_replica(slot)
|
198
|
+
return if slot.nil?
|
199
|
+
|
162
200
|
slot = Integer(slot)
|
163
201
|
return if slot < MIN_SLOT || slot > MAX_SLOT
|
164
202
|
|
@@ -241,9 +279,7 @@ class RedisClient
|
|
241
279
|
end
|
242
280
|
|
243
281
|
threads.each(&:join)
|
244
|
-
|
245
|
-
|
246
|
-
raise ::RedisClient::Cluster::ErrorCollection, errors
|
282
|
+
[results, errors]
|
247
283
|
end
|
248
284
|
end
|
249
285
|
end
|
data/lib/redis_client/cluster.rb
CHANGED
@@ -170,8 +170,10 @@ class RedisClient
|
|
170
170
|
end
|
171
171
|
|
172
172
|
def close
|
173
|
-
@node.
|
173
|
+
@node.call_all(:close)
|
174
174
|
nil
|
175
|
+
rescue StandardError
|
176
|
+
# ignore
|
175
177
|
end
|
176
178
|
|
177
179
|
private
|
@@ -187,10 +189,11 @@ class RedisClient
|
|
187
189
|
def send_command(method, *command, **kwargs, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
|
188
190
|
cmd = command.first.to_s.downcase
|
189
191
|
case cmd
|
190
|
-
when 'acl', 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save'
|
192
|
+
when 'acl', 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save'
|
191
193
|
@node.call_all(method, *command, **kwargs, &block).first
|
192
194
|
when 'flushall', 'flushdb'
|
193
195
|
@node.call_primaries(method, *command, **kwargs, &block).first
|
196
|
+
when 'ping' then @node.send_ping(method, *command, **kwargs, &block).first
|
194
197
|
when 'wait' then send_wait_command(method, *command, **kwargs, &block)
|
195
198
|
when 'keys' then @node.call_replicas(method, *command, **kwargs, &block).flatten.sort
|
196
199
|
when 'dbsize' then @node.call_replicas(method, *command, **kwargs, &block).sum
|
@@ -211,15 +214,18 @@ class RedisClient
|
|
211
214
|
node = assign_node(*command)
|
212
215
|
try_send(node, method, *command, **kwargs, &block)
|
213
216
|
end
|
214
|
-
rescue RedisClient::Cluster::
|
215
|
-
update_cluster_info!
|
216
|
-
raise
|
217
|
+
rescue RedisClient::Cluster::Node::ReloadNeeded
|
218
|
+
update_cluster_info!
|
219
|
+
raise ::RedisClient::Cluster::NodeMightBeDown
|
217
220
|
end
|
218
221
|
|
219
222
|
def send_wait_command(method, *command, retry_count: 3, **kwargs, &block)
|
220
223
|
@node.call_primaries(method, *command, **kwargs, &block).sum
|
221
224
|
rescue RedisClient::Cluster::ErrorCollection => e
|
222
|
-
raise if retry_count <= 0
|
225
|
+
raise if retry_count <= 0
|
226
|
+
raise if e.errors.values.none? do |err|
|
227
|
+
err.message.include?('WAIT cannot be used with replica instances')
|
228
|
+
end
|
223
229
|
|
224
230
|
update_cluster_info!
|
225
231
|
retry_count -= 1
|
@@ -353,44 +359,30 @@ class RedisClient
|
|
353
359
|
find_node(node_key)
|
354
360
|
end
|
355
361
|
|
356
|
-
def find_node_key(*command, primary_only: false)
|
362
|
+
def find_node_key(*command, primary_only: false)
|
357
363
|
key = @command.extract_first_key(command)
|
358
|
-
|
359
|
-
return @node.primary_node_keys.sample if @command.should_send_to_primary?(command) || primary_only
|
360
|
-
|
361
|
-
return @node.replica_node_keys.sample
|
362
|
-
end
|
363
|
-
|
364
|
-
slot = ::RedisClient::Cluster::KeySlotConverter.convert(key)
|
365
|
-
return unless @node.slot_exists?(slot)
|
364
|
+
slot = key.empty? ? nil : ::RedisClient::Cluster::KeySlotConverter.convert(key)
|
366
365
|
|
367
366
|
if @command.should_send_to_primary?(command) || primary_only
|
368
|
-
@node.find_node_key_of_primary(slot)
|
367
|
+
@node.find_node_key_of_primary(slot) || @node.primary_node_keys.sample
|
369
368
|
else
|
370
|
-
@node.find_node_key_of_replica(slot)
|
369
|
+
@node.find_node_key_of_replica(slot) || @node.replica_node_keys.sample
|
371
370
|
end
|
372
371
|
end
|
373
372
|
|
374
373
|
def find_node(node_key, retry_count: 3)
|
375
|
-
return @node.sample if node_key.nil?
|
376
|
-
|
377
374
|
@node.find_by(node_key)
|
378
375
|
rescue ::RedisClient::Cluster::Node::ReloadNeeded
|
379
|
-
raise
|
376
|
+
raise ::RedieClient::Cluster::NodeMightBeDown if retry_count <= 0
|
380
377
|
|
381
|
-
update_cluster_info!
|
378
|
+
update_cluster_info!
|
382
379
|
retry_count -= 1
|
383
380
|
retry
|
384
381
|
end
|
385
382
|
|
386
|
-
def update_cluster_info!
|
383
|
+
def update_cluster_info!
|
387
384
|
@mutex.synchronize do
|
388
|
-
|
389
|
-
host, port = ::RedisClient::Cluster::NodeKey.split(node_key)
|
390
|
-
@config.add_node(host, port)
|
391
|
-
end
|
392
|
-
|
393
|
-
@node.each(&:close)
|
385
|
+
close
|
394
386
|
@node = fetch_cluster_info!(@config, pool: @pool, **@client_kwargs)
|
395
387
|
end
|
396
388
|
end
|
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.0.
|
4
|
+
version: 0.0.8
|
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-06-
|
11
|
+
date: 2022-06-21 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis-client
|