redis-cluster-client 0.11.6 → 0.12.1
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 +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
|