redis-cluster-client 0.0.3 → 0.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/lib/redis_client/cluster/node.rb +32 -17
- data/lib/redis_client/cluster.rb +60 -33
- data/lib/redis_client/cluster_config.rb +5 -2
- 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: fde84481c2ca9680670f252a002391a34ae793148a21a8e02086d199412a53ab
|
4
|
+
data.tar.gz: 5af188432f9ae5e76c13d0dd174c25cde4b98d1de49efeb97e2a809bd111a9a6
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: f26dc7bb447ee70742efc90344758ad3058cd0d1506ad8576637b7e9ad296a806d7e15e3b824cead0fb2313140dea00f6f579a15803399c16257ac347c45085f
|
7
|
+
data.tar.gz: 06f43e223a53d9ada0803f7292bc1ca58505fb3c341fd7643eb6cf0eff73bebe4ecd2022b0a1b59af81dda879a9d8a9b078107181ff45037773382dada321597
|
@@ -79,6 +79,7 @@ class RedisClient
|
|
79
79
|
@slots = build_slot_node_mappings(node_info)
|
80
80
|
@replications = build_replication_mappings(node_info)
|
81
81
|
@clients = build_clients(options, pool: pool, **kwargs)
|
82
|
+
@mutex = Mutex.new
|
82
83
|
end
|
83
84
|
|
84
85
|
def inspect
|
@@ -101,6 +102,12 @@ class RedisClient
|
|
101
102
|
@clients.filter_map { |k, _| primary?(k) ? k : nil }.sort
|
102
103
|
end
|
103
104
|
|
105
|
+
def replica_node_keys
|
106
|
+
return primary_node_keys if replica_disabled?
|
107
|
+
|
108
|
+
@clients.filter_map { |k, _| replica?(k) ? k : nil }.sort
|
109
|
+
end
|
110
|
+
|
104
111
|
def find_by(node_key)
|
105
112
|
@clients.fetch(node_key)
|
106
113
|
rescue KeyError
|
@@ -122,19 +129,17 @@ class RedisClient
|
|
122
129
|
def call_replica(method, *command, **kwargs, &block)
|
123
130
|
return call_primary(method, *command, **kwargs, &block) if replica_disabled?
|
124
131
|
|
132
|
+
replica_node_keys = @replications.values.map(&:sample)
|
125
133
|
try_map do |node_key, client|
|
126
|
-
next if primary?(node_key)
|
134
|
+
next if primary?(node_key) || !replica_node_keys.include?(node_key)
|
127
135
|
|
128
136
|
client.send(method, *command, **kwargs, &block)
|
129
137
|
end.values
|
130
138
|
end
|
131
139
|
|
132
140
|
def scale_reading_clients
|
133
|
-
|
134
|
-
|
135
|
-
end
|
136
|
-
|
137
|
-
clients.values.sort_by do |client|
|
141
|
+
keys = replica_disabled? ? @replications.keys : @replications.values.map(&:first)
|
142
|
+
@clients.select { |k, _| keys.include?(k) }.values.sort_by do |client|
|
138
143
|
::RedisClient::Cluster::NodeKey.build_from_host_port(client.config.host, client.config.port)
|
139
144
|
end
|
140
145
|
end
|
@@ -163,7 +168,13 @@ class RedisClient
|
|
163
168
|
end
|
164
169
|
|
165
170
|
def update_slot(slot, node_key)
|
166
|
-
@slots[slot] = node_key
|
171
|
+
@mutex.synchronize { @slots[slot] = node_key }
|
172
|
+
end
|
173
|
+
|
174
|
+
def replicated?(primary_node_key, replica_node_key)
|
175
|
+
return false if @replications.nil? || @replications.size.zero?
|
176
|
+
|
177
|
+
@replications.fetch(primary_node_key).include?(replica_node_key)
|
167
178
|
end
|
168
179
|
|
169
180
|
private
|
@@ -177,7 +188,9 @@ class RedisClient
|
|
177
188
|
end
|
178
189
|
|
179
190
|
def replica?(node_key)
|
180
|
-
|
191
|
+
return false if @replications.nil? || @replications.size.zero?
|
192
|
+
|
193
|
+
!@replications.key?(node_key)
|
181
194
|
end
|
182
195
|
|
183
196
|
def build_slot_node_mappings(node_info)
|
@@ -191,12 +204,12 @@ class RedisClient
|
|
191
204
|
slots
|
192
205
|
end
|
193
206
|
|
194
|
-
def build_replication_mappings(node_info)
|
207
|
+
def build_replication_mappings(node_info) # rubocop:disable Metrics/AbcSize
|
195
208
|
dict = node_info.to_h { |info| [info[:id], info] }
|
196
209
|
node_info.each_with_object(Hash.new { |h, k| h[k] = [] }) do |info, acc|
|
197
210
|
primary_info = dict[info[:primary_id]]
|
198
211
|
acc[primary_info[:node_key]] << info[:node_key] unless primary_info.nil?
|
199
|
-
acc[info[:node_key]]
|
212
|
+
acc[info[:node_key]] if info[:role] == 'master' # for the primary which have no replicas
|
200
213
|
end
|
201
214
|
end
|
202
215
|
|
@@ -217,15 +230,17 @@ class RedisClient
|
|
217
230
|
def try_map # rubocop:disable Metrics/MethodLength
|
218
231
|
errors = {}
|
219
232
|
results = {}
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
233
|
+
threads = @clients.map do |k, v|
|
234
|
+
Thread.new(k, v) do |node_key, client|
|
235
|
+
Thread.pass
|
236
|
+
reply = yield(node_key, client)
|
237
|
+
results[node_key] = reply unless reply.nil?
|
238
|
+
rescue ::RedisClient::CommandError => e
|
239
|
+
errors[node_key] = e
|
240
|
+
end
|
227
241
|
end
|
228
242
|
|
243
|
+
threads.each(&:join)
|
229
244
|
return results if errors.empty?
|
230
245
|
|
231
246
|
raise ::RedisClient::Cluster::CommandErrorCollection, errors
|
data/lib/redis_client/cluster.rb
CHANGED
@@ -15,7 +15,6 @@ class RedisClient
|
|
15
15
|
def initialize(client)
|
16
16
|
@client = client
|
17
17
|
@grouped = Hash.new([].freeze)
|
18
|
-
@replies = []
|
19
18
|
@size = 0
|
20
19
|
end
|
21
20
|
|
@@ -41,27 +40,31 @@ class RedisClient
|
|
41
40
|
@size.zero?
|
42
41
|
end
|
43
42
|
|
44
|
-
# TODO:
|
43
|
+
# TODO: https://github.com/redis-rb/redis-cluster-client/issues/37
|
45
44
|
def execute # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
45
|
+
all_replies = Array.new(@size)
|
46
|
+
threads = @grouped.map do |k, v|
|
47
|
+
Thread.new(@client, k, v) do |client, node_key, rows|
|
48
|
+
Thread.pass
|
49
|
+
replies = client.send(:find_node, node_key).pipelined do |pipeline|
|
50
|
+
rows.each do |row|
|
51
|
+
case row[1]
|
52
|
+
when :call then pipeline.call(*row[2], **row[3])
|
53
|
+
when :call_once then pipeline.call_once(*row[2], **row[3])
|
54
|
+
when :blocking_call then pipeline.blocking_call(row[2], *row[3], **row[4])
|
55
|
+
else raise NotImplementedError, row[1]
|
56
|
+
end
|
55
57
|
end
|
56
58
|
end
|
57
|
-
end
|
58
59
|
|
59
|
-
|
60
|
+
raise ReplySizeError, "commands: #{rows.size}, replies: #{replies.size}" if rows.size != replies.size
|
60
61
|
|
61
|
-
|
62
|
+
rows.each_with_index { |row, idx| all_replies[row.first] = replies[idx] }
|
63
|
+
end
|
62
64
|
end
|
63
65
|
|
64
|
-
|
66
|
+
threads.each(&:join)
|
67
|
+
all_replies
|
65
68
|
end
|
66
69
|
end
|
67
70
|
|
@@ -103,6 +106,7 @@ class RedisClient
|
|
103
106
|
@client_kwargs = kwargs
|
104
107
|
@node = fetch_cluster_info!(@config, pool: @pool, **@client_kwargs)
|
105
108
|
@command = ::RedisClient::Cluster::Command.load(@node)
|
109
|
+
@mutex = Mutex.new
|
106
110
|
end
|
107
111
|
|
108
112
|
def inspect
|
@@ -178,11 +182,11 @@ class RedisClient
|
|
178
182
|
def send_command(method, *command, **kwargs, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
|
179
183
|
cmd = command.first.to_s.downcase
|
180
184
|
case cmd
|
181
|
-
when 'acl', 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save'
|
185
|
+
when 'acl', 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save', 'ping'
|
182
186
|
@node.call_all(method, *command, **kwargs, &block).first
|
183
187
|
when 'flushall', 'flushdb'
|
184
188
|
@node.call_primary(method, *command, **kwargs, &block).first
|
185
|
-
when 'wait' then
|
189
|
+
when 'wait' then send_wait_command(method, *command, **kwargs, &block)
|
186
190
|
when 'keys' then @node.call_replica(method, *command, **kwargs, &block).flatten.sort
|
187
191
|
when 'dbsize' then @node.call_replica(method, *command, **kwargs, &block).sum
|
188
192
|
when 'scan' then _scan(*command, **kwargs)
|
@@ -202,6 +206,19 @@ class RedisClient
|
|
202
206
|
node = assign_node(*command)
|
203
207
|
try_send(node, method, *command, **kwargs, &block)
|
204
208
|
end
|
209
|
+
rescue RedisClient::Cluster::CommandErrorCollection => e
|
210
|
+
update_cluster_info! if e.errors.values.map(&:class).any?(::RedisClient::ConnectionError)
|
211
|
+
raise
|
212
|
+
end
|
213
|
+
|
214
|
+
def send_wait_command(method, *command, retry_count: 3, **kwargs, &block)
|
215
|
+
@node.call_primary(method, *command, **kwargs, &block).sum
|
216
|
+
rescue RedisClient::Cluster::CommandErrorCollection => e
|
217
|
+
raise if retry_count <= 0 || e.errors.values.map(&:message).grep(/ERR WAIT cannot be used with replica instances/).empty?
|
218
|
+
|
219
|
+
update_cluster_info!
|
220
|
+
retry_count -= 1
|
221
|
+
retry
|
205
222
|
end
|
206
223
|
|
207
224
|
def send_config_command(method, *command, **kwargs, &block)
|
@@ -263,18 +280,16 @@ class RedisClient
|
|
263
280
|
|
264
281
|
# @see https://redis.io/topics/cluster-spec#redirection-and-resharding
|
265
282
|
# Redirection and resharding
|
266
|
-
def try_send(node, method, *args, retry_count: 3, **kwargs, &block) # rubocop:disable Metrics/
|
283
|
+
def try_send(node, method, *args, retry_count: 3, **kwargs, &block) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
267
284
|
node.send(method, *args, **kwargs, &block)
|
268
285
|
rescue ::RedisClient::CommandError => e
|
269
|
-
if
|
270
|
-
raise if retry_count <= 0
|
286
|
+
raise if retry_count <= 0
|
271
287
|
|
288
|
+
if e.message.start_with?(REPLY_MOVED)
|
272
289
|
node = assign_redirection_node(e.message)
|
273
290
|
retry_count -= 1
|
274
291
|
retry
|
275
292
|
elsif e.message.start_with?(REPLY_ASK)
|
276
|
-
raise if retry_count <= 0
|
277
|
-
|
278
293
|
node = assign_asking_node(e.message)
|
279
294
|
node.call(CMD_ASKING)
|
280
295
|
retry_count -= 1
|
@@ -283,8 +298,11 @@ class RedisClient
|
|
283
298
|
raise
|
284
299
|
end
|
285
300
|
rescue ::RedisClient::ConnectionError
|
301
|
+
raise if retry_count <= 0
|
302
|
+
|
286
303
|
update_cluster_info!
|
287
|
-
|
304
|
+
retry_count -= 1
|
305
|
+
retry
|
288
306
|
end
|
289
307
|
|
290
308
|
def _scan(*command, **kwargs) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
@@ -326,9 +344,13 @@ class RedisClient
|
|
326
344
|
find_node(node_key)
|
327
345
|
end
|
328
346
|
|
329
|
-
def find_node_key(*command, primary_only: false)
|
347
|
+
def find_node_key(*command, primary_only: false) # rubocop:disable Metrics/MethodLength
|
330
348
|
key = @command.extract_first_key(command)
|
331
|
-
|
349
|
+
if key.empty?
|
350
|
+
return @node.primary_node_keys.sample if @command.should_send_to_primary?(command) || primary_only
|
351
|
+
|
352
|
+
return @node.replica_node_keys.sample
|
353
|
+
end
|
332
354
|
|
333
355
|
slot = ::RedisClient::Cluster::KeySlotConverter.convert(key)
|
334
356
|
return unless @node.slot_exists?(slot)
|
@@ -340,23 +362,28 @@ class RedisClient
|
|
340
362
|
end
|
341
363
|
end
|
342
364
|
|
343
|
-
def find_node(node_key)
|
365
|
+
def find_node(node_key, retry_count: 3)
|
344
366
|
return @node.sample if node_key.nil?
|
345
367
|
|
346
368
|
@node.find_by(node_key)
|
347
369
|
rescue ::RedisClient::Cluster::Node::ReloadNeeded
|
370
|
+
raise(::RedisClient::ConnectionError, 'unstable cluster state') if retry_count <= 0
|
371
|
+
|
348
372
|
update_cluster_info!(node_key)
|
349
|
-
|
373
|
+
retry_count -= 1
|
374
|
+
retry
|
350
375
|
end
|
351
376
|
|
352
377
|
def update_cluster_info!(node_key = nil)
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
378
|
+
@mutex.synchronize do
|
379
|
+
unless node_key.nil?
|
380
|
+
host, port = ::RedisClient::Cluster::NodeKey.split(node_key)
|
381
|
+
@config.add_node(host, port)
|
382
|
+
end
|
357
383
|
|
358
|
-
|
359
|
-
|
384
|
+
@node.each(&:close)
|
385
|
+
@node = fetch_cluster_info!(@config, pool: @pool, **@client_kwargs)
|
386
|
+
end
|
360
387
|
end
|
361
388
|
end
|
362
389
|
end
|
@@ -15,6 +15,7 @@ class RedisClient
|
|
15
15
|
VALID_SCHEMES = [DEFAULT_SCHEME, SECURE_SCHEME].freeze
|
16
16
|
VALID_NODES_KEYS = %i[ssl username password host port db].freeze
|
17
17
|
MERGE_CONFIG_KEYS = %i[ssl username password].freeze
|
18
|
+
IGNORE_GENERIC_CONFIG_KEYS = %i[url host port path].freeze
|
18
19
|
|
19
20
|
InvalidClientConfigError = Class.new(::RedisClient::Error)
|
20
21
|
|
@@ -22,7 +23,9 @@ class RedisClient
|
|
22
23
|
@replica = true & replica
|
23
24
|
@fixed_hostname = fixed_hostname.to_s
|
24
25
|
@node_configs = build_node_configs(nodes.dup)
|
26
|
+
client_config = client_config.reject { |k, _| IGNORE_GENERIC_CONFIG_KEYS.include?(k) }
|
25
27
|
@client_config = merge_generic_config(client_config, @node_configs)
|
28
|
+
@mutex = Mutex.new
|
26
29
|
end
|
27
30
|
|
28
31
|
def inspect
|
@@ -51,11 +54,11 @@ class RedisClient
|
|
51
54
|
end
|
52
55
|
|
53
56
|
def update_node(addrs)
|
54
|
-
@node_configs = build_node_configs(addrs)
|
57
|
+
@mutex.synchronize { @node_configs = build_node_configs(addrs) }
|
55
58
|
end
|
56
59
|
|
57
60
|
def add_node(host, port)
|
58
|
-
@node_configs << { host: host, port: port }
|
61
|
+
@mutex.synchronize { @node_configs << { host: host, port: port } }
|
59
62
|
end
|
60
63
|
|
61
64
|
def dup
|
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.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-06-
|
11
|
+
date: 2022-06-19 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis-client
|