redis-cluster-client 0.0.3 → 0.0.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/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
|