redis-cluster-client 0.11.6 → 0.12.1
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/command.rb +5 -53
- data/lib/redis_client/cluster/errors.rb +30 -18
- data/lib/redis_client/cluster/node.rb +4 -4
- data/lib/redis_client/cluster/noop_command_builder.rb +13 -0
- data/lib/redis_client/cluster/pipeline.rb +6 -5
- data/lib/redis_client/cluster/router.rb +40 -28
- data/lib/redis_client/cluster/transaction.rb +7 -5
- data/lib/redis_client/cluster.rb +16 -9
- data/lib/redis_client/cluster_config.rb +19 -3
- metadata +4 -3
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5d90c095f5058e808331eaa9219fb52b9d4a27de639a9c54d194c68ecb253611
|
4
|
+
data.tar.gz: 8cd8092db87c7cfa42e00c6cabd30747bdea74130d8eb1e19b971b95637665fa
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4821b78b0d5766566fa3943f65271908d3e52e3f82269bd2ea0a789e571df7818b5e0275c88d8833eb7b449c7c0eb3ed672034c1caf03fac35528114f31adb73
|
7
|
+
data.tar.gz: a4bb1d4a766cd742645c61342c2822e2bd337c792fb67b9793bfbb06ec0a889185eaa8b3ed34f96591f9e57997ecfe83cc901ce65451577b7e8f13d7d23f64b3
|
@@ -17,7 +17,6 @@ class RedisClient
|
|
17
17
|
Detail = Struct.new(
|
18
18
|
'RedisCommand',
|
19
19
|
:first_key_position,
|
20
|
-
:last_key_position,
|
21
20
|
:key_step,
|
22
21
|
:write?,
|
23
22
|
:readonly?,
|
@@ -25,7 +24,7 @@ class RedisClient
|
|
25
24
|
)
|
26
25
|
|
27
26
|
class << self
|
28
|
-
def load(nodes, slow_command_timeout: -1)
|
27
|
+
def load(nodes, slow_command_timeout: -1) # rubocop:disable Metrics/AbcSize
|
29
28
|
cmd = errors = nil
|
30
29
|
|
31
30
|
nodes&.each do |node|
|
@@ -43,7 +42,7 @@ class RedisClient
|
|
43
42
|
|
44
43
|
return cmd unless cmd.nil?
|
45
44
|
|
46
|
-
raise ::RedisClient::Cluster::InitialSetupError
|
45
|
+
raise ::RedisClient::Cluster::InitialSetupError.from_errors(errors)
|
47
46
|
end
|
48
47
|
|
49
48
|
private
|
@@ -54,7 +53,6 @@ class RedisClient
|
|
54
53
|
|
55
54
|
acc[row[0].downcase] = ::RedisClient::Cluster::Command::Detail.new(
|
56
55
|
first_key_position: row[3],
|
57
|
-
last_key_position: row[4],
|
58
56
|
key_step: row[5],
|
59
57
|
write?: row[2].include?('write'),
|
60
58
|
readonly?: row[2].include?('readonly')
|
@@ -71,18 +69,7 @@ class RedisClient
|
|
71
69
|
i = determine_first_key_position(command)
|
72
70
|
return EMPTY_STRING if i == 0
|
73
71
|
|
74
|
-
|
75
|
-
end
|
76
|
-
|
77
|
-
def extract_all_keys(command)
|
78
|
-
keys_start = determine_first_key_position(command)
|
79
|
-
keys_end = determine_last_key_position(command, keys_start)
|
80
|
-
keys_step = determine_key_step(command)
|
81
|
-
return EMPTY_ARRAY if [keys_start, keys_end, keys_step].any?(&:zero?)
|
82
|
-
|
83
|
-
keys_end = [keys_end, command.size - 1].min
|
84
|
-
# use .. inclusive range because keys_end is a valid index.
|
85
|
-
(keys_start..keys_end).step(keys_step).map { |i| command[i] }
|
72
|
+
command[i]
|
86
73
|
end
|
87
74
|
|
88
75
|
def should_send_to_primary?(command)
|
@@ -116,45 +103,10 @@ class RedisClient
|
|
116
103
|
end
|
117
104
|
end
|
118
105
|
|
119
|
-
|
120
|
-
|
121
|
-
# This is in line with what Redis returns from COMMANDS.
|
122
|
-
def determine_last_key_position(command, keys_start) # rubocop:disable Metrics/AbcSize
|
123
|
-
case name = ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_command(command)
|
124
|
-
when 'eval', 'evalsha', 'zinterstore', 'zunionstore'
|
125
|
-
# EVALSHA sha1 numkeys [key [key ...]] [arg [arg ...]]
|
126
|
-
# ZINTERSTORE destination numkeys key [key ...] [WEIGHTS weight [weight ...]] [AGGREGATE <SUM | MIN | MAX>]
|
127
|
-
command[2].to_i + 2
|
128
|
-
when 'object', 'memory'
|
129
|
-
# OBJECT [ENCODING | FREQ | IDLETIME | REFCOUNT] key
|
130
|
-
# MEMORY USAGE key [SAMPLES count]
|
131
|
-
keys_start
|
132
|
-
when 'migrate'
|
133
|
-
# MIGRATE host port <key | ""> destination-db timeout [COPY] [REPLACE] [AUTH password | AUTH2 username password] [KEYS key [key ...]]
|
134
|
-
command[3].empty? ? (command.length - 1) : 3
|
135
|
-
when 'xread', 'xreadgroup'
|
136
|
-
# XREAD [COUNT count] [BLOCK milliseconds] STREAMS key [key ...] id [id ...]
|
137
|
-
keys_start + ((command.length - keys_start) / 2) - 1
|
138
|
-
else
|
139
|
-
# If there is a fixed, non-variable number of keys, don't iterate past that.
|
140
|
-
if @commands[name].last_key_position >= 0
|
141
|
-
@commands[name].last_key_position
|
142
|
-
else
|
143
|
-
command.length + @commands[name].last_key_position
|
144
|
-
end
|
145
|
-
end
|
146
|
-
end
|
147
|
-
|
148
|
-
def determine_optional_key_position(command, option_name) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
149
|
-
idx = command&.flatten&.map(&:to_s)&.map(&:downcase)&.index(option_name&.downcase)
|
106
|
+
def determine_optional_key_position(command, option_name)
|
107
|
+
idx = command.map { |e| e.to_s.downcase }.index(option_name&.downcase)
|
150
108
|
idx.nil? ? 0 : idx + 1
|
151
109
|
end
|
152
|
-
|
153
|
-
def determine_key_step(command)
|
154
|
-
name = ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_command(command)
|
155
|
-
# Some commands like EVALSHA have zero as the step in COMMANDS somehow.
|
156
|
-
@commands[name].key_step == 0 ? 1 : @commands[name].key_step
|
157
|
-
end
|
158
110
|
end
|
159
111
|
end
|
160
112
|
end
|
@@ -4,51 +4,63 @@ 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
|
37
|
+
EMPTY_HASH = {}.freeze
|
38
|
+
|
39
|
+
private_constant :EMPTY_HASH
|
30
40
|
attr_reader :errors
|
31
41
|
|
32
|
-
def
|
33
|
-
@errors = {}
|
42
|
+
def self.with_errors(errors)
|
34
43
|
if !errors.is_a?(Hash) || errors.empty?
|
35
|
-
|
36
|
-
|
44
|
+
new(errors.to_s).with_errors(EMPTY_HASH)
|
45
|
+
else
|
46
|
+
messages = errors.map { |node_key, error| "#{node_key}: (#{error.class}) #{error.message}" }
|
47
|
+
new(messages.join(', ')).with_errors(errors)
|
37
48
|
end
|
49
|
+
end
|
38
50
|
|
39
|
-
|
40
|
-
|
41
|
-
|
51
|
+
def with_errors(errors)
|
52
|
+
@errors = errors if @errors.nil?
|
53
|
+
self
|
42
54
|
end
|
43
55
|
end
|
44
56
|
|
45
|
-
class AmbiguousNodeError <
|
46
|
-
def
|
47
|
-
|
57
|
+
class AmbiguousNodeError < Error
|
58
|
+
def self.from_command(command)
|
59
|
+
new("Cluster client doesn't know which node the #{command} command should be sent to.")
|
48
60
|
end
|
49
61
|
end
|
50
62
|
|
51
|
-
class NodeMightBeDown <
|
63
|
+
class NodeMightBeDown < Error
|
52
64
|
def initialize(_ = '')
|
53
65
|
super(
|
54
66
|
'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)
|
@@ -2,6 +2,7 @@
|
|
2
2
|
|
3
3
|
require 'redis_client'
|
4
4
|
require 'redis_client/cluster/errors'
|
5
|
+
require 'redis_client/cluster/noop_command_builder'
|
5
6
|
require 'redis_client/connection_mixin'
|
6
7
|
require 'redis_client/middlewares'
|
7
8
|
require 'redis_client/pooled'
|
@@ -108,13 +109,13 @@ class RedisClient
|
|
108
109
|
end
|
109
110
|
end
|
110
111
|
|
111
|
-
ReplySizeError = Class.new(::RedisClient::Error)
|
112
|
+
ReplySizeError = Class.new(::RedisClient::Cluster::Error)
|
112
113
|
|
113
|
-
class StaleClusterState < ::RedisClient::Error
|
114
|
+
class StaleClusterState < ::RedisClient::Cluster::Error
|
114
115
|
attr_accessor :replies, :first_exception
|
115
116
|
end
|
116
117
|
|
117
|
-
class RedirectionNeeded < ::RedisClient::Error
|
118
|
+
class RedirectionNeeded < ::RedisClient::Cluster::Error
|
118
119
|
attr_accessor :replies, :indices, :first_exception
|
119
120
|
end
|
120
121
|
|
@@ -204,7 +205,7 @@ class RedisClient
|
|
204
205
|
|
205
206
|
work_group.close
|
206
207
|
@router.renew_cluster_state if cluster_state_errors
|
207
|
-
raise ::RedisClient::Cluster::ErrorCollection
|
208
|
+
raise ::RedisClient::Cluster::ErrorCollection.with_errors(errors).with_config(@router.config) unless errors.nil?
|
208
209
|
|
209
210
|
required_redirections&.each do |node_key, v|
|
210
211
|
raise v.first_exception if v.first_exception
|
@@ -229,7 +230,7 @@ class RedisClient
|
|
229
230
|
|
230
231
|
def append_pipeline(node_key)
|
231
232
|
@pipelines ||= {}
|
232
|
-
@pipelines[node_key] ||= ::RedisClient::Cluster::Pipeline::Extended.new(
|
233
|
+
@pipelines[node_key] ||= ::RedisClient::Cluster::Pipeline::Extended.new(::RedisClient::Cluster::NoopCommandBuilder)
|
233
234
|
@pipelines[node_key].add_outer_index(@size)
|
234
235
|
@size += 1
|
235
236
|
@pipelines[node_key]
|
@@ -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|
|
@@ -100,12 +104,6 @@ class RedisClient
|
|
100
104
|
end
|
101
105
|
end
|
102
106
|
|
103
|
-
def try_delegate(node, method, *args, retry_count: 3, **kwargs, &block)
|
104
|
-
handle_redirection(node, nil, retry_count: retry_count) do |on_node|
|
105
|
-
on_node.public_send(method, *args, **kwargs, &block)
|
106
|
-
end
|
107
|
-
end
|
108
|
-
|
109
107
|
def handle_redirection(node, command, retry_count:) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
110
108
|
yield node
|
111
109
|
rescue ::RedisClient::CircuitBreaker::OpenCircuitError
|
@@ -149,9 +147,7 @@ class RedisClient
|
|
149
147
|
raise
|
150
148
|
end
|
151
149
|
|
152
|
-
def scan(
|
153
|
-
command = @command_builder.generate(command, kwargs)
|
154
|
-
|
150
|
+
def scan(command, seed: nil) # rubocop:disable Metrics/AbcSize
|
155
151
|
command[1] = ZERO_CURSOR_FOR_SCAN if command.size == 1
|
156
152
|
input_cursor = Integer(command[1])
|
157
153
|
|
@@ -176,6 +172,16 @@ class RedisClient
|
|
176
172
|
raise
|
177
173
|
end
|
178
174
|
|
175
|
+
def scan_single_key(command, arity:, &block)
|
176
|
+
node = assign_node(command)
|
177
|
+
loop do
|
178
|
+
cursor, values = handle_redirection(node, nil, retry_count: 3) { |n| n.call_v(command) }
|
179
|
+
command[2] = cursor
|
180
|
+
arity < 2 ? values.each(&block) : values.each_slice(arity, &block)
|
181
|
+
break if cursor == ZERO_CURSOR_FOR_SCAN
|
182
|
+
end
|
183
|
+
end
|
184
|
+
|
179
185
|
def assign_node(command)
|
180
186
|
handle_node_reload_error do
|
181
187
|
node_key = find_node_key(command)
|
@@ -189,7 +195,7 @@ class RedisClient
|
|
189
195
|
node_key = primary ? @node.find_node_key_of_primary(slot) : @node.find_node_key_of_replica(slot)
|
190
196
|
if node_key.nil?
|
191
197
|
renew_cluster_state
|
192
|
-
raise ::RedisClient::Cluster::NodeMightBeDown
|
198
|
+
raise ::RedisClient::Cluster::NodeMightBeDown.new.with_config(@config)
|
193
199
|
end
|
194
200
|
node_key
|
195
201
|
else
|
@@ -303,7 +309,7 @@ class RedisClient
|
|
303
309
|
case subcommand = ::RedisClient::Cluster::NormalizedCmdName.instance.get_by_subcommand(command)
|
304
310
|
when 'addslots', 'delslots', 'failover', 'forget', 'meet', 'replicate',
|
305
311
|
'reset', 'set-config-epoch', 'setslot'
|
306
|
-
raise ::RedisClient::Cluster::OrchestrationCommandNotSupported
|
312
|
+
raise ::RedisClient::Cluster::OrchestrationCommandNotSupported.from_command(['cluster', subcommand]).with_config(@config)
|
307
313
|
when 'saveconfig' then @node.call_all(method, command, args).first.then(&TSF.call(block))
|
308
314
|
when 'getkeysinslot'
|
309
315
|
raise ArgumentError, command.join(' ') if command.size != 4
|
@@ -347,7 +353,10 @@ class RedisClient
|
|
347
353
|
end
|
348
354
|
|
349
355
|
def send_watch_command(command)
|
350
|
-
|
356
|
+
unless block_given?
|
357
|
+
msg = 'A block required. And you need to use the block argument as a client for the transaction.'
|
358
|
+
raise ::RedisClient::Cluster::Transaction::ConsistencyError.new(msg).with_config(@config)
|
359
|
+
end
|
351
360
|
|
352
361
|
::RedisClient::Cluster::OptimisticLocking.new(self).watch(command[1..]) do |c, slot, asking|
|
353
362
|
transaction = ::RedisClient::Cluster::Transaction.new(
|
@@ -358,17 +367,20 @@ class RedisClient
|
|
358
367
|
end
|
359
368
|
end
|
360
369
|
|
361
|
-
MULTIPLE_KEYS_COMMAND_TO_SINGLE = {
|
362
|
-
'mget' => ['get', 1].freeze,
|
363
|
-
'mset' => ['set', 2].freeze,
|
364
|
-
'del' => ['del', 1].freeze
|
365
|
-
}.freeze
|
366
|
-
|
367
|
-
private_constant :MULTIPLE_KEYS_COMMAND_TO_SINGLE
|
368
|
-
|
369
370
|
def send_multiple_keys_command(cmd, method, command, args, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
370
371
|
# This implementation is prioritized performance rather than readability or so.
|
371
|
-
|
372
|
+
case cmd
|
373
|
+
when 'mget'
|
374
|
+
single_key_cmd = 'get'
|
375
|
+
keys_step = 1
|
376
|
+
when 'mset'
|
377
|
+
single_key_cmd = 'set'
|
378
|
+
keys_step = 2
|
379
|
+
when 'del'
|
380
|
+
single_key_cmd = 'del'
|
381
|
+
keys_step = 1
|
382
|
+
else raise NotImplementedError, cmd
|
383
|
+
end
|
372
384
|
|
373
385
|
return try_send(assign_node(command), method, command, args, &block) if command.size <= keys_step + 1 || ::RedisClient::Cluster::KeySlotConverter.hash_tag_included?(command[1])
|
374
386
|
|
@@ -401,7 +413,7 @@ class RedisClient
|
|
401
413
|
def handle_node_reload_error(retry_count: 1)
|
402
414
|
yield
|
403
415
|
rescue ::RedisClient::Cluster::Node::ReloadNeeded
|
404
|
-
raise ::RedisClient::Cluster::NodeMightBeDown if retry_count <= 0
|
416
|
+
raise ::RedisClient::Cluster::NodeMightBeDown.new.with_config(@config) if retry_count <= 0
|
405
417
|
|
406
418
|
retry_count -= 1
|
407
419
|
renew_cluster_state
|
@@ -1,12 +1,14 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'redis_client'
|
4
|
+
require 'redis_client/cluster/errors'
|
5
|
+
require 'redis_client/cluster/noop_command_builder'
|
4
6
|
require 'redis_client/cluster/pipeline'
|
5
7
|
|
6
8
|
class RedisClient
|
7
9
|
class Cluster
|
8
10
|
class Transaction
|
9
|
-
ConsistencyError = Class.new(::RedisClient::Error)
|
11
|
+
ConsistencyError = Class.new(::RedisClient::Cluster::Error)
|
10
12
|
|
11
13
|
MAX_REDIRECTION = 2
|
12
14
|
EMPTY_ARRAY = [].freeze
|
@@ -17,7 +19,7 @@ class RedisClient
|
|
17
19
|
@router = router
|
18
20
|
@command_builder = command_builder
|
19
21
|
@retryable = true
|
20
|
-
@pipeline = ::RedisClient::Pipeline.new(
|
22
|
+
@pipeline = ::RedisClient::Pipeline.new(::RedisClient::Cluster::NoopCommandBuilder)
|
21
23
|
@pending_commands = []
|
22
24
|
@node = node
|
23
25
|
prepare_tx unless @node.nil?
|
@@ -67,7 +69,7 @@ class RedisClient
|
|
67
69
|
@pending_commands.each(&:call)
|
68
70
|
|
69
71
|
return EMPTY_ARRAY if @pipeline._empty?
|
70
|
-
raise ConsistencyError
|
72
|
+
raise ConsistencyError.new("couldn't determine the node: #{@pipeline._commands}").with_config(@router.config) if @node.nil?
|
71
73
|
|
72
74
|
commit
|
73
75
|
end
|
@@ -163,7 +165,7 @@ class RedisClient
|
|
163
165
|
|
164
166
|
def handle_command_error!(err, redirect:) # rubocop:disable Metrics/AbcSize
|
165
167
|
if err.message.start_with?('CROSSSLOT')
|
166
|
-
raise ConsistencyError
|
168
|
+
raise ConsistencyError.new("#{err.message}: #{err.command}").with_config(@router.config)
|
167
169
|
elsif err.message.start_with?('MOVED')
|
168
170
|
node = @router.assign_redirection_node(err.message)
|
169
171
|
send_transaction(node, redirect: redirect - 1)
|
@@ -183,7 +185,7 @@ class RedisClient
|
|
183
185
|
return if slots.size == 1 && @watching_slot.nil?
|
184
186
|
return if slots.size == 1 && @watching_slot == slots.first
|
185
187
|
|
186
|
-
raise(
|
188
|
+
raise ConsistencyError.new("the transaction should be executed to a slot in a node: #{commands}").with_config(@router.config)
|
187
189
|
end
|
188
190
|
|
189
191
|
def try_asking(node)
|
data/lib/redis_client/cluster.rb
CHANGED
@@ -62,30 +62,37 @@ class RedisClient
|
|
62
62
|
end
|
63
63
|
|
64
64
|
def scan(*args, **kwargs, &block)
|
65
|
-
|
65
|
+
return to_enum(__callee__, *args, **kwargs) unless block_given?
|
66
66
|
|
67
|
+
command = @command_builder.generate(['SCAN', ZERO_CURSOR_FOR_SCAN] + args, kwargs)
|
67
68
|
seed = Random.new_seed
|
68
|
-
cursor = ZERO_CURSOR_FOR_SCAN
|
69
69
|
loop do
|
70
|
-
cursor, keys = router.scan(
|
70
|
+
cursor, keys = router.scan(command, seed: seed)
|
71
|
+
command[1] = cursor
|
71
72
|
keys.each(&block)
|
72
73
|
break if cursor == ZERO_CURSOR_FOR_SCAN
|
73
74
|
end
|
74
75
|
end
|
75
76
|
|
76
77
|
def sscan(key, *args, **kwargs, &block)
|
77
|
-
|
78
|
-
|
78
|
+
return to_enum(__callee__, key, *args, **kwargs) unless block_given?
|
79
|
+
|
80
|
+
command = @command_builder.generate(['SSCAN', key, ZERO_CURSOR_FOR_SCAN] + args, kwargs)
|
81
|
+
router.scan_single_key(command, arity: 1, &block)
|
79
82
|
end
|
80
83
|
|
81
84
|
def hscan(key, *args, **kwargs, &block)
|
82
|
-
|
83
|
-
|
85
|
+
return to_enum(__callee__, key, *args, **kwargs) unless block_given?
|
86
|
+
|
87
|
+
command = @command_builder.generate(['HSCAN', key, ZERO_CURSOR_FOR_SCAN] + args, kwargs)
|
88
|
+
router.scan_single_key(command, arity: 2, &block)
|
84
89
|
end
|
85
90
|
|
86
91
|
def zscan(key, *args, **kwargs, &block)
|
87
|
-
|
88
|
-
|
92
|
+
return to_enum(__callee__, key, *args, **kwargs) unless block_given?
|
93
|
+
|
94
|
+
command = @command_builder.generate(['ZSCAN', key, ZERO_CURSOR_FOR_SCAN] + args, kwargs)
|
95
|
+
router.scan_single_key(command, arity: 2, &block)
|
89
96
|
end
|
90
97
|
|
91
98
|
def pipelined(exception: true)
|
@@ -3,7 +3,9 @@
|
|
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'
|
8
|
+
require 'redis_client/cluster/noop_command_builder'
|
7
9
|
require 'redis_client/command_builder'
|
8
10
|
|
9
11
|
class RedisClient
|
@@ -27,10 +29,10 @@ class RedisClient
|
|
27
29
|
:VALID_SCHEMES, :VALID_NODES_KEYS, :MERGE_CONFIG_KEYS, :IGNORE_GENERIC_CONFIG_KEYS,
|
28
30
|
:MAX_WORKERS, :SLOW_COMMAND_TIMEOUT, :MAX_STARTUP_SAMPLE
|
29
31
|
|
30
|
-
InvalidClientConfigError = Class.new(::RedisClient::Error)
|
32
|
+
InvalidClientConfigError = Class.new(::RedisClient::Cluster::Error)
|
31
33
|
|
32
34
|
attr_reader :command_builder, :client_config, :replica_affinity, :slow_command_timeout,
|
33
|
-
:connect_with_original_config, :startup_nodes, :max_startup_sample
|
35
|
+
:connect_with_original_config, :startup_nodes, :max_startup_sample, :id
|
34
36
|
|
35
37
|
def initialize( # rubocop:disable Metrics/ParameterLists
|
36
38
|
nodes: DEFAULT_NODES,
|
@@ -59,10 +61,11 @@ class RedisClient
|
|
59
61
|
@client_implementation = client_implementation
|
60
62
|
@slow_command_timeout = slow_command_timeout
|
61
63
|
@max_startup_sample = max_startup_sample
|
64
|
+
@id = client_config[:id]
|
62
65
|
end
|
63
66
|
|
64
67
|
def inspect
|
65
|
-
"#<#{self.class.name} #{startup_nodes.values}>"
|
68
|
+
"#<#{self.class.name} #{startup_nodes.values.map { |v| v.reject { |k| k == :command_builder } }}>"
|
66
69
|
end
|
67
70
|
|
68
71
|
def read_timeout
|
@@ -92,6 +95,18 @@ class RedisClient
|
|
92
95
|
augment_client_config(config)
|
93
96
|
end
|
94
97
|
|
98
|
+
def resolved?
|
99
|
+
true
|
100
|
+
end
|
101
|
+
|
102
|
+
def sentinel?
|
103
|
+
false
|
104
|
+
end
|
105
|
+
|
106
|
+
def server_url
|
107
|
+
nil
|
108
|
+
end
|
109
|
+
|
95
110
|
private
|
96
111
|
|
97
112
|
def merge_concurrency_option(option)
|
@@ -173,6 +188,7 @@ class RedisClient
|
|
173
188
|
def augment_client_config(config)
|
174
189
|
config = @client_config.merge(config)
|
175
190
|
config = config.merge(host: @fixed_hostname) unless @fixed_hostname.empty?
|
191
|
+
config[:command_builder] = ::RedisClient::Cluster::NoopCommandBuilder # prevent twice call
|
176
192
|
config
|
177
193
|
end
|
178
194
|
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.
|
4
|
+
version: 0.12.1
|
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-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis-client
|
@@ -48,6 +48,7 @@ files:
|
|
48
48
|
- lib/redis_client/cluster/node/random_replica.rb
|
49
49
|
- lib/redis_client/cluster/node/random_replica_or_primary.rb
|
50
50
|
- lib/redis_client/cluster/node_key.rb
|
51
|
+
- lib/redis_client/cluster/noop_command_builder.rb
|
51
52
|
- lib/redis_client/cluster/normalized_cmd_name.rb
|
52
53
|
- lib/redis_client/cluster/optimistic_locking.rb
|
53
54
|
- lib/redis_client/cluster/pipeline.rb
|
@@ -77,7 +78,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
77
78
|
- !ruby/object:Gem::Version
|
78
79
|
version: '0'
|
79
80
|
requirements: []
|
80
|
-
rubygems_version: 3.5.
|
81
|
+
rubygems_version: 3.5.22
|
81
82
|
signing_key:
|
82
83
|
specification_version: 4
|
83
84
|
summary: A Redis cluster client for Ruby
|