redis-cluster-client 0.7.5 → 0.7.7
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 +53 -17
- data/lib/redis_client/cluster/error_identification.rb +49 -0
- data/lib/redis_client/cluster/key_slot_converter.rb +18 -0
- data/lib/redis_client/cluster/node/base_topology.rb +60 -0
- data/lib/redis_client/cluster/node/latency_replica.rb +13 -17
- data/lib/redis_client/cluster/node/primary_only.rb +7 -19
- data/lib/redis_client/cluster/node/random_replica.rb +2 -4
- data/lib/redis_client/cluster/node/random_replica_or_primary.rb +2 -4
- data/lib/redis_client/cluster/node.rb +169 -109
- data/lib/redis_client/cluster/node_key.rb +4 -0
- data/lib/redis_client/cluster/optimistic_locking.rb +48 -0
- data/lib/redis_client/cluster/pinning_node.rb +35 -0
- data/lib/redis_client/cluster/pipeline.rb +19 -18
- data/lib/redis_client/cluster/router.rb +49 -83
- data/lib/redis_client/cluster/transaction.rb +146 -20
- data/lib/redis_client/cluster.rb +37 -2
- data/lib/redis_client/cluster_config.rb +30 -45
- metadata +7 -4
- data/lib/redis_client/cluster/node/replica_mixin.rb +0 -37
@@ -13,7 +13,6 @@ class RedisClient
|
|
13
13
|
class Cluster
|
14
14
|
class Router
|
15
15
|
ZERO_CURSOR_FOR_SCAN = '0'
|
16
|
-
METHODS_FOR_BLOCKING_CMD = %i[blocking_call_v blocking_call].freeze
|
17
16
|
TSF = ->(f, x) { f.nil? ? x : f.call(x) }.curry
|
18
17
|
|
19
18
|
def initialize(config, concurrent_worker, pool: nil, **kwargs)
|
@@ -23,9 +22,9 @@ class RedisClient
|
|
23
22
|
@concurrent_worker = concurrent_worker
|
24
23
|
@pool = pool
|
25
24
|
@client_kwargs = kwargs
|
26
|
-
@node =
|
25
|
+
@node = ::RedisClient::Cluster::Node.new(concurrent_worker, config: config, pool: pool, **kwargs)
|
26
|
+
update_cluster_info!
|
27
27
|
@command = ::RedisClient::Cluster::Command.load(@node.replica_clients.shuffle, slow_command_timeout: config.slow_command_timeout)
|
28
|
-
@mutex = Mutex.new
|
29
28
|
@command_builder = @config.command_builder
|
30
29
|
end
|
31
30
|
|
@@ -66,77 +65,61 @@ class RedisClient
|
|
66
65
|
raise if e.errors.any?(::RedisClient::CircuitBreaker::OpenCircuitError)
|
67
66
|
|
68
67
|
update_cluster_info! if e.errors.values.any? do |err|
|
69
|
-
err.message.start_with?('CLUSTERDOWN Hash slot not served')
|
68
|
+
@node.owns_error?(err) && err.message.start_with?('CLUSTERDOWN Hash slot not served')
|
70
69
|
end
|
71
70
|
|
72
71
|
raise
|
73
72
|
end
|
74
73
|
|
75
74
|
# @see https://redis.io/docs/reference/cluster-spec/#redirection-and-resharding Redirection and resharding
|
76
|
-
def try_send(node, method, command, args, retry_count: 3, &block)
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
81
|
-
|
75
|
+
def try_send(node, method, command, args, retry_count: 3, &block)
|
76
|
+
handle_redirection(node, retry_count: retry_count) do |on_node|
|
77
|
+
if args.empty?
|
78
|
+
# prevent memory allocation for variable-length args
|
79
|
+
on_node.public_send(method, command, &block)
|
80
|
+
else
|
81
|
+
on_node.public_send(method, *args, command, &block)
|
82
|
+
end
|
82
83
|
end
|
83
|
-
|
84
|
-
raise
|
85
|
-
rescue ::RedisClient::CommandError => e
|
86
|
-
raise if retry_count <= 0
|
84
|
+
end
|
87
85
|
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
retry
|
92
|
-
elsif e.message.start_with?('ASK')
|
93
|
-
node = assign_asking_node(e.message)
|
94
|
-
node.call('ASKING')
|
95
|
-
retry_count -= 1
|
96
|
-
retry
|
97
|
-
elsif e.message.start_with?('CLUSTERDOWN Hash slot not served')
|
98
|
-
update_cluster_info!
|
99
|
-
retry_count -= 1
|
100
|
-
retry
|
101
|
-
else
|
102
|
-
raise
|
86
|
+
def try_delegate(node, method, *args, retry_count: 3, **kwargs, &block)
|
87
|
+
handle_redirection(node, retry_count: retry_count) do |on_node|
|
88
|
+
on_node.public_send(method, *args, **kwargs, &block)
|
103
89
|
end
|
104
|
-
rescue ::RedisClient::ConnectionError => e
|
105
|
-
raise if METHODS_FOR_BLOCKING_CMD.include?(method) && e.is_a?(RedisClient::ReadTimeoutError)
|
106
|
-
raise if retry_count <= 0
|
107
|
-
|
108
|
-
update_cluster_info!
|
109
|
-
retry_count -= 1
|
110
|
-
retry
|
111
90
|
end
|
112
91
|
|
113
|
-
def
|
114
|
-
node
|
92
|
+
def handle_redirection(node, retry_count:) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
93
|
+
yield node
|
115
94
|
rescue ::RedisClient::CircuitBreaker::OpenCircuitError
|
116
95
|
raise
|
117
96
|
rescue ::RedisClient::CommandError => e
|
118
|
-
raise
|
97
|
+
raise unless ErrorIdentification.client_owns_error?(e, node)
|
119
98
|
|
120
99
|
if e.message.start_with?('MOVED')
|
121
100
|
node = assign_redirection_node(e.message)
|
122
101
|
retry_count -= 1
|
123
|
-
retry
|
102
|
+
retry if retry_count >= 0
|
124
103
|
elsif e.message.start_with?('ASK')
|
125
104
|
node = assign_asking_node(e.message)
|
126
|
-
node.call('ASKING')
|
127
105
|
retry_count -= 1
|
128
|
-
|
106
|
+
if retry_count >= 0
|
107
|
+
node.call('ASKING')
|
108
|
+
retry
|
109
|
+
end
|
129
110
|
elsif e.message.start_with?('CLUSTERDOWN Hash slot not served')
|
130
111
|
update_cluster_info!
|
131
112
|
retry_count -= 1
|
132
|
-
retry
|
133
|
-
else
|
134
|
-
raise
|
113
|
+
retry if retry_count >= 0
|
135
114
|
end
|
136
|
-
|
137
|
-
|
115
|
+
raise
|
116
|
+
rescue ::RedisClient::ConnectionError => e
|
117
|
+
raise unless ErrorIdentification.client_owns_error?(e, node)
|
138
118
|
|
139
119
|
update_cluster_info!
|
120
|
+
|
121
|
+
raise if retry_count <= 0
|
122
|
+
|
140
123
|
retry_count -= 1
|
141
124
|
retry
|
142
125
|
end
|
@@ -170,21 +153,30 @@ class RedisClient
|
|
170
153
|
find_node(node_key)
|
171
154
|
end
|
172
155
|
|
173
|
-
def
|
174
|
-
key
|
175
|
-
|
176
|
-
|
177
|
-
if @command.should_send_to_primary?(command)
|
178
|
-
@node.find_node_key_of_primary(slot) || @node.any_primary_node_key(seed: seed)
|
156
|
+
def find_node_key_by_key(key, seed: nil, primary: false)
|
157
|
+
if key && !key.empty?
|
158
|
+
slot = ::RedisClient::Cluster::KeySlotConverter.convert(key)
|
159
|
+
primary ? @node.find_node_key_of_primary(slot) : @node.find_node_key_of_replica(slot)
|
179
160
|
else
|
180
|
-
@node.
|
161
|
+
primary ? @node.any_primary_node_key(seed: seed) : @node.any_replica_node_key(seed: seed)
|
181
162
|
end
|
182
163
|
end
|
183
164
|
|
165
|
+
def find_primary_node_by_slot(slot)
|
166
|
+
node_key = @node.find_node_key_of_primary(slot)
|
167
|
+
find_node(node_key)
|
168
|
+
end
|
169
|
+
|
170
|
+
def find_node_key(command, seed: nil)
|
171
|
+
key = @command.extract_first_key(command)
|
172
|
+
find_node_key_by_key(key, seed: seed, primary: @command.should_send_to_primary?(command))
|
173
|
+
end
|
174
|
+
|
184
175
|
def find_primary_node_key(command)
|
185
176
|
key = @command.extract_first_key(command)
|
186
|
-
|
187
|
-
|
177
|
+
return nil unless key&.size&.> 0
|
178
|
+
|
179
|
+
find_node_key_by_key(key, primary: true)
|
188
180
|
end
|
189
181
|
|
190
182
|
def find_node(node_key, retry_count: 3)
|
@@ -306,34 +298,8 @@ class RedisClient
|
|
306
298
|
end
|
307
299
|
end
|
308
300
|
|
309
|
-
def fetch_cluster_info(config, concurrent_worker, pool: nil, **kwargs)
|
310
|
-
node_info_list = ::RedisClient::Cluster::Node.load_info(config.per_node_key, concurrent_worker, slow_command_timeout: config.slow_command_timeout, **kwargs)
|
311
|
-
node_addrs = node_info_list.map { |i| ::RedisClient::Cluster::NodeKey.hashify(i.node_key) }
|
312
|
-
config.update_node(node_addrs)
|
313
|
-
::RedisClient::Cluster::Node.new(
|
314
|
-
config.per_node_key,
|
315
|
-
concurrent_worker,
|
316
|
-
node_info_list: node_info_list,
|
317
|
-
pool: pool,
|
318
|
-
with_replica: config.use_replica?,
|
319
|
-
replica_affinity: config.replica_affinity,
|
320
|
-
**kwargs
|
321
|
-
)
|
322
|
-
end
|
323
|
-
|
324
301
|
def update_cluster_info!
|
325
|
-
|
326
|
-
|
327
|
-
@mutex.synchronize do
|
328
|
-
begin
|
329
|
-
@node.each(&:close)
|
330
|
-
rescue ::RedisClient::Cluster::ErrorCollection
|
331
|
-
# ignore
|
332
|
-
end
|
333
|
-
|
334
|
-
@config = @original_config.dup if @connect_with_original_config
|
335
|
-
@node = fetch_cluster_info(@config, @concurrent_worker, pool: @pool, **@client_kwargs)
|
336
|
-
end
|
302
|
+
@node.reload!
|
337
303
|
end
|
338
304
|
end
|
339
305
|
end
|
@@ -1,56 +1,182 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'redis_client'
|
4
|
+
require 'redis_client/cluster/pipeline'
|
5
|
+
require 'redis_client/cluster/node_key'
|
4
6
|
|
5
7
|
class RedisClient
|
6
8
|
class Cluster
|
7
9
|
class Transaction
|
8
10
|
ConsistencyError = Class.new(::RedisClient::Error)
|
9
11
|
|
10
|
-
def initialize(router, command_builder)
|
12
|
+
def initialize(router, command_builder, node = nil)
|
11
13
|
@router = router
|
12
14
|
@command_builder = command_builder
|
13
|
-
@
|
15
|
+
@retryable = true
|
16
|
+
@pipeline = ::RedisClient::Pipeline.new(@command_builder)
|
17
|
+
@pending_commands = []
|
18
|
+
@node = node
|
19
|
+
prepare_tx unless @node.nil?
|
14
20
|
end
|
15
21
|
|
16
|
-
def call(*command, **kwargs, &
|
22
|
+
def call(*command, **kwargs, &block)
|
17
23
|
command = @command_builder.generate(command, kwargs)
|
18
|
-
|
24
|
+
if prepare(command)
|
25
|
+
@pipeline.call_v(command, &block)
|
26
|
+
else
|
27
|
+
defer { @pipeline.call_v(command, &block) }
|
28
|
+
end
|
19
29
|
end
|
20
30
|
|
21
|
-
def call_v(command, &
|
31
|
+
def call_v(command, &block)
|
22
32
|
command = @command_builder.generate(command)
|
23
|
-
|
33
|
+
if prepare(command)
|
34
|
+
@pipeline.call_v(command, &block)
|
35
|
+
else
|
36
|
+
defer { @pipeline.call_v(command, &block) }
|
37
|
+
end
|
24
38
|
end
|
25
39
|
|
26
|
-
def call_once(*command, **kwargs, &
|
40
|
+
def call_once(*command, **kwargs, &block)
|
41
|
+
@retryable = false
|
27
42
|
command = @command_builder.generate(command, kwargs)
|
28
|
-
|
43
|
+
if prepare(command)
|
44
|
+
@pipeline.call_once_v(command, &block)
|
45
|
+
else
|
46
|
+
defer { @pipeline.call_once_v(command, &block) }
|
47
|
+
end
|
29
48
|
end
|
30
49
|
|
31
|
-
def call_once_v(command, &
|
50
|
+
def call_once_v(command, &block)
|
51
|
+
@retryable = false
|
32
52
|
command = @command_builder.generate(command)
|
33
|
-
|
53
|
+
if prepare(command)
|
54
|
+
@pipeline.call_once_v(command, &block)
|
55
|
+
else
|
56
|
+
defer { @pipeline.call_once_v(command, &block) }
|
57
|
+
end
|
34
58
|
end
|
35
59
|
|
36
|
-
def execute
|
37
|
-
|
38
|
-
raise ArgumentError, 'empty transaction' if @node_key.nil?
|
60
|
+
def execute
|
61
|
+
@pending_commands.each(&:call)
|
39
62
|
|
40
|
-
|
41
|
-
|
63
|
+
raise ArgumentError, 'empty transaction' if @pipeline._empty?
|
64
|
+
raise ConsistencyError, "couldn't determine the node: #{@pipeline._commands}" if @node.nil?
|
65
|
+
|
66
|
+
settle
|
42
67
|
end
|
43
68
|
|
44
69
|
private
|
45
70
|
|
46
|
-
def
|
71
|
+
def defer(&block)
|
72
|
+
@pending_commands << block
|
73
|
+
nil
|
74
|
+
end
|
75
|
+
|
76
|
+
def prepare(command)
|
77
|
+
return true unless @node.nil?
|
78
|
+
|
47
79
|
node_key = @router.find_primary_node_key(command)
|
48
|
-
|
80
|
+
return false if node_key.nil?
|
49
81
|
|
50
|
-
@
|
51
|
-
|
82
|
+
@node = @router.find_node(node_key)
|
83
|
+
prepare_tx
|
84
|
+
true
|
85
|
+
end
|
52
86
|
|
53
|
-
|
87
|
+
def prepare_tx
|
88
|
+
@pipeline.call('MULTI')
|
89
|
+
@pending_commands.each(&:call)
|
90
|
+
@pending_commands.clear
|
91
|
+
end
|
92
|
+
|
93
|
+
def settle
|
94
|
+
@pipeline.call('EXEC')
|
95
|
+
send_transaction(@node, redirect: true)
|
96
|
+
end
|
97
|
+
|
98
|
+
def send_transaction(client, redirect:)
|
99
|
+
case client
|
100
|
+
when ::RedisClient then send_pipeline(client, redirect: redirect)
|
101
|
+
when ::RedisClient::Pooled then client.with { |c| send_pipeline(c, redirect: redirect) }
|
102
|
+
else raise NotImplementedError, "#{client.class.name}#multi for cluster client"
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def send_pipeline(client, redirect:)
|
107
|
+
replies = client.ensure_connected_cluster_scoped(retryable: @retryable) do |connection|
|
108
|
+
commands = @pipeline._commands
|
109
|
+
client.middlewares.call_pipelined(commands, client.config) do
|
110
|
+
connection.call_pipelined(commands, nil)
|
111
|
+
rescue ::RedisClient::CommandError => e
|
112
|
+
return handle_command_error!(commands, e) if redirect
|
113
|
+
|
114
|
+
raise
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
return if replies.last.nil?
|
119
|
+
|
120
|
+
coerce_results!(replies.last)
|
121
|
+
end
|
122
|
+
|
123
|
+
def coerce_results!(results, offset: 1)
|
124
|
+
results.each_with_index do |result, index|
|
125
|
+
if result.is_a?(::RedisClient::CommandError)
|
126
|
+
result._set_command(@pipeline._commands[index + offset])
|
127
|
+
raise result
|
128
|
+
end
|
129
|
+
|
130
|
+
next if @pipeline._blocks.nil?
|
131
|
+
|
132
|
+
block = @pipeline._blocks[index + offset]
|
133
|
+
next if block.nil?
|
134
|
+
|
135
|
+
results[index] = block.call(result)
|
136
|
+
end
|
137
|
+
|
138
|
+
results
|
139
|
+
end
|
140
|
+
|
141
|
+
def handle_command_error!(commands, err)
|
142
|
+
if err.message.start_with?('CROSSSLOT')
|
143
|
+
raise ConsistencyError, "#{err.message}: #{err.command}"
|
144
|
+
elsif err.message.start_with?('MOVED', 'ASK')
|
145
|
+
ensure_the_same_node!(commands)
|
146
|
+
handle_redirection(err)
|
147
|
+
else
|
148
|
+
raise err
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
def ensure_the_same_node!(commands)
|
153
|
+
expected_node_key = NodeKey.build_from_client(@node)
|
154
|
+
|
155
|
+
commands.each do |command|
|
156
|
+
node_key = @router.find_primary_node_key(command)
|
157
|
+
next if node_key.nil?
|
158
|
+
next if node_key == expected_node_key
|
159
|
+
|
160
|
+
raise ConsistencyError, "the transaction should be executed to a slot in a node: #{commands}"
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def handle_redirection(err)
|
165
|
+
if err.message.start_with?('MOVED')
|
166
|
+
node = @router.assign_redirection_node(err.message)
|
167
|
+
send_transaction(node, redirect: false)
|
168
|
+
elsif err.message.start_with?('ASK')
|
169
|
+
node = @router.assign_asking_node(err.message)
|
170
|
+
try_asking(node) ? send_transaction(node, redirect: false) : err
|
171
|
+
else
|
172
|
+
raise err
|
173
|
+
end
|
174
|
+
end
|
175
|
+
|
176
|
+
def try_asking(node)
|
177
|
+
node.call('ASKING') == 'OK'
|
178
|
+
rescue StandardError
|
179
|
+
false
|
54
180
|
end
|
55
181
|
end
|
56
182
|
end
|
data/lib/redis_client/cluster.rb
CHANGED
@@ -5,6 +5,8 @@ require 'redis_client/cluster/pipeline'
|
|
5
5
|
require 'redis_client/cluster/pub_sub'
|
6
6
|
require 'redis_client/cluster/router'
|
7
7
|
require 'redis_client/cluster/transaction'
|
8
|
+
require 'redis_client/cluster/pinning_node'
|
9
|
+
require 'redis_client/cluster/optimistic_locking'
|
8
10
|
|
9
11
|
class RedisClient
|
10
12
|
class Cluster
|
@@ -89,14 +91,34 @@ class RedisClient
|
|
89
91
|
pipeline.execute
|
90
92
|
end
|
91
93
|
|
92
|
-
def multi(watch: nil
|
93
|
-
|
94
|
+
def multi(watch: nil)
|
95
|
+
if watch.nil? || watch.empty?
|
96
|
+
transaction = ::RedisClient::Cluster::Transaction.new(@router, @command_builder)
|
97
|
+
yield transaction
|
98
|
+
transaction.execute
|
99
|
+
else
|
100
|
+
locking = ::RedisClient::Cluster::OptimisticLocking.new(watch, @router)
|
101
|
+
locking.watch do |c|
|
102
|
+
transaction = ::RedisClient::Cluster::Transaction.new(@router, @command_builder, c)
|
103
|
+
yield transaction
|
104
|
+
transaction.execute
|
105
|
+
end
|
106
|
+
end
|
94
107
|
end
|
95
108
|
|
96
109
|
def pubsub
|
97
110
|
::RedisClient::Cluster::PubSub.new(@router, @command_builder)
|
98
111
|
end
|
99
112
|
|
113
|
+
# TODO: This isn't an official public interface yet. Don't use in your production environment.
|
114
|
+
# @see https://github.com/redis-rb/redis-cluster-client/issues/299
|
115
|
+
def with(key: nil, hashtag: nil, write: true)
|
116
|
+
key = process_with_arguments(key, hashtag)
|
117
|
+
node_key = @router.find_node_key_by_key(key, primary: write)
|
118
|
+
node = @router.find_node(node_key)
|
119
|
+
node.with { |c| yield ::RedisClient::Cluster::PinningNode.new(c) }
|
120
|
+
end
|
121
|
+
|
100
122
|
def close
|
101
123
|
@concurrent_worker.close
|
102
124
|
@router.close
|
@@ -105,6 +127,19 @@ class RedisClient
|
|
105
127
|
|
106
128
|
private
|
107
129
|
|
130
|
+
def process_with_arguments(key, hashtag) # rubocop:disable Metrics/CyclomaticComplexity
|
131
|
+
raise ArgumentError, 'Only one of key or hashtag may be provided' if key && hashtag
|
132
|
+
|
133
|
+
if hashtag
|
134
|
+
# The documentation says not to wrap your hashtag in {}, but people will probably
|
135
|
+
# do it anyway and it's easy for us to fix here.
|
136
|
+
key = hashtag&.match?(/^{.*}$/) ? hashtag : "{#{hashtag}}"
|
137
|
+
end
|
138
|
+
raise ArgumentError, 'One of key or hashtag must be provided' if key.nil? || key.empty?
|
139
|
+
|
140
|
+
key
|
141
|
+
end
|
142
|
+
|
108
143
|
def method_missing(name, *args, **kwargs, &block)
|
109
144
|
if @router.command_exists?(name)
|
110
145
|
args.unshift(name)
|
@@ -23,9 +23,10 @@ class RedisClient
|
|
23
23
|
|
24
24
|
InvalidClientConfigError = Class.new(::RedisClient::Error)
|
25
25
|
|
26
|
-
attr_reader :command_builder, :client_config, :replica_affinity, :slow_command_timeout,
|
26
|
+
attr_reader :command_builder, :client_config, :replica_affinity, :slow_command_timeout,
|
27
|
+
:connect_with_original_config, :startup_nodes
|
27
28
|
|
28
|
-
def initialize(
|
29
|
+
def initialize(
|
29
30
|
nodes: DEFAULT_NODES,
|
30
31
|
replica: false,
|
31
32
|
replica_affinity: :random,
|
@@ -34,39 +35,26 @@ class RedisClient
|
|
34
35
|
connect_with_original_config: false,
|
35
36
|
client_implementation: ::RedisClient::Cluster, # for redis gem
|
36
37
|
slow_command_timeout: SLOW_COMMAND_TIMEOUT,
|
38
|
+
command_builder: ::RedisClient::CommandBuilder,
|
37
39
|
**client_config
|
38
40
|
)
|
39
41
|
|
40
42
|
@replica = true & replica
|
41
43
|
@replica_affinity = replica_affinity.to_s.to_sym
|
42
44
|
@fixed_hostname = fixed_hostname.to_s
|
43
|
-
@
|
44
|
-
|
45
|
-
@
|
46
|
-
|
45
|
+
@command_builder = command_builder
|
46
|
+
node_configs = build_node_configs(nodes.dup)
|
47
|
+
@client_config = merge_generic_config(client_config, node_configs)
|
48
|
+
# Keep tabs on the original startup nodes we were constructed with
|
49
|
+
@startup_nodes = build_startup_nodes(node_configs)
|
47
50
|
@concurrency = merge_concurrency_option(concurrency)
|
48
51
|
@connect_with_original_config = connect_with_original_config
|
49
52
|
@client_implementation = client_implementation
|
50
53
|
@slow_command_timeout = slow_command_timeout
|
51
|
-
@mutex = Mutex.new
|
52
|
-
end
|
53
|
-
|
54
|
-
def dup
|
55
|
-
self.class.new(
|
56
|
-
nodes: @node_configs,
|
57
|
-
replica: @replica,
|
58
|
-
replica_affinity: @replica_affinity,
|
59
|
-
fixed_hostname: @fixed_hostname,
|
60
|
-
concurrency: @concurrency,
|
61
|
-
connect_with_original_config: @connect_with_original_config,
|
62
|
-
client_implementation: @client_implementation,
|
63
|
-
slow_command_timeout: @slow_command_timeout,
|
64
|
-
**@client_config
|
65
|
-
)
|
66
54
|
end
|
67
55
|
|
68
56
|
def inspect
|
69
|
-
"#<#{self.class.name} #{
|
57
|
+
"#<#{self.class.name} #{startup_nodes.values}>"
|
70
58
|
end
|
71
59
|
|
72
60
|
def read_timeout
|
@@ -86,29 +74,14 @@ class RedisClient
|
|
86
74
|
@client_implementation.new(self, concurrency: @concurrency, **kwargs)
|
87
75
|
end
|
88
76
|
|
89
|
-
def per_node_key
|
90
|
-
@node_configs.to_h do |config|
|
91
|
-
node_key = ::RedisClient::Cluster::NodeKey.build_from_host_port(config[:host], config[:port])
|
92
|
-
config = @client_config.merge(config)
|
93
|
-
config = config.merge(host: @fixed_hostname) unless @fixed_hostname.empty?
|
94
|
-
[node_key, config]
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
77
|
def use_replica?
|
99
78
|
@replica
|
100
79
|
end
|
101
80
|
|
102
|
-
def
|
103
|
-
|
104
|
-
|
105
|
-
|
106
|
-
end
|
107
|
-
|
108
|
-
def add_node(host, port)
|
109
|
-
return if @mutex.locked?
|
110
|
-
|
111
|
-
@mutex.synchronize { @node_configs << { host: host, port: port } }
|
81
|
+
def client_config_for_node(node_key)
|
82
|
+
config = ::RedisClient::Cluster::NodeKey.hashify(node_key)
|
83
|
+
config[:port] = ensure_integer(config[:port])
|
84
|
+
augment_client_config(config)
|
112
85
|
end
|
113
86
|
|
114
87
|
private
|
@@ -176,11 +149,23 @@ class RedisClient
|
|
176
149
|
end
|
177
150
|
|
178
151
|
def merge_generic_config(client_config, node_configs)
|
179
|
-
|
152
|
+
cfg = node_configs.first || {}
|
153
|
+
client_config.reject { |k, _| IGNORE_GENERIC_CONFIG_KEYS.include?(k) }
|
154
|
+
.merge(cfg.slice(*MERGE_CONFIG_KEYS))
|
155
|
+
end
|
156
|
+
|
157
|
+
def build_startup_nodes(configs)
|
158
|
+
configs.to_h do |config|
|
159
|
+
node_key = ::RedisClient::Cluster::NodeKey.build_from_host_port(config[:host], config[:port])
|
160
|
+
config = augment_client_config(config)
|
161
|
+
[node_key, config]
|
162
|
+
end
|
163
|
+
end
|
180
164
|
|
181
|
-
|
182
|
-
|
183
|
-
|
165
|
+
def augment_client_config(config)
|
166
|
+
config = @client_config.merge(config)
|
167
|
+
config = config.merge(host: @fixed_hostname) unless @fixed_hostname.empty?
|
168
|
+
config
|
184
169
|
end
|
185
170
|
end
|
186
171
|
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.7.
|
4
|
+
version: 0.7.7
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Taishi Kasuga
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date:
|
11
|
+
date: 2024-02-18 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis-client
|
@@ -38,16 +38,19 @@ files:
|
|
38
38
|
- lib/redis_client/cluster/concurrent_worker/none.rb
|
39
39
|
- lib/redis_client/cluster/concurrent_worker/on_demand.rb
|
40
40
|
- lib/redis_client/cluster/concurrent_worker/pooled.rb
|
41
|
+
- lib/redis_client/cluster/error_identification.rb
|
41
42
|
- lib/redis_client/cluster/errors.rb
|
42
43
|
- lib/redis_client/cluster/key_slot_converter.rb
|
43
44
|
- lib/redis_client/cluster/node.rb
|
45
|
+
- lib/redis_client/cluster/node/base_topology.rb
|
44
46
|
- lib/redis_client/cluster/node/latency_replica.rb
|
45
47
|
- lib/redis_client/cluster/node/primary_only.rb
|
46
48
|
- lib/redis_client/cluster/node/random_replica.rb
|
47
49
|
- lib/redis_client/cluster/node/random_replica_or_primary.rb
|
48
|
-
- lib/redis_client/cluster/node/replica_mixin.rb
|
49
50
|
- lib/redis_client/cluster/node_key.rb
|
50
51
|
- lib/redis_client/cluster/normalized_cmd_name.rb
|
52
|
+
- lib/redis_client/cluster/optimistic_locking.rb
|
53
|
+
- lib/redis_client/cluster/pinning_node.rb
|
51
54
|
- lib/redis_client/cluster/pipeline.rb
|
52
55
|
- lib/redis_client/cluster/pub_sub.rb
|
53
56
|
- lib/redis_client/cluster/router.rb
|
@@ -75,7 +78,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
75
78
|
- !ruby/object:Gem::Version
|
76
79
|
version: '0'
|
77
80
|
requirements: []
|
78
|
-
rubygems_version: 3.
|
81
|
+
rubygems_version: 3.5.3
|
79
82
|
signing_key:
|
80
83
|
specification_version: 4
|
81
84
|
summary: A Redis cluster client for Ruby
|
@@ -1,37 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class RedisClient
|
4
|
-
class Cluster
|
5
|
-
class Node
|
6
|
-
module ReplicaMixin
|
7
|
-
attr_reader :clients, :primary_clients
|
8
|
-
|
9
|
-
EMPTY_ARRAY = [].freeze
|
10
|
-
|
11
|
-
def initialize(replications, options, pool, _concurrent_worker, **kwargs)
|
12
|
-
@replications = replications
|
13
|
-
@primary_node_keys = @replications.keys.sort
|
14
|
-
@replica_node_keys = @replications.values.flatten.sort
|
15
|
-
@clients = build_clients(@primary_node_keys, options, pool, **kwargs)
|
16
|
-
@primary_clients = @clients.select { |k, _| @primary_node_keys.include?(k) }
|
17
|
-
end
|
18
|
-
|
19
|
-
def any_primary_node_key(seed: nil)
|
20
|
-
random = seed.nil? ? Random : Random.new(seed)
|
21
|
-
@primary_node_keys.sample(random: random)
|
22
|
-
end
|
23
|
-
|
24
|
-
private
|
25
|
-
|
26
|
-
def build_clients(primary_node_keys, options, pool, **kwargs)
|
27
|
-
options.to_h do |node_key, option|
|
28
|
-
option = option.merge(kwargs.reject { |k, _| ::RedisClient::Cluster::Node::IGNORE_GENERIC_CONFIG_KEYS.include?(k) })
|
29
|
-
config = ::RedisClient::Cluster::Node::Config.new(scale_read: !primary_node_keys.include?(node_key), **option)
|
30
|
-
client = pool.nil? ? config.new_client : config.new_pool(**pool)
|
31
|
-
[node_key, client]
|
32
|
-
end
|
33
|
-
end
|
34
|
-
end
|
35
|
-
end
|
36
|
-
end
|
37
|
-
end
|