redis-cluster-client 0.11.6 → 0.12.0
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/command.rb +2 -2
- data/lib/redis_client/cluster/errors.rb +27 -18
- data/lib/redis_client/cluster/node.rb +4 -4
- data/lib/redis_client/cluster/pipeline.rb +4 -4
- data/lib/redis_client/cluster/router.rb +17 -10
- data/lib/redis_client/cluster/transaction.rb +5 -4
- data/lib/redis_client/cluster_config.rb +16 -2
- metadata +3 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2465b865cd79c78cca3a1e7e3950989cce94f6675b0497763e03e261d3fa9c6f
|
4
|
+
data.tar.gz: df607279a58171aca75835b767f3bf5965256db0fcb7b540bfcb3a8f302eb638
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: bfb9031660b642bb14768dc8edc66fb15073be9ca23ddc061d279a351c497a83ff8a3aad21903e2d0a37aa6824da35761188ba4dc1fe8ae0182b54584edaa5e5
|
7
|
+
data.tar.gz: 9fab63d0595bfb7dac85a07c255a41573ab8d44598605919fd4182e99cf3da585bf931abe63da9e2f470815c6ef0d34ab24a5b7f2de346ba29870fb894c556ee
|
@@ -25,7 +25,7 @@ class RedisClient
|
|
25
25
|
)
|
26
26
|
|
27
27
|
class << self
|
28
|
-
def load(nodes, slow_command_timeout: -1)
|
28
|
+
def load(nodes, slow_command_timeout: -1) # rubocop:disable Metrics/AbcSize
|
29
29
|
cmd = errors = nil
|
30
30
|
|
31
31
|
nodes&.each do |node|
|
@@ -43,7 +43,7 @@ class RedisClient
|
|
43
43
|
|
44
44
|
return cmd unless cmd.nil?
|
45
45
|
|
46
|
-
raise ::RedisClient::Cluster::InitialSetupError
|
46
|
+
raise ::RedisClient::Cluster::InitialSetupError.from_errors(errors)
|
47
47
|
end
|
48
48
|
|
49
49
|
private
|
@@ -4,51 +4,60 @@ require 'redis_client'
|
|
4
4
|
|
5
5
|
class RedisClient
|
6
6
|
class Cluster
|
7
|
+
class Error < ::RedisClient::Error
|
8
|
+
def with_config(config)
|
9
|
+
@config = config
|
10
|
+
self
|
11
|
+
end
|
12
|
+
end
|
13
|
+
|
7
14
|
ERR_ARG_NORMALIZATION = ->(arg) { Array[arg].flatten.reject { |e| e.nil? || (e.respond_to?(:empty?) && e.empty?) } }
|
8
15
|
|
9
16
|
private_constant :ERR_ARG_NORMALIZATION
|
10
17
|
|
11
|
-
class InitialSetupError <
|
12
|
-
def
|
18
|
+
class InitialSetupError < Error
|
19
|
+
def self.from_errors(errors)
|
13
20
|
msg = ERR_ARG_NORMALIZATION.call(errors).map(&:message).uniq.join(',')
|
14
|
-
|
21
|
+
new("Redis client could not fetch cluster information: #{msg}")
|
15
22
|
end
|
16
23
|
end
|
17
24
|
|
18
|
-
class OrchestrationCommandNotSupported <
|
19
|
-
def
|
25
|
+
class OrchestrationCommandNotSupported < Error
|
26
|
+
def self.from_command(command)
|
20
27
|
str = ERR_ARG_NORMALIZATION.call(command).map(&:to_s).join(' ').upcase
|
21
28
|
msg = "#{str} command should be used with care " \
|
22
29
|
'only by applications orchestrating Redis Cluster, like redis-cli, ' \
|
23
30
|
'and the command if used out of the right context can leave the cluster ' \
|
24
31
|
'in a wrong state or cause data loss.'
|
25
|
-
|
32
|
+
new(msg)
|
26
33
|
end
|
27
34
|
end
|
28
35
|
|
29
|
-
class ErrorCollection <
|
36
|
+
class ErrorCollection < Error
|
30
37
|
attr_reader :errors
|
31
38
|
|
32
|
-
def
|
33
|
-
@errors = {}
|
39
|
+
def self.with_errors(errors)
|
34
40
|
if !errors.is_a?(Hash) || errors.empty?
|
35
|
-
|
36
|
-
|
41
|
+
new(errors.to_s).with_errors({})
|
42
|
+
else
|
43
|
+
messages = errors.map { |node_key, error| "#{node_key}: (#{error.class}) #{error.message}" }
|
44
|
+
new(messages.join(', ')).with_errors(errors)
|
37
45
|
end
|
46
|
+
end
|
38
47
|
|
39
|
-
|
40
|
-
|
41
|
-
|
48
|
+
def with_errors(errors)
|
49
|
+
@errors = errors if @errors.nil?
|
50
|
+
self
|
42
51
|
end
|
43
52
|
end
|
44
53
|
|
45
|
-
class AmbiguousNodeError <
|
46
|
-
def
|
47
|
-
|
54
|
+
class AmbiguousNodeError < Error
|
55
|
+
def self.from_command(command)
|
56
|
+
new("Cluster client doesn't know which node the #{command} command should be sent to.")
|
48
57
|
end
|
49
58
|
end
|
50
59
|
|
51
|
-
class NodeMightBeDown <
|
60
|
+
class NodeMightBeDown < Error
|
52
61
|
def initialize(_ = '')
|
53
62
|
super(
|
54
63
|
'The client is trying to fetch the latest cluster state ' \
|
@@ -28,7 +28,7 @@ class RedisClient
|
|
28
28
|
private_constant :USE_CHAR_ARRAY_SLOT, :SLOT_SIZE, :MIN_SLOT, :MAX_SLOT,
|
29
29
|
:DEAD_FLAGS, :ROLE_FLAGS, :EMPTY_ARRAY, :EMPTY_HASH
|
30
30
|
|
31
|
-
ReloadNeeded = Class.new(::RedisClient::Error)
|
31
|
+
ReloadNeeded = Class.new(::RedisClient::Cluster::Error)
|
32
32
|
|
33
33
|
Info = Struct.new(
|
34
34
|
'RedisClusterNode',
|
@@ -148,7 +148,7 @@ class RedisClient
|
|
148
148
|
|
149
149
|
raise ReloadNeeded if errors.values.any?(::RedisClient::ConnectionError)
|
150
150
|
|
151
|
-
raise ::RedisClient::Cluster::ErrorCollection
|
151
|
+
raise ::RedisClient::Cluster::ErrorCollection.with_errors(errors)
|
152
152
|
end
|
153
153
|
|
154
154
|
def clients_for_scanning(seed: nil)
|
@@ -267,7 +267,7 @@ class RedisClient
|
|
267
267
|
result_values, errors = call_multiple_nodes(clients, method, command, args, &block)
|
268
268
|
return result_values if errors.nil? || errors.empty?
|
269
269
|
|
270
|
-
raise ::RedisClient::Cluster::ErrorCollection
|
270
|
+
raise ::RedisClient::Cluster::ErrorCollection.with_errors(errors)
|
271
271
|
end
|
272
272
|
|
273
273
|
def try_map(clients, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity
|
@@ -334,7 +334,7 @@ class RedisClient
|
|
334
334
|
|
335
335
|
work_group.close
|
336
336
|
|
337
|
-
raise ::RedisClient::Cluster::InitialSetupError
|
337
|
+
raise ::RedisClient::Cluster::InitialSetupError.from_errors(errors) if node_info_list.nil?
|
338
338
|
|
339
339
|
grouped = node_info_list.compact.group_by do |info_list|
|
340
340
|
info_list.sort_by!(&:id)
|
@@ -108,13 +108,13 @@ class RedisClient
|
|
108
108
|
end
|
109
109
|
end
|
110
110
|
|
111
|
-
ReplySizeError = Class.new(::RedisClient::Error)
|
111
|
+
ReplySizeError = Class.new(::RedisClient::Cluster::Error)
|
112
112
|
|
113
|
-
class StaleClusterState < ::RedisClient::Error
|
113
|
+
class StaleClusterState < ::RedisClient::Cluster::Error
|
114
114
|
attr_accessor :replies, :first_exception
|
115
115
|
end
|
116
116
|
|
117
|
-
class RedirectionNeeded < ::RedisClient::Error
|
117
|
+
class RedirectionNeeded < ::RedisClient::Cluster::Error
|
118
118
|
attr_accessor :replies, :indices, :first_exception
|
119
119
|
end
|
120
120
|
|
@@ -204,7 +204,7 @@ class RedisClient
|
|
204
204
|
|
205
205
|
work_group.close
|
206
206
|
@router.renew_cluster_state if cluster_state_errors
|
207
|
-
raise ::RedisClient::Cluster::ErrorCollection
|
207
|
+
raise ::RedisClient::Cluster::ErrorCollection.with_errors(errors).with_config(@router.config) unless errors.nil?
|
208
208
|
|
209
209
|
required_redirections&.each do |node_key, v|
|
210
210
|
raise v.first_exception if v.first_exception
|
@@ -21,10 +21,10 @@ class RedisClient
|
|
21
21
|
|
22
22
|
private_constant :ZERO_CURSOR_FOR_SCAN, :TSF
|
23
23
|
|
24
|
+
attr_reader :config
|
25
|
+
|
24
26
|
def initialize(config, concurrent_worker, pool: nil, **kwargs)
|
25
|
-
@config = config
|
26
|
-
@original_config = config.dup if config.connect_with_original_config
|
27
|
-
@connect_with_original_config = config.connect_with_original_config
|
27
|
+
@config = config
|
28
28
|
@concurrent_worker = concurrent_worker
|
29
29
|
@pool = pool
|
30
30
|
@client_kwargs = kwargs
|
@@ -32,6 +32,9 @@ class RedisClient
|
|
32
32
|
@node.reload!
|
33
33
|
@command = ::RedisClient::Cluster::Command.load(@node.replica_clients.shuffle, slow_command_timeout: config.slow_command_timeout)
|
34
34
|
@command_builder = @config.command_builder
|
35
|
+
rescue ::RedisClient::Cluster::InitialSetupError => e
|
36
|
+
e.with_config(config)
|
37
|
+
raise
|
35
38
|
end
|
36
39
|
|
37
40
|
def send_command(method, command, *args, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
@@ -58,9 +61,9 @@ class RedisClient
|
|
58
61
|
when 'flushall', 'flushdb'
|
59
62
|
@node.call_primaries(method, command, args).first.then(&TSF.call(block))
|
60
63
|
when 'readonly', 'readwrite', 'shutdown'
|
61
|
-
raise ::RedisClient::Cluster::OrchestrationCommandNotSupported
|
64
|
+
raise ::RedisClient::Cluster::OrchestrationCommandNotSupported.from_command(cmd).with_config(@config)
|
62
65
|
when 'discard', 'exec', 'multi', 'unwatch'
|
63
|
-
raise ::RedisClient::Cluster::AmbiguousNodeError
|
66
|
+
raise ::RedisClient::Cluster::AmbiguousNodeError.from_command(cmd).with_config(@config)
|
64
67
|
else
|
65
68
|
node = assign_node(command)
|
66
69
|
try_send(node, method, command, args, &block)
|
@@ -69,7 +72,7 @@ class RedisClient
|
|
69
72
|
raise
|
70
73
|
rescue ::RedisClient::Cluster::Node::ReloadNeeded
|
71
74
|
renew_cluster_state
|
72
|
-
raise ::RedisClient::Cluster::NodeMightBeDown
|
75
|
+
raise ::RedisClient::Cluster::NodeMightBeDown.new.with_config(@config)
|
73
76
|
rescue ::RedisClient::ConnectionError
|
74
77
|
renew_cluster_state
|
75
78
|
raise
|
@@ -77,6 +80,7 @@ class RedisClient
|
|
77
80
|
renew_cluster_state if e.message.start_with?('CLUSTERDOWN')
|
78
81
|
raise
|
79
82
|
rescue ::RedisClient::Cluster::ErrorCollection => e
|
83
|
+
e.with_config(@config)
|
80
84
|
raise if e.errors.any?(::RedisClient::CircuitBreaker::OpenCircuitError)
|
81
85
|
|
82
86
|
renew_cluster_state if e.errors.values.any? do |err|
|
@@ -189,7 +193,7 @@ class RedisClient
|
|
189
193
|
node_key = primary ? @node.find_node_key_of_primary(slot) : @node.find_node_key_of_replica(slot)
|
190
194
|
if node_key.nil?
|
191
195
|
renew_cluster_state
|
192
|
-
raise ::RedisClient::Cluster::NodeMightBeDown
|
196
|
+
raise ::RedisClient::Cluster::NodeMightBeDown.new.with_config(@config)
|
193
197
|
end
|
194
198
|
node_key
|
195
199
|
else
|
@@ -303,7 +307,7 @@ class RedisClient
|
|
303
307
|
case subcommand = ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_subcommand(command)
|
304
308
|
when 'addslots', 'delslots', 'failover', 'forget', 'meet', 'replicate',
|
305
309
|
'reset', 'set-config-epoch', 'setslot'
|
306
|
-
raise ::RedisClient::Cluster::OrchestrationCommandNotSupported
|
310
|
+
raise ::RedisClient::Cluster::OrchestrationCommandNotSupported.from_command(['cluster', subcommand]).with_config(@config)
|
307
311
|
when 'saveconfig' then @node.call_all(method, command, args).first.then(&TSF.call(block))
|
308
312
|
when 'getkeysinslot'
|
309
313
|
raise ArgumentError, command.join(' ') if command.size != 4
|
@@ -347,7 +351,10 @@ class RedisClient
|
|
347
351
|
end
|
348
352
|
|
349
353
|
def send_watch_command(command)
|
350
|
-
|
354
|
+
unless block_given?
|
355
|
+
msg = 'A block required. And you need to use the block argument as a client for the transaction.'
|
356
|
+
raise ::RedisClient::Cluster::Transaction::ConsistencyError.new(msg).with_config(@config)
|
357
|
+
end
|
351
358
|
|
352
359
|
::RedisClient::Cluster::OptimisticLocking.new(self).watch(command[1..]) do |c, slot, asking|
|
353
360
|
transaction = ::RedisClient::Cluster::Transaction.new(
|
@@ -401,7 +408,7 @@ class RedisClient
|
|
401
408
|
def handle_node_reload_error(retry_count: 1)
|
402
409
|
yield
|
403
410
|
rescue ::RedisClient::Cluster::Node::ReloadNeeded
|
404
|
-
raise ::RedisClient::Cluster::NodeMightBeDown if retry_count <= 0
|
411
|
+
raise ::RedisClient::Cluster::NodeMightBeDown.new.with_config(@config) if retry_count <= 0
|
405
412
|
|
406
413
|
retry_count -= 1
|
407
414
|
renew_cluster_state
|
@@ -1,12 +1,13 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'redis_client'
|
4
|
+
require 'redis_client/cluster/errors'
|
4
5
|
require 'redis_client/cluster/pipeline'
|
5
6
|
|
6
7
|
class RedisClient
|
7
8
|
class Cluster
|
8
9
|
class Transaction
|
9
|
-
ConsistencyError = Class.new(::RedisClient::Error)
|
10
|
+
ConsistencyError = Class.new(::RedisClient::Cluster::Error)
|
10
11
|
|
11
12
|
MAX_REDIRECTION = 2
|
12
13
|
EMPTY_ARRAY = [].freeze
|
@@ -67,7 +68,7 @@ class RedisClient
|
|
67
68
|
@pending_commands.each(&:call)
|
68
69
|
|
69
70
|
return EMPTY_ARRAY if @pipeline._empty?
|
70
|
-
raise ConsistencyError
|
71
|
+
raise ConsistencyError.new("couldn't determine the node: #{@pipeline._commands}").with_config(@router.config) if @node.nil?
|
71
72
|
|
72
73
|
commit
|
73
74
|
end
|
@@ -163,7 +164,7 @@ class RedisClient
|
|
163
164
|
|
164
165
|
def handle_command_error!(err, redirect:) # rubocop:disable Metrics/AbcSize
|
165
166
|
if err.message.start_with?('CROSSSLOT')
|
166
|
-
raise ConsistencyError
|
167
|
+
raise ConsistencyError.new("#{err.message}: #{err.command}").with_config(@router.config)
|
167
168
|
elsif err.message.start_with?('MOVED')
|
168
169
|
node = @router.assign_redirection_node(err.message)
|
169
170
|
send_transaction(node, redirect: redirect - 1)
|
@@ -183,7 +184,7 @@ class RedisClient
|
|
183
184
|
return if slots.size == 1 && @watching_slot.nil?
|
184
185
|
return if slots.size == 1 && @watching_slot == slots.first
|
185
186
|
|
186
|
-
raise(
|
187
|
+
raise ConsistencyError.new("the transaction should be executed to a slot in a node: #{commands}").with_config(@router.config)
|
187
188
|
end
|
188
189
|
|
189
190
|
def try_asking(node)
|
@@ -3,6 +3,7 @@
|
|
3
3
|
require 'uri'
|
4
4
|
require 'redis_client'
|
5
5
|
require 'redis_client/cluster'
|
6
|
+
require 'redis_client/cluster/errors'
|
6
7
|
require 'redis_client/cluster/node_key'
|
7
8
|
require 'redis_client/command_builder'
|
8
9
|
|
@@ -27,10 +28,10 @@ class RedisClient
|
|
27
28
|
:VALID_SCHEMES, :VALID_NODES_KEYS, :MERGE_CONFIG_KEYS, :IGNORE_GENERIC_CONFIG_KEYS,
|
28
29
|
:MAX_WORKERS, :SLOW_COMMAND_TIMEOUT, :MAX_STARTUP_SAMPLE
|
29
30
|
|
30
|
-
InvalidClientConfigError = Class.new(::RedisClient::Error)
|
31
|
+
InvalidClientConfigError = Class.new(::RedisClient::Cluster::Error)
|
31
32
|
|
32
33
|
attr_reader :command_builder, :client_config, :replica_affinity, :slow_command_timeout,
|
33
|
-
:connect_with_original_config, :startup_nodes, :max_startup_sample
|
34
|
+
:connect_with_original_config, :startup_nodes, :max_startup_sample, :id
|
34
35
|
|
35
36
|
def initialize( # rubocop:disable Metrics/ParameterLists
|
36
37
|
nodes: DEFAULT_NODES,
|
@@ -59,6 +60,7 @@ class RedisClient
|
|
59
60
|
@client_implementation = client_implementation
|
60
61
|
@slow_command_timeout = slow_command_timeout
|
61
62
|
@max_startup_sample = max_startup_sample
|
63
|
+
@id = client_config[:id]
|
62
64
|
end
|
63
65
|
|
64
66
|
def inspect
|
@@ -92,6 +94,18 @@ class RedisClient
|
|
92
94
|
augment_client_config(config)
|
93
95
|
end
|
94
96
|
|
97
|
+
def resolved?
|
98
|
+
true
|
99
|
+
end
|
100
|
+
|
101
|
+
def sentinel?
|
102
|
+
false
|
103
|
+
end
|
104
|
+
|
105
|
+
def server_url
|
106
|
+
nil
|
107
|
+
end
|
108
|
+
|
95
109
|
private
|
96
110
|
|
97
111
|
def merge_concurrency_option(option)
|
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.
|
4
|
+
version: 0.12.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: 2024-
|
11
|
+
date: 2024-11-14 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis-client
|
@@ -77,7 +77,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
77
77
|
- !ruby/object:Gem::Version
|
78
78
|
version: '0'
|
79
79
|
requirements: []
|
80
|
-
rubygems_version: 3.5.
|
80
|
+
rubygems_version: 3.5.22
|
81
81
|
signing_key:
|
82
82
|
specification_version: 4
|
83
83
|
summary: A Redis cluster client for Ruby
|