redis-cluster-client 0.16.1 → 0.16.3
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 +48 -48
- data/lib/redis_client/cluster/concurrent_worker/none.rb +48 -8
- data/lib/redis_client/cluster/concurrent_worker.rb +1 -1
- data/lib/redis_client/cluster/node/base_topology.rb +6 -2
- data/lib/redis_client/cluster/node/latency_replica.rb +1 -2
- data/lib/redis_client/cluster/node/primary_only.rb +1 -2
- data/lib/redis_client/cluster/node/random_replica.rb +5 -5
- data/lib/redis_client/cluster/node/random_replica_or_primary.rb +3 -4
- data/lib/redis_client/cluster/node.rb +5 -3
- data/lib/redis_client/cluster/node_key.rb +19 -1
- data/lib/redis_client/cluster/optimistic_locking.rb +1 -1
- data/lib/redis_client/cluster/pipeline.rb +3 -3
- data/lib/redis_client/cluster/pub_sub.rb +51 -4
- data/lib/redis_client/cluster/router.rb +17 -8
- data/lib/redis_client/cluster.rb +1 -2
- data/lib/redis_client/cluster_config.rb +15 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 5c16432e083671229128632c116401dc9b1bd56f6b3b1e3c8f107956901d29f2
|
|
4
|
+
data.tar.gz: b464a6f87e8c952a5e4f5b22d0609a9be09b3c6c7817eb91ed779e0b142a4290
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 0b94204a193f0bfe56c75da819651c8c16c72bd9620c51996f2dcfc8b2c7faefc416c342ca7631772c96d4fec7a9e7c8aede9638c28fb540855603b829a1042d
|
|
7
|
+
data.tar.gz: 6d873858848aa00124a043efce6ee8fdd629516e8c5447802b079636de1dd4782b0584c84d791ed0bc7c589d66c273e985878cdea238054e60f1a5a5a2ce72c9
|
|
@@ -9,18 +9,57 @@ class RedisClient
|
|
|
9
9
|
class Command
|
|
10
10
|
EMPTY_STRING = ''
|
|
11
11
|
EMPTY_HASH = {}.freeze
|
|
12
|
-
EMPTY_ARRAY = [].freeze
|
|
13
12
|
|
|
14
|
-
private_constant :
|
|
13
|
+
private_constant :EMPTY_HASH
|
|
15
14
|
|
|
16
|
-
|
|
15
|
+
Spec = Struct.new(
|
|
17
16
|
'RedisCommand',
|
|
18
17
|
:first_key_position,
|
|
19
18
|
:key_step,
|
|
20
19
|
:write?,
|
|
21
20
|
:readonly?,
|
|
22
21
|
keyword_init: true
|
|
23
|
-
)
|
|
22
|
+
) do
|
|
23
|
+
def extract_first_key(command)
|
|
24
|
+
i = first_key_position.to_i
|
|
25
|
+
return command[i] if i > 0
|
|
26
|
+
|
|
27
|
+
i = determine_first_key_position(command)
|
|
28
|
+
return ::RedisClient::Cluster::Command::EMPTY_STRING if i == 0
|
|
29
|
+
|
|
30
|
+
command[i]
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def should_send_to_primary?
|
|
34
|
+
write?
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def should_send_to_replica?
|
|
38
|
+
readonly?
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
private
|
|
42
|
+
|
|
43
|
+
def determine_first_key_position(command) # rubocop:disable Metrics/AbcSize
|
|
44
|
+
cmd_name = command.first
|
|
45
|
+
if cmd_name.casecmp('xread').zero?
|
|
46
|
+
determine_optional_key_position(command, 'streams')
|
|
47
|
+
elsif cmd_name.casecmp('xreadgroup').zero?
|
|
48
|
+
determine_optional_key_position(command, 'streams')
|
|
49
|
+
elsif cmd_name.casecmp('migrate').zero?
|
|
50
|
+
command[3].empty? ? determine_optional_key_position(command, 'keys') : 3
|
|
51
|
+
elsif cmd_name.casecmp('memory').zero?
|
|
52
|
+
command[1].to_s.casecmp('usage').zero? ? 2 : 0
|
|
53
|
+
else
|
|
54
|
+
0
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def determine_optional_key_position(command, option_name)
|
|
59
|
+
i = command.index { |v| v.to_s.casecmp(option_name).zero? }
|
|
60
|
+
i.nil? ? 0 : i + 1
|
|
61
|
+
end
|
|
62
|
+
end
|
|
24
63
|
|
|
25
64
|
class << self
|
|
26
65
|
def load(nodes, slow_command_timeout: -1) # rubocop:disable Metrics/AbcSize
|
|
@@ -30,13 +69,14 @@ class RedisClient
|
|
|
30
69
|
regular_timeout = node.read_timeout
|
|
31
70
|
node.read_timeout = slow_command_timeout > 0.0 ? slow_command_timeout : regular_timeout
|
|
32
71
|
reply = node.call('command')
|
|
33
|
-
node.read_timeout = regular_timeout
|
|
34
72
|
commands = parse_command_reply(reply)
|
|
35
73
|
cmd = ::RedisClient::Cluster::Command.new(commands)
|
|
36
74
|
break
|
|
37
75
|
rescue ::RedisClient::Error => e
|
|
38
76
|
errors ||= []
|
|
39
77
|
errors << e
|
|
78
|
+
ensure
|
|
79
|
+
node.read_timeout = regular_timeout
|
|
40
80
|
end
|
|
41
81
|
|
|
42
82
|
return cmd unless cmd.nil?
|
|
@@ -64,7 +104,7 @@ class RedisClient
|
|
|
64
104
|
else row[2].include?('write')
|
|
65
105
|
end
|
|
66
106
|
|
|
67
|
-
acc[row.first] = ::RedisClient::Cluster::Command::
|
|
107
|
+
acc[row.first] = ::RedisClient::Cluster::Command::Spec.new(
|
|
68
108
|
first_key_position: pos,
|
|
69
109
|
key_step: row[5],
|
|
70
110
|
write?: writable,
|
|
@@ -78,53 +118,13 @@ class RedisClient
|
|
|
78
118
|
@commands = commands || EMPTY_HASH
|
|
79
119
|
end
|
|
80
120
|
|
|
81
|
-
def
|
|
82
|
-
|
|
83
|
-
return EMPTY_STRING if i == 0
|
|
84
|
-
|
|
85
|
-
command[i]
|
|
86
|
-
end
|
|
87
|
-
|
|
88
|
-
def should_send_to_primary?(command)
|
|
89
|
-
find_command_info(command.first)&.write?
|
|
90
|
-
end
|
|
91
|
-
|
|
92
|
-
def should_send_to_replica?(command)
|
|
93
|
-
find_command_info(command.first)&.readonly?
|
|
121
|
+
def get_spec(name)
|
|
122
|
+
@commands[name] || @commands[name.to_s.downcase(:ascii)]
|
|
94
123
|
end
|
|
95
124
|
|
|
96
125
|
def exists?(name)
|
|
97
126
|
@commands.key?(name) || @commands.key?(name.to_s.downcase(:ascii))
|
|
98
127
|
end
|
|
99
|
-
|
|
100
|
-
private
|
|
101
|
-
|
|
102
|
-
def find_command_info(name)
|
|
103
|
-
@commands[name] || @commands[name.to_s.downcase(:ascii)]
|
|
104
|
-
end
|
|
105
|
-
|
|
106
|
-
def determine_first_key_position(command) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/PerceivedComplexity
|
|
107
|
-
i = find_command_info(command.first)&.first_key_position.to_i
|
|
108
|
-
return i if i > 0
|
|
109
|
-
|
|
110
|
-
cmd_name = command.first
|
|
111
|
-
if cmd_name.casecmp('xread').zero?
|
|
112
|
-
determine_optional_key_position(command, 'streams')
|
|
113
|
-
elsif cmd_name.casecmp('xreadgroup').zero?
|
|
114
|
-
determine_optional_key_position(command, 'streams')
|
|
115
|
-
elsif cmd_name.casecmp('migrate').zero?
|
|
116
|
-
command[3].empty? ? determine_optional_key_position(command, 'keys') : 3
|
|
117
|
-
elsif cmd_name.casecmp('memory').zero?
|
|
118
|
-
command[1].to_s.casecmp('usage').zero? ? 2 : 0
|
|
119
|
-
else
|
|
120
|
-
i
|
|
121
|
-
end
|
|
122
|
-
end
|
|
123
|
-
|
|
124
|
-
def determine_optional_key_position(command, option_name)
|
|
125
|
-
i = command.index { |v| v.to_s.casecmp(option_name).zero? }
|
|
126
|
-
i.nil? ? 0 : i + 1
|
|
127
|
-
end
|
|
128
128
|
end
|
|
129
129
|
end
|
|
130
130
|
end
|
|
@@ -3,17 +3,57 @@
|
|
|
3
3
|
class RedisClient
|
|
4
4
|
class Cluster
|
|
5
5
|
module ConcurrentWorker
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
size: size
|
|
6
|
+
module None
|
|
7
|
+
class Group
|
|
8
|
+
Task = Struct.new(
|
|
9
|
+
'RedisClusterClientSingleThreadTask',
|
|
10
|
+
:id, :result, keyword_init: true
|
|
12
11
|
)
|
|
12
|
+
|
|
13
|
+
def initialize(size:)
|
|
14
|
+
@tasks = Array.new(size)
|
|
15
|
+
@idx = 0
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def push(id, *args, **kwargs, &block)
|
|
19
|
+
raise InvalidNumberOfTasks, "max size reached: #{@idx}" if @idx == @tasks.size
|
|
20
|
+
|
|
21
|
+
result = exec(*args, **kwargs, &block)
|
|
22
|
+
@tasks[@idx] = Task.new(id: id, result: result)
|
|
23
|
+
@idx += 1
|
|
24
|
+
nil
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def each
|
|
28
|
+
raise InvalidNumberOfTasks, "expected: #{@tasks.size}, actual: #{@idx}" if @idx != @tasks.size
|
|
29
|
+
|
|
30
|
+
@tasks.each { |task| yield(task.id, task.result) }
|
|
31
|
+
nil
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def close
|
|
35
|
+
@idx = 0
|
|
36
|
+
@tasks.clear
|
|
37
|
+
nil
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def inspect
|
|
41
|
+
"#<#{self.class.name} size: #{@idx}, max: #{@tasks.size}>"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def exec(*args, **kwargs, &block)
|
|
47
|
+
block&.call(*args, **kwargs)
|
|
48
|
+
rescue StandardError => e
|
|
49
|
+
e
|
|
50
|
+
end
|
|
13
51
|
end
|
|
14
52
|
|
|
15
|
-
|
|
16
|
-
|
|
53
|
+
module_function
|
|
54
|
+
|
|
55
|
+
def new_group(size:)
|
|
56
|
+
Group.new(size: size)
|
|
17
57
|
end
|
|
18
58
|
|
|
19
59
|
def close; end
|
|
@@ -73,7 +73,7 @@ class RedisClient
|
|
|
73
73
|
|
|
74
74
|
def create(model: :none, size: 5)
|
|
75
75
|
case model
|
|
76
|
-
when :none then ::RedisClient::Cluster::ConcurrentWorker::None
|
|
76
|
+
when :none then ::RedisClient::Cluster::ConcurrentWorker::None
|
|
77
77
|
when :on_demand then ::RedisClient::Cluster::ConcurrentWorker::OnDemand.new(size: size)
|
|
78
78
|
when :pooled then ::RedisClient::Cluster::ConcurrentWorker::Pooled.new(size: size)
|
|
79
79
|
else raise ArgumentError, "unknown model: #{model}"
|
|
@@ -25,8 +25,7 @@ class RedisClient
|
|
|
25
25
|
end
|
|
26
26
|
|
|
27
27
|
def any_primary_node_key(seed: nil)
|
|
28
|
-
random
|
|
29
|
-
@primary_node_keys.sample(random: random)
|
|
28
|
+
@primary_node_keys.sample(random: make_random(seed))
|
|
30
29
|
end
|
|
31
30
|
|
|
32
31
|
def process_topology_update!(replications, options) # rubocop:disable Metrics/AbcSize
|
|
@@ -59,6 +58,11 @@ class RedisClient
|
|
|
59
58
|
@clients[node_key] = client
|
|
60
59
|
end
|
|
61
60
|
end
|
|
61
|
+
|
|
62
|
+
def make_random(seed)
|
|
63
|
+
# OPTIMIZE: Figure out the most elegant way to pin a node during a pipeline or scan.
|
|
64
|
+
seed.nil? ? Random : Random.new(seed)
|
|
65
|
+
end
|
|
62
66
|
end
|
|
63
67
|
end
|
|
64
68
|
end
|
|
@@ -20,8 +20,7 @@ class RedisClient
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def any_replica_node_key(seed: nil)
|
|
23
|
-
random
|
|
24
|
-
@existed_replicas.sample(random: random)&.first || any_primary_node_key(seed: seed)
|
|
23
|
+
@existed_replicas.sample(random: make_random(seed))&.first || any_primary_node_key(seed: seed)
|
|
25
24
|
end
|
|
26
25
|
|
|
27
26
|
def process_topology_update!(replications, options)
|
|
@@ -18,8 +18,7 @@ class RedisClient
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def any_primary_node_key(seed: nil)
|
|
21
|
-
random
|
|
22
|
-
@primary_node_keys.sample(random: random)
|
|
21
|
+
@primary_node_keys.sample(random: make_random(seed))
|
|
23
22
|
end
|
|
24
23
|
|
|
25
24
|
alias any_replica_node_key any_primary_node_key
|
|
@@ -12,7 +12,7 @@ class RedisClient
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def clients_for_scanning(seed: nil)
|
|
15
|
-
random =
|
|
15
|
+
random = make_random(seed)
|
|
16
16
|
keys = @replications.map do |primary_node_key, replica_node_keys|
|
|
17
17
|
replica_node_keys.empty? ? primary_node_key : replica_node_keys.sample(random: random)
|
|
18
18
|
end
|
|
@@ -21,13 +21,13 @@ class RedisClient
|
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def find_node_key_of_replica(primary_node_key, seed: nil)
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
replica_node_keys = @replications.fetch(primary_node_key, EMPTY_ARRAY)
|
|
25
|
+
replica_node_key = replica_node_keys.size <= 1 ? replica_node_keys.first : replica_node_keys.sample(random: make_random(seed))
|
|
26
|
+
replica_node_key || primary_node_key
|
|
26
27
|
end
|
|
27
28
|
|
|
28
29
|
def any_replica_node_key(seed: nil)
|
|
29
|
-
random
|
|
30
|
-
@replica_node_keys.sample(random: random) || any_primary_node_key(seed: seed)
|
|
30
|
+
@replica_node_keys.sample(random: make_random(seed)) || any_primary_node_key(seed: seed)
|
|
31
31
|
end
|
|
32
32
|
end
|
|
33
33
|
end
|
|
@@ -12,7 +12,7 @@ class RedisClient
|
|
|
12
12
|
end
|
|
13
13
|
|
|
14
14
|
def clients_for_scanning(seed: nil)
|
|
15
|
-
random =
|
|
15
|
+
random = make_random(seed)
|
|
16
16
|
keys = @replications.map do |primary_node_key, replica_node_keys|
|
|
17
17
|
decide_use_primary?(random, replica_node_keys.size) ? primary_node_key : replica_node_keys.sample(random: random)
|
|
18
18
|
end
|
|
@@ -21,7 +21,7 @@ class RedisClient
|
|
|
21
21
|
end
|
|
22
22
|
|
|
23
23
|
def find_node_key_of_replica(primary_node_key, seed: nil)
|
|
24
|
-
random =
|
|
24
|
+
random = make_random(seed)
|
|
25
25
|
|
|
26
26
|
replica_node_keys = @replications.fetch(primary_node_key, EMPTY_ARRAY)
|
|
27
27
|
if decide_use_primary?(random, replica_node_keys.size)
|
|
@@ -32,8 +32,7 @@ class RedisClient
|
|
|
32
32
|
end
|
|
33
33
|
|
|
34
34
|
def any_replica_node_key(seed: nil)
|
|
35
|
-
random
|
|
36
|
-
@replica_node_keys.sample(random: random) || any_primary_node_key(seed: seed)
|
|
35
|
+
@replica_node_keys.sample(random: make_random(seed)) || any_primary_node_key(seed: seed)
|
|
37
36
|
end
|
|
38
37
|
|
|
39
38
|
private
|
|
@@ -479,9 +479,11 @@ class RedisClient
|
|
|
479
479
|
def with_reload_jitter
|
|
480
480
|
return unless @next_reload_time.nil? || obtain_current_time >= @next_reload_time
|
|
481
481
|
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
482
|
+
begin
|
|
483
|
+
yield
|
|
484
|
+
ensure
|
|
485
|
+
@next_reload_time = obtain_current_time + @random.rand(JITTER_WINDOW)
|
|
486
|
+
end
|
|
485
487
|
end
|
|
486
488
|
|
|
487
489
|
def with_reload_lock
|
|
@@ -18,12 +18,30 @@ class RedisClient
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def split(node_key)
|
|
21
|
-
|
|
21
|
+
return [node_key, nil] if node_key.nil? || node_key.empty?
|
|
22
|
+
|
|
23
|
+
bracketed = split_bracketed(node_key)
|
|
24
|
+
return bracketed unless bracketed.nil?
|
|
25
|
+
|
|
26
|
+
pos = node_key.rindex(DELIMITER, -1)
|
|
22
27
|
return [node_key, nil] if pos.nil?
|
|
23
28
|
|
|
24
29
|
[node_key[0, pos], node_key[(pos + 1)..]]
|
|
25
30
|
end
|
|
26
31
|
|
|
32
|
+
def split_bracketed(node_key)
|
|
33
|
+
return nil unless node_key.start_with?('[')
|
|
34
|
+
|
|
35
|
+
end_bracket = node_key.index(']')
|
|
36
|
+
return nil if end_bracket.nil?
|
|
37
|
+
|
|
38
|
+
host = node_key[1, end_bracket - 1]
|
|
39
|
+
remainder = node_key[(end_bracket + 1)..]
|
|
40
|
+
port = remainder.start_with?(DELIMITER) ? remainder[1..] : nil
|
|
41
|
+
[host, port]
|
|
42
|
+
end
|
|
43
|
+
private_class_method :split_bracketed
|
|
44
|
+
|
|
27
45
|
def build_from_uri(uri)
|
|
28
46
|
return '' if uri.nil?
|
|
29
47
|
|
|
@@ -54,7 +54,7 @@ class RedisClient
|
|
|
54
54
|
rescue ::RedisClient::ConnectionError
|
|
55
55
|
# Deduct the number of retries that happened _inside_ router#handle_redirection from our remaining
|
|
56
56
|
# _external_ retries. Always deduct at least one in case handle_redirection raises without trying the block.
|
|
57
|
-
retry_count -= [times_block_executed, 1].min
|
|
57
|
+
retry_count -= times_block_executed == 0 ? 1 : [times_block_executed, 1].min
|
|
58
58
|
raise if retry_count < 0
|
|
59
59
|
|
|
60
60
|
retry
|
|
@@ -230,10 +230,10 @@ class RedisClient
|
|
|
230
230
|
|
|
231
231
|
def append_pipeline(node_key)
|
|
232
232
|
@pipelines ||= {}
|
|
233
|
-
@pipelines[node_key] ||= ::RedisClient::Cluster::Pipeline::Extended.new(::RedisClient::Cluster::NoopCommandBuilder)
|
|
234
|
-
|
|
233
|
+
pi = (@pipelines[node_key] ||= ::RedisClient::Cluster::Pipeline::Extended.new(::RedisClient::Cluster::NoopCommandBuilder))
|
|
234
|
+
pi.add_outer_index(@size)
|
|
235
235
|
@size += 1
|
|
236
|
-
|
|
236
|
+
pi
|
|
237
237
|
end
|
|
238
238
|
|
|
239
239
|
def do_pipelining(client, pipeline)
|
|
@@ -61,7 +61,17 @@ class RedisClient
|
|
|
61
61
|
end
|
|
62
62
|
|
|
63
63
|
BUF_SIZE = Integer(ENV.fetch('REDIS_CLIENT_PUBSUB_BUF_SIZE', 1024))
|
|
64
|
-
|
|
64
|
+
RECOVERY_BASE_INTERVAL = Float(ENV.fetch('REDIS_CLIENT_PUBSUB_RECOVERY_INTERVAL_SEC', 1.0))
|
|
65
|
+
RECOVERY_MAX_INTERVAL = Float(ENV.fetch('REDIS_CLIENT_PUBSUB_RECOVERY_MAX_INTERVAL_SEC', 30.0))
|
|
66
|
+
RECOVERY_MAX_ATTEMPTS = Integer(ENV.fetch('REDIS_CLIENT_PUBSUB_RECOVERY_MAX_ATTEMPTS', 10))
|
|
67
|
+
SUBSCRIBE_COMMANDS = %w[subscribe psubscribe ssubscribe].freeze
|
|
68
|
+
UNSUBSCRIBE_TO_SUBSCRIBE = {
|
|
69
|
+
'unsubscribe' => 'subscribe',
|
|
70
|
+
'punsubscribe' => 'psubscribe',
|
|
71
|
+
'sunsubscribe' => 'ssubscribe'
|
|
72
|
+
}.freeze
|
|
73
|
+
private_constant :BUF_SIZE, :RECOVERY_BASE_INTERVAL, :RECOVERY_MAX_INTERVAL, :RECOVERY_MAX_ATTEMPTS,
|
|
74
|
+
:SUBSCRIBE_COMMANDS, :UNSUBSCRIBE_TO_SUBSCRIBE
|
|
65
75
|
|
|
66
76
|
def initialize(router, command_builder)
|
|
67
77
|
@router = router
|
|
@@ -74,14 +84,14 @@ class RedisClient
|
|
|
74
84
|
def call(*args, **kwargs)
|
|
75
85
|
command = @command_builder.generate(args, kwargs)
|
|
76
86
|
_call(command)
|
|
77
|
-
|
|
87
|
+
remember(command)
|
|
78
88
|
nil
|
|
79
89
|
end
|
|
80
90
|
|
|
81
91
|
def call_v(command)
|
|
82
92
|
command = @command_builder.generate(command)
|
|
83
93
|
_call(command)
|
|
84
|
-
|
|
94
|
+
remember(command)
|
|
85
95
|
nil
|
|
86
96
|
end
|
|
87
97
|
|
|
@@ -118,6 +128,35 @@ class RedisClient
|
|
|
118
128
|
|
|
119
129
|
private
|
|
120
130
|
|
|
131
|
+
def remember(command)
|
|
132
|
+
cmd = command.first.to_s.downcase
|
|
133
|
+
if SUBSCRIBE_COMMANDS.include?(cmd)
|
|
134
|
+
@commands << command
|
|
135
|
+
elsif (subscribe_cmd = UNSUBSCRIBE_TO_SUBSCRIBE[cmd])
|
|
136
|
+
forget_subscriptions(subscribe_cmd, command[1..])
|
|
137
|
+
else
|
|
138
|
+
@commands << command
|
|
139
|
+
end
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def forget_subscriptions(subscribe_cmd, channels)
|
|
143
|
+
if channels.nil? || channels.empty?
|
|
144
|
+
@commands.reject! { |c| c.first.to_s.casecmp(subscribe_cmd).zero? }
|
|
145
|
+
else
|
|
146
|
+
@commands.reject! { |c| prune_entry(c, subscribe_cmd, channels) }
|
|
147
|
+
end
|
|
148
|
+
end
|
|
149
|
+
|
|
150
|
+
def prune_entry(entry, subscribe_cmd, channels)
|
|
151
|
+
return false unless entry.first.to_s.casecmp(subscribe_cmd).zero?
|
|
152
|
+
|
|
153
|
+
remaining = entry[1..] - channels
|
|
154
|
+
return true if remaining.empty?
|
|
155
|
+
|
|
156
|
+
entry.replace([entry.first, *remaining])
|
|
157
|
+
false
|
|
158
|
+
end
|
|
159
|
+
|
|
121
160
|
def _call(command) # rubocop:disable Metrics/AbcSize
|
|
122
161
|
if command.first.casecmp('subscribe').zero?
|
|
123
162
|
call_to_single_state(command)
|
|
@@ -179,6 +218,7 @@ class RedisClient
|
|
|
179
218
|
end
|
|
180
219
|
|
|
181
220
|
def start_over
|
|
221
|
+
attempt = 0
|
|
182
222
|
loop do
|
|
183
223
|
@router.renew_cluster_state
|
|
184
224
|
@state_dict.each_value(&:close)
|
|
@@ -186,9 +226,16 @@ class RedisClient
|
|
|
186
226
|
@commands.each { |command| _call(command) }
|
|
187
227
|
break
|
|
188
228
|
rescue ::RedisClient::ConnectionError, ::RedisClient::Cluster::NodeMightBeDown
|
|
189
|
-
|
|
229
|
+
attempt += 1
|
|
230
|
+
raise if attempt >= RECOVERY_MAX_ATTEMPTS
|
|
231
|
+
|
|
232
|
+
sleep recovery_interval(attempt)
|
|
190
233
|
end
|
|
191
234
|
end
|
|
235
|
+
|
|
236
|
+
def recovery_interval(attempt)
|
|
237
|
+
[RECOVERY_BASE_INTERVAL * (2**(attempt - 1)), RECOVERY_MAX_INTERVAL].min
|
|
238
|
+
end
|
|
192
239
|
end
|
|
193
240
|
end
|
|
194
241
|
end
|
|
@@ -246,23 +246,27 @@ class RedisClient
|
|
|
246
246
|
end
|
|
247
247
|
|
|
248
248
|
def find_node_key(command, seed: nil)
|
|
249
|
-
|
|
250
|
-
find_node_key_by_key(
|
|
249
|
+
cmd_spec = @command.get_spec(command.first)
|
|
250
|
+
find_node_key_by_key(
|
|
251
|
+
cmd_spec&.extract_first_key(command),
|
|
252
|
+
seed: seed,
|
|
253
|
+
primary: cmd_spec&.should_send_to_primary?
|
|
254
|
+
)
|
|
251
255
|
end
|
|
252
256
|
|
|
253
257
|
def find_primary_node_key(command)
|
|
254
|
-
key = @command.extract_first_key(command)
|
|
255
|
-
return
|
|
258
|
+
key = @command.get_spec(command.first)&.extract_first_key(command)
|
|
259
|
+
return unless key&.size&.> 0
|
|
256
260
|
|
|
257
261
|
find_node_key_by_key(key, primary: true)
|
|
258
262
|
end
|
|
259
263
|
|
|
260
264
|
def find_slot(command)
|
|
261
|
-
find_slot_by_key(@command.extract_first_key(command))
|
|
265
|
+
find_slot_by_key(@command.get_spec(command.first)&.extract_first_key(command))
|
|
262
266
|
end
|
|
263
267
|
|
|
264
268
|
def find_slot_by_key(key)
|
|
265
|
-
return if key.empty?
|
|
269
|
+
return if key.nil? || key.empty?
|
|
266
270
|
|
|
267
271
|
::RedisClient::Cluster::KeySlotConverter.convert(key)
|
|
268
272
|
end
|
|
@@ -467,8 +471,13 @@ class RedisClient
|
|
|
467
471
|
|
|
468
472
|
return assign_node_and_send_command(method, command, args, &block) if command.size <= keys_step + 1 || ::RedisClient::Cluster::KeySlotConverter.hash_tag_included?(command[1])
|
|
469
473
|
|
|
470
|
-
|
|
471
|
-
|
|
474
|
+
pipeline = ::RedisClient::Cluster::Pipeline.new(
|
|
475
|
+
self,
|
|
476
|
+
@command_builder,
|
|
477
|
+
@concurrent_worker,
|
|
478
|
+
exception: true,
|
|
479
|
+
seed: Random.new_seed
|
|
480
|
+
)
|
|
472
481
|
|
|
473
482
|
single_command = Array.new(keys_step + 1)
|
|
474
483
|
single_command[0] = single_key_cmd
|
data/lib/redis_client/cluster.rb
CHANGED
|
@@ -95,13 +95,12 @@ class RedisClient
|
|
|
95
95
|
end
|
|
96
96
|
|
|
97
97
|
def pipelined(exception: true)
|
|
98
|
-
seed = @config.use_replica? && @config.replica_affinity == :random ? nil : Random.new_seed
|
|
99
98
|
pipeline = ::RedisClient::Cluster::Pipeline.new(
|
|
100
99
|
router,
|
|
101
100
|
@command_builder,
|
|
102
101
|
@concurrent_worker,
|
|
103
102
|
exception: exception,
|
|
104
|
-
seed:
|
|
103
|
+
seed: Random.new_seed
|
|
105
104
|
)
|
|
106
105
|
|
|
107
106
|
yield pipeline
|
|
@@ -32,6 +32,12 @@ class RedisClient
|
|
|
32
32
|
|
|
33
33
|
InvalidClientConfigError = Class.new(::RedisClient::Cluster::Error)
|
|
34
34
|
|
|
35
|
+
SENSITIVE_INSPECT_KEYS = %i[username password].freeze
|
|
36
|
+
INSPECT_REDACTED_KEYS = %i[command_builder].freeze
|
|
37
|
+
INSPECT_PLACEHOLDER = '[FILTERED]'
|
|
38
|
+
|
|
39
|
+
private_constant :SENSITIVE_INSPECT_KEYS, :INSPECT_REDACTED_KEYS, :INSPECT_PLACEHOLDER
|
|
40
|
+
|
|
35
41
|
attr_reader :command_builder, :client_config, :replica_affinity, :slow_command_timeout,
|
|
36
42
|
:connect_with_original_config, :startup_nodes, :max_startup_sample, :id
|
|
37
43
|
|
|
@@ -65,7 +71,7 @@ class RedisClient
|
|
|
65
71
|
end
|
|
66
72
|
|
|
67
73
|
def inspect
|
|
68
|
-
"#<#{self.class.name} #{startup_nodes.values.map { |v| v
|
|
74
|
+
"#<#{self.class.name} #{startup_nodes.values.map { |v| redact_for_inspect(v) }}>"
|
|
69
75
|
end
|
|
70
76
|
|
|
71
77
|
def connect_timeout
|
|
@@ -117,6 +123,14 @@ class RedisClient
|
|
|
117
123
|
|
|
118
124
|
private
|
|
119
125
|
|
|
126
|
+
def redact_for_inspect(node_config)
|
|
127
|
+
node_config.each_with_object({}) do |(key, value), redacted|
|
|
128
|
+
next if INSPECT_REDACTED_KEYS.include?(key)
|
|
129
|
+
|
|
130
|
+
redacted[key] = SENSITIVE_INSPECT_KEYS.include?(key) ? INSPECT_PLACEHOLDER : value
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
|
|
120
134
|
def merge_concurrency_option(option)
|
|
121
135
|
opts = {}
|
|
122
136
|
|