redis-cluster-client 0.0.9 → 0.0.12
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 +12 -6
- data/lib/redis_client/cluster/errors.rb +6 -12
- data/lib/redis_client/cluster/node.rb +4 -5
- data/lib/redis_client/cluster/pipeline.rb +72 -0
- data/lib/redis_client/cluster/pub_sub.rb +27 -0
- data/lib/redis_client/cluster/router.rb +266 -0
- data/lib/redis_client/cluster.rb +18 -346
- metadata +5 -2
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 2aec24d7a41cc222cf5fdec13b063d0ce2a68df59c05049a4719f71b6ac07391
|
4
|
+
data.tar.gz: 00b0eebd7da92305fbf2512f660721ad0976cfb1e33fb72037a673fe65899ce1
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 28d31d104bd1f2100c0f61c765783e86fea745910c748510bdc7b4985f779df4e8d8240a38afc043504cc36ad33a00273288762defcdcd03e7486cece5351882
|
7
|
+
data.tar.gz: 82a6c2b2e786f27183cc000ca202e4d6bf29b6260dc00a2659eb18660e6e4b3d60946d170606938242dc9340b4ae644aaa15de7369c23e2f9cda0c2bab604fe8
|
@@ -7,15 +7,21 @@ class RedisClient
|
|
7
7
|
class Cluster
|
8
8
|
class Command
|
9
9
|
class << self
|
10
|
-
def load(nodes)
|
11
|
-
errors =
|
10
|
+
def load(nodes) # rubocop:disable Metrics/MethodLength
|
11
|
+
errors = []
|
12
|
+
cmd = nil
|
13
|
+
nodes&.each do |node|
|
14
|
+
break unless cmd.nil?
|
15
|
+
|
12
16
|
reply = node.call('COMMAND')
|
13
17
|
details = parse_command_details(reply)
|
14
|
-
|
15
|
-
rescue ::RedisClient::
|
16
|
-
e
|
18
|
+
cmd = ::RedisClient::Cluster::Command.new(details)
|
19
|
+
rescue ::RedisClient::Error => e
|
20
|
+
errors << e
|
17
21
|
end
|
18
22
|
|
23
|
+
return cmd unless cmd.nil?
|
24
|
+
|
19
25
|
raise ::RedisClient::Cluster::InitialSetupError, errors
|
20
26
|
end
|
21
27
|
|
@@ -75,7 +81,7 @@ class RedisClient
|
|
75
81
|
when 'memory'
|
76
82
|
command[1].to_s.casecmp('usage').zero? ? 2 : 0
|
77
83
|
when 'migrate'
|
78
|
-
command[3]
|
84
|
+
command[3].empty? ? determine_optional_key_position(command, 'keys') : 3
|
79
85
|
when 'xread', 'xreadgroup'
|
80
86
|
determine_optional_key_position(command, 'streams')
|
81
87
|
else
|
@@ -6,8 +6,6 @@ class RedisClient
|
|
6
6
|
class Cluster
|
7
7
|
ERR_ARG_NORMALIZATION = ->(arg) { Array[arg].flatten.reject { |e| e.nil? || (e.respond_to?(:empty?) && e.empty?) } }
|
8
8
|
|
9
|
-
# Raised when client connected to redis as cluster mode
|
10
|
-
# and failed to fetch cluster state information by commands.
|
11
9
|
class InitialSetupError < ::RedisClient::Error
|
12
10
|
def initialize(errors)
|
13
11
|
msg = ERR_ARG_NORMALIZATION.call(errors).map(&:message).uniq.join(',')
|
@@ -15,20 +13,17 @@ class RedisClient
|
|
15
13
|
end
|
16
14
|
end
|
17
15
|
|
18
|
-
# Raised when client connected to redis as cluster mode
|
19
|
-
# and some cluster subcommands were called.
|
20
16
|
class OrchestrationCommandNotSupported < ::RedisClient::Error
|
21
17
|
def initialize(command)
|
22
18
|
str = ERR_ARG_NORMALIZATION.call(command).map(&:to_s).join(' ').upcase
|
23
|
-
msg = "#{str} command should be used with care "\
|
24
|
-
'only by applications orchestrating Redis Cluster, like redis-cli, '\
|
25
|
-
'and the command if used out of the right context can leave the cluster '\
|
19
|
+
msg = "#{str} command should be used with care " \
|
20
|
+
'only by applications orchestrating Redis Cluster, like redis-cli, ' \
|
21
|
+
'and the command if used out of the right context can leave the cluster ' \
|
26
22
|
'in a wrong state or cause data loss.'
|
27
23
|
super(msg)
|
28
24
|
end
|
29
25
|
end
|
30
26
|
|
31
|
-
# Raised when error occurs on any node of cluster.
|
32
27
|
class ErrorCollection < ::RedisClient::Error
|
33
28
|
attr_reader :errors
|
34
29
|
|
@@ -41,11 +36,10 @@ class RedisClient
|
|
41
36
|
|
42
37
|
@errors = errors
|
43
38
|
messages = @errors.map { |node_key, error| "#{node_key}: #{error.message}" }
|
44
|
-
super("
|
39
|
+
super("Errors occurred on any node: #{messages.join(', ')}")
|
45
40
|
end
|
46
41
|
end
|
47
42
|
|
48
|
-
# Raised when cluster client can't select node.
|
49
43
|
class AmbiguousNodeError < ::RedisClient::Error
|
50
44
|
def initialize(command)
|
51
45
|
super("Cluster client doesn't know which node the #{command} command should be sent to.")
|
@@ -55,8 +49,8 @@ class RedisClient
|
|
55
49
|
class NodeMightBeDown < ::RedisClient::Error
|
56
50
|
def initialize(_ = '')
|
57
51
|
super(
|
58
|
-
'The client is trying to fetch the latest cluster state '\
|
59
|
-
'because a subset of nodes might be down. '\
|
52
|
+
'The client is trying to fetch the latest cluster state ' \
|
53
|
+
'because a subset of nodes might be down. ' \
|
60
54
|
'It might continue to raise errors for a while.'
|
61
55
|
)
|
62
56
|
end
|
@@ -77,9 +77,8 @@ class RedisClient
|
|
77
77
|
arr[8] = []
|
78
78
|
next
|
79
79
|
end
|
80
|
-
|
81
|
-
|
82
|
-
arr[8] = arr[8].map { |a| a.size == 1 ? a << a.first : a }.map(&:sort)
|
80
|
+
arr[8] = arr[8..].filter_map { |str| str.start_with?('[') ? nil : str.split('-').map { |s| Integer(s) } }
|
81
|
+
.map { |a| a.size == 1 ? a << a.first : a }.map(&:sort)
|
83
82
|
end
|
84
83
|
|
85
84
|
rows.map do |arr|
|
@@ -102,7 +101,7 @@ class RedisClient
|
|
102
101
|
end
|
103
102
|
|
104
103
|
def each(&block)
|
105
|
-
@clients.
|
104
|
+
@clients.each_value(&block)
|
106
105
|
end
|
107
106
|
|
108
107
|
def sample
|
@@ -181,7 +180,7 @@ class RedisClient
|
|
181
180
|
def scale_reading_clients
|
182
181
|
keys = replica_disabled? ? @replications.keys : @replications.values.map(&:first)
|
183
182
|
@clients.select { |k, _| keys.include?(k) }.values.sort_by do |client|
|
184
|
-
|
183
|
+
"#{client.config.host}-#{client.config.port}"
|
185
184
|
end
|
186
185
|
end
|
187
186
|
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'redis_client'
|
4
|
+
require 'redis_client/cluster/errors'
|
5
|
+
|
6
|
+
class RedisClient
|
7
|
+
class Cluster
|
8
|
+
class Pipeline
|
9
|
+
ReplySizeError = Class.new(::RedisClient::Error)
|
10
|
+
|
11
|
+
def initialize(router)
|
12
|
+
@router = router
|
13
|
+
@grouped = Hash.new([].freeze)
|
14
|
+
@size = 0
|
15
|
+
end
|
16
|
+
|
17
|
+
def call(*command, **kwargs)
|
18
|
+
node_key = @router.find_node_key(*command, primary_only: true)
|
19
|
+
@grouped[node_key] += [[@size, :call, command, kwargs]]
|
20
|
+
@size += 1
|
21
|
+
end
|
22
|
+
|
23
|
+
def call_once(*command, **kwargs)
|
24
|
+
node_key = @router.find_node_key(*command, primary_only: true)
|
25
|
+
@grouped[node_key] += [[@size, :call_once, command, kwargs]]
|
26
|
+
@size += 1
|
27
|
+
end
|
28
|
+
|
29
|
+
def blocking_call(timeout, *command, **kwargs)
|
30
|
+
node_key = @router.find_node_key(*command, primary_only: true)
|
31
|
+
@grouped[node_key] += [[@size, :blocking_call, timeout, command, kwargs]]
|
32
|
+
@size += 1
|
33
|
+
end
|
34
|
+
|
35
|
+
def empty?
|
36
|
+
@size.zero?
|
37
|
+
end
|
38
|
+
|
39
|
+
# TODO: https://github.com/redis-rb/redis-cluster-client/issues/37 handle redirections
|
40
|
+
def execute # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
41
|
+
all_replies = Array.new(@size)
|
42
|
+
errors = {}
|
43
|
+
threads = @grouped.map do |k, v|
|
44
|
+
Thread.new(@router, k, v) do |router, node_key, rows|
|
45
|
+
Thread.pass
|
46
|
+
replies = router.find_node(node_key).pipelined do |pipeline|
|
47
|
+
rows.each do |row|
|
48
|
+
case row[1]
|
49
|
+
when :call then pipeline.call(*row[2], **row[3])
|
50
|
+
when :call_once then pipeline.call_once(*row[2], **row[3])
|
51
|
+
when :blocking_call then pipeline.blocking_call(row[2], *row[3], **row[4])
|
52
|
+
else raise NotImplementedError, row[1]
|
53
|
+
end
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
raise ReplySizeError, "commands: #{rows.size}, replies: #{replies.size}" if rows.size != replies.size
|
58
|
+
|
59
|
+
rows.each_with_index { |row, idx| all_replies[row.first] = replies[idx] }
|
60
|
+
rescue StandardError => e
|
61
|
+
errors[node_key] = e
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
threads.each(&:join)
|
66
|
+
return all_replies if errors.empty?
|
67
|
+
|
68
|
+
raise ::RedisClient::Cluster::ErrorCollection, errors
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class RedisClient
|
4
|
+
class Cluster
|
5
|
+
class PubSub
|
6
|
+
def initialize(router)
|
7
|
+
@router = router
|
8
|
+
@pubsub = nil
|
9
|
+
end
|
10
|
+
|
11
|
+
def call(*command, **kwargs)
|
12
|
+
close
|
13
|
+
@pubsub = @router.assign_node(*command).pubsub
|
14
|
+
@pubsub.call(*command, **kwargs)
|
15
|
+
end
|
16
|
+
|
17
|
+
def close
|
18
|
+
@pubsub&.close
|
19
|
+
@pubsub = nil
|
20
|
+
end
|
21
|
+
|
22
|
+
def next_event(timeout = nil)
|
23
|
+
@pubsub&.next_event(timeout)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
@@ -0,0 +1,266 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'redis_client'
|
4
|
+
require 'redis_client/cluster/command'
|
5
|
+
require 'redis_client/cluster/errors'
|
6
|
+
require 'redis_client/cluster/key_slot_converter'
|
7
|
+
require 'redis_client/cluster/node'
|
8
|
+
require 'redis_client/cluster/node_key'
|
9
|
+
|
10
|
+
class RedisClient
|
11
|
+
class Cluster
|
12
|
+
class Router
|
13
|
+
ZERO_CURSOR_FOR_SCAN = '0'
|
14
|
+
|
15
|
+
attr_reader :node
|
16
|
+
|
17
|
+
def initialize(config, pool: nil, **kwargs)
|
18
|
+
@config = config.dup
|
19
|
+
@pool = pool
|
20
|
+
@client_kwargs = kwargs
|
21
|
+
@node = fetch_cluster_info(@config, pool: @pool, **@client_kwargs)
|
22
|
+
@command = ::RedisClient::Cluster::Command.load(@node)
|
23
|
+
@mutex = Mutex.new
|
24
|
+
end
|
25
|
+
|
26
|
+
def send_command(method, *args, **kwargs, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
27
|
+
command = method == :blocking_call && args.size > 1 ? args[1..] : args
|
28
|
+
|
29
|
+
cmd = command.first.to_s.downcase
|
30
|
+
case cmd
|
31
|
+
when 'acl', 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save'
|
32
|
+
@node.call_all(method, *args, **kwargs, &block).first
|
33
|
+
when 'flushall', 'flushdb'
|
34
|
+
@node.call_primaries(method, *args, **kwargs, &block).first
|
35
|
+
when 'ping' then @node.send_ping(method, *args, **kwargs, &block).first
|
36
|
+
when 'wait' then send_wait_command(method, *args, **kwargs, &block)
|
37
|
+
when 'keys' then @node.call_replicas(method, *args, **kwargs, &block).flatten.sort_by(&:to_s)
|
38
|
+
when 'dbsize' then @node.call_replicas(method, *args, **kwargs, &block).select { |e| e.is_a?(Integer) }.sum
|
39
|
+
when 'scan' then scan(*command, **kwargs)
|
40
|
+
when 'lastsave' then @node.call_all(method, *args, **kwargs, &block).sort_by(&:to_i)
|
41
|
+
when 'role' then @node.call_all(method, *args, **kwargs, &block)
|
42
|
+
when 'config' then send_config_command(method, *args, **kwargs, &block)
|
43
|
+
when 'client' then send_client_command(method, *args, **kwargs, &block)
|
44
|
+
when 'cluster' then send_cluster_command(method, *args, **kwargs, &block)
|
45
|
+
when 'readonly', 'readwrite', 'shutdown'
|
46
|
+
raise ::RedisClient::Cluster::OrchestrationCommandNotSupported, cmd
|
47
|
+
when 'memory' then send_memory_command(method, *args, **kwargs, &block)
|
48
|
+
when 'script' then send_script_command(method, *args, **kwargs, &block)
|
49
|
+
when 'pubsub' then send_pubsub_command(method, *args, **kwargs, &block)
|
50
|
+
when 'discard', 'exec', 'multi', 'unwatch'
|
51
|
+
raise ::RedisClient::Cluster::AmbiguousNodeError, cmd
|
52
|
+
else
|
53
|
+
node = assign_node(*command)
|
54
|
+
try_send(node, method, *args, **kwargs, &block)
|
55
|
+
end
|
56
|
+
rescue ::RedisClient::Cluster::Node::ReloadNeeded
|
57
|
+
update_cluster_info!
|
58
|
+
raise ::RedisClient::Cluster::NodeMightBeDown
|
59
|
+
rescue ::RedisClient::Cluster::ErrorCollection => e
|
60
|
+
update_cluster_info! if e.errors.values.any? do |err|
|
61
|
+
err.message.start_with?('CLUSTERDOWN Hash slot not served')
|
62
|
+
end
|
63
|
+
raise
|
64
|
+
end
|
65
|
+
|
66
|
+
# @see https://redis.io/topics/cluster-spec#redirection-and-resharding
|
67
|
+
# Redirection and resharding
|
68
|
+
def try_send(node, method, *args, retry_count: 3, **kwargs, &block) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
69
|
+
node.send(method, *args, **kwargs, &block)
|
70
|
+
rescue ::RedisClient::CommandError => e
|
71
|
+
raise if retry_count <= 0
|
72
|
+
|
73
|
+
if e.message.start_with?('MOVED')
|
74
|
+
node = assign_redirection_node(e.message)
|
75
|
+
retry_count -= 1
|
76
|
+
retry
|
77
|
+
elsif e.message.start_with?('ASK')
|
78
|
+
node = assign_asking_node(e.message)
|
79
|
+
node.call('ASKING')
|
80
|
+
retry_count -= 1
|
81
|
+
retry
|
82
|
+
elsif e.message.start_with?('CLUSTERDOWN Hash slot not served')
|
83
|
+
update_cluster_info!
|
84
|
+
retry_count -= 1
|
85
|
+
retry
|
86
|
+
else
|
87
|
+
raise
|
88
|
+
end
|
89
|
+
rescue ::RedisClient::ConnectionError
|
90
|
+
raise if retry_count <= 0
|
91
|
+
|
92
|
+
update_cluster_info!
|
93
|
+
retry_count -= 1
|
94
|
+
retry
|
95
|
+
end
|
96
|
+
|
97
|
+
def scan(*command, **kwargs) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
98
|
+
command[1] = ZERO_CURSOR_FOR_SCAN if command.size == 1
|
99
|
+
input_cursor = Integer(command[1])
|
100
|
+
|
101
|
+
client_index = input_cursor % 256
|
102
|
+
raw_cursor = input_cursor >> 8
|
103
|
+
|
104
|
+
clients = @node.scale_reading_clients
|
105
|
+
|
106
|
+
client = clients[client_index]
|
107
|
+
return [ZERO_CURSOR_FOR_SCAN, []] unless client
|
108
|
+
|
109
|
+
command[1] = raw_cursor.to_s
|
110
|
+
|
111
|
+
result_cursor, result_keys = client.call(*command, **kwargs)
|
112
|
+
result_cursor = Integer(result_cursor)
|
113
|
+
|
114
|
+
client_index += 1 if result_cursor == 0
|
115
|
+
|
116
|
+
[((result_cursor << 8) + client_index).to_s, result_keys]
|
117
|
+
end
|
118
|
+
|
119
|
+
def assign_node(*command)
|
120
|
+
node_key = find_node_key(*command)
|
121
|
+
find_node(node_key)
|
122
|
+
end
|
123
|
+
|
124
|
+
def find_node_key(*command, primary_only: false)
|
125
|
+
key = @command.extract_first_key(command)
|
126
|
+
slot = key.empty? ? nil : ::RedisClient::Cluster::KeySlotConverter.convert(key)
|
127
|
+
|
128
|
+
if @command.should_send_to_primary?(command) || primary_only
|
129
|
+
@node.find_node_key_of_primary(slot) || @node.primary_node_keys.sample
|
130
|
+
else
|
131
|
+
@node.find_node_key_of_replica(slot) || @node.replica_node_keys.sample
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
def find_node(node_key, retry_count: 3)
|
136
|
+
@node.find_by(node_key)
|
137
|
+
rescue ::RedisClient::Cluster::Node::ReloadNeeded
|
138
|
+
raise ::RedieClient::Cluster::NodeMightBeDown if retry_count <= 0
|
139
|
+
|
140
|
+
update_cluster_info!
|
141
|
+
retry_count -= 1
|
142
|
+
retry
|
143
|
+
end
|
144
|
+
|
145
|
+
private
|
146
|
+
|
147
|
+
def send_wait_command(method, *args, retry_count: 3, **kwargs, &block)
|
148
|
+
@node.call_primaries(method, *args, **kwargs, &block).select { |r| r.is_a?(Integer) }.sum
|
149
|
+
rescue ::RedisClient::Cluster::ErrorCollection => e
|
150
|
+
raise if retry_count <= 0
|
151
|
+
raise if e.errors.values.none? do |err|
|
152
|
+
err.message.include?('WAIT cannot be used with replica instances')
|
153
|
+
end
|
154
|
+
|
155
|
+
update_cluster_info!
|
156
|
+
retry_count -= 1
|
157
|
+
retry
|
158
|
+
end
|
159
|
+
|
160
|
+
def send_config_command(method, *args, **kwargs, &block)
|
161
|
+
command = method == :blocking_call && args.size > 1 ? args[1..] : args
|
162
|
+
|
163
|
+
case command[1].to_s.downcase
|
164
|
+
when 'resetstat', 'rewrite', 'set'
|
165
|
+
@node.call_all(method, *args, **kwargs, &block).first
|
166
|
+
else assign_node(*command).send(method, *args, **kwargs, &block)
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
def send_memory_command(method, *args, **kwargs, &block)
|
171
|
+
command = method == :blocking_call && args.size > 1 ? args[1..] : args
|
172
|
+
|
173
|
+
case command[1].to_s.downcase
|
174
|
+
when 'stats' then @node.call_all(method, *args, **kwargs, &block)
|
175
|
+
when 'purge' then @node.call_all(method, *args, **kwargs, &block).first
|
176
|
+
else assign_node(*command).send(method, *args, **kwargs, &block)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def send_client_command(method, *args, **kwargs, &block)
|
181
|
+
command = method == :blocking_call && args.size > 1 ? args[1..] : args
|
182
|
+
|
183
|
+
case command[1].to_s.downcase
|
184
|
+
when 'list' then @node.call_all(method, *args, **kwargs, &block).flatten
|
185
|
+
when 'pause', 'reply', 'setname'
|
186
|
+
@node.call_all(method, *args, **kwargs, &block).first
|
187
|
+
else assign_node(*command).send(method, *args, **kwargs, &block)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def send_cluster_command(method, *args, **kwargs, &block) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
192
|
+
command = method == :blocking_call && args.size > 1 ? args[1..] : args
|
193
|
+
subcommand = command[1].to_s.downcase
|
194
|
+
|
195
|
+
case subcommand
|
196
|
+
when 'addslots', 'delslots', 'failover', 'forget', 'meet', 'replicate',
|
197
|
+
'reset', 'set-config-epoch', 'setslot'
|
198
|
+
raise ::RedisClient::Cluster::OrchestrationCommandNotSupported, ['cluster', subcommand]
|
199
|
+
when 'saveconfig' then @node.call_all(method, *args, **kwargs, &block).first
|
200
|
+
when 'getkeysinslot'
|
201
|
+
raise ArgumentError, command.join(' ') if command.size != 4
|
202
|
+
|
203
|
+
find_node(@node.find_node_key_of_replica(command[2])).send(method, *args, **kwargs, &block)
|
204
|
+
else assign_node(*command).send(method, *args, **kwargs, &block)
|
205
|
+
end
|
206
|
+
end
|
207
|
+
|
208
|
+
def send_script_command(method, *args, **kwargs, &block)
|
209
|
+
command = method == :blocking_call && args.size > 1 ? args[1..] : args
|
210
|
+
|
211
|
+
case command[1].to_s.downcase
|
212
|
+
when 'debug', 'kill'
|
213
|
+
@node.call_all(method, *args, **kwargs, &block).first
|
214
|
+
when 'flush', 'load'
|
215
|
+
@node.call_primaries(method, *args, **kwargs, &block).first
|
216
|
+
else assign_node(*command).send(method, *args, **kwargs, &block)
|
217
|
+
end
|
218
|
+
end
|
219
|
+
|
220
|
+
def send_pubsub_command(method, *args, **kwargs, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
221
|
+
command = method == :blocking_call && args.size > 1 ? args[1..] : args
|
222
|
+
|
223
|
+
case command[1].to_s.downcase
|
224
|
+
when 'channels' then @node.call_all(method, *args, **kwargs, &block).flatten.uniq.sort_by(&:to_s)
|
225
|
+
when 'numsub'
|
226
|
+
@node.call_all(method, *args, **kwargs, &block).reject(&:empty?).map { |e| Hash[*e] }
|
227
|
+
.reduce({}) { |a, e| a.merge(e) { |_, v1, v2| v1 + v2 } }
|
228
|
+
when 'numpat' then @node.call_all(method, *args, **kwargs, &block).select { |e| e.is_a?(Integer) }.sum
|
229
|
+
else assign_node(*command).send(method, *args, **kwargs, &block)
|
230
|
+
end
|
231
|
+
end
|
232
|
+
|
233
|
+
def assign_redirection_node(err_msg)
|
234
|
+
_, slot, node_key = err_msg.split
|
235
|
+
slot = slot.to_i
|
236
|
+
@node.update_slot(slot, node_key)
|
237
|
+
find_node(node_key)
|
238
|
+
end
|
239
|
+
|
240
|
+
def assign_asking_node(err_msg)
|
241
|
+
_, _, node_key = err_msg.split
|
242
|
+
find_node(node_key)
|
243
|
+
end
|
244
|
+
|
245
|
+
def fetch_cluster_info(config, pool: nil, **kwargs)
|
246
|
+
node_info = ::RedisClient::Cluster::Node.load_info(config.per_node_key, **kwargs)
|
247
|
+
node_addrs = node_info.map { |info| ::RedisClient::Cluster::NodeKey.hashify(info[:node_key]) }
|
248
|
+
config.update_node(node_addrs)
|
249
|
+
::RedisClient::Cluster::Node.new(config.per_node_key,
|
250
|
+
node_info: node_info, pool: pool, with_replica: config.use_replica?, **kwargs)
|
251
|
+
end
|
252
|
+
|
253
|
+
def update_cluster_info!
|
254
|
+
@mutex.synchronize do
|
255
|
+
begin
|
256
|
+
@node.call_all(:close)
|
257
|
+
rescue ::RedisClient::Cluster::ErrorCollection
|
258
|
+
# ignore
|
259
|
+
end
|
260
|
+
|
261
|
+
@node = fetch_cluster_info(@config, pool: @pool, **@client_kwargs)
|
262
|
+
end
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
data/lib/redis_client/cluster.rb
CHANGED
@@ -1,133 +1,32 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
require 'redis_client'
|
4
|
-
require 'redis_client/cluster/
|
5
|
-
require 'redis_client/cluster/
|
6
|
-
require 'redis_client/cluster/
|
7
|
-
require 'redis_client/cluster/node'
|
8
|
-
require 'redis_client/cluster/node_key'
|
4
|
+
require 'redis_client/cluster/pipeline'
|
5
|
+
require 'redis_client/cluster/pub_sub'
|
6
|
+
require 'redis_client/cluster/router'
|
9
7
|
|
10
8
|
class RedisClient
|
11
9
|
class Cluster
|
12
|
-
class Pipeline
|
13
|
-
ReplySizeError = Class.new(::RedisClient::Error)
|
14
|
-
|
15
|
-
def initialize(client)
|
16
|
-
@client = client
|
17
|
-
@grouped = Hash.new([].freeze)
|
18
|
-
@size = 0
|
19
|
-
end
|
20
|
-
|
21
|
-
def call(*command, **kwargs)
|
22
|
-
node_key = @client.send(:find_node_key, *command, primary_only: true)
|
23
|
-
@grouped[node_key] += [[@size, :call, command, kwargs]]
|
24
|
-
@size += 1
|
25
|
-
end
|
26
|
-
|
27
|
-
def call_once(*command, **kwargs)
|
28
|
-
node_key = @client.send(:find_node_key, *command, primary_only: true)
|
29
|
-
@grouped[node_key] += [[@size, :call_once, command, kwargs]]
|
30
|
-
@size += 1
|
31
|
-
end
|
32
|
-
|
33
|
-
def blocking_call(timeout, *command, **kwargs)
|
34
|
-
node_key = @client.send(:find_node_key, *command, primary_only: true)
|
35
|
-
@grouped[node_key] += [[@size, :blocking_call, timeout, command, kwargs]]
|
36
|
-
@size += 1
|
37
|
-
end
|
38
|
-
|
39
|
-
def empty?
|
40
|
-
@size.zero?
|
41
|
-
end
|
42
|
-
|
43
|
-
# TODO: https://github.com/redis-rb/redis-cluster-client/issues/37 handle redirections
|
44
|
-
def execute # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength, Metrics/PerceivedComplexity
|
45
|
-
all_replies = Array.new(@size)
|
46
|
-
errors = {}
|
47
|
-
threads = @grouped.map do |k, v|
|
48
|
-
Thread.new(@client, k, v) do |client, node_key, rows|
|
49
|
-
Thread.pass
|
50
|
-
replies = client.send(:find_node, node_key).pipelined do |pipeline|
|
51
|
-
rows.each do |row|
|
52
|
-
case row[1]
|
53
|
-
when :call then pipeline.call(*row[2], **row[3])
|
54
|
-
when :call_once then pipeline.call_once(*row[2], **row[3])
|
55
|
-
when :blocking_call then pipeline.blocking_call(row[2], *row[3], **row[4])
|
56
|
-
else raise NotImplementedError, row[1]
|
57
|
-
end
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
raise ReplySizeError, "commands: #{rows.size}, replies: #{replies.size}" if rows.size != replies.size
|
62
|
-
|
63
|
-
rows.each_with_index { |row, idx| all_replies[row.first] = replies[idx] }
|
64
|
-
rescue StandardError => e
|
65
|
-
errors[node_key] = e
|
66
|
-
end
|
67
|
-
end
|
68
|
-
|
69
|
-
threads.each(&:join)
|
70
|
-
return all_replies if errors.empty?
|
71
|
-
|
72
|
-
raise ::RedisClient::Cluster::ErrorCollection, errors
|
73
|
-
end
|
74
|
-
end
|
75
|
-
|
76
|
-
class PubSub
|
77
|
-
def initialize(client)
|
78
|
-
@client = client
|
79
|
-
@pubsub = nil
|
80
|
-
end
|
81
|
-
|
82
|
-
def call(*command, **kwargs)
|
83
|
-
close
|
84
|
-
@pubsub = @client.send(:assign_node, *command).pubsub
|
85
|
-
@pubsub.call(*command, **kwargs)
|
86
|
-
end
|
87
|
-
|
88
|
-
def close
|
89
|
-
@pubsub&.close
|
90
|
-
@pubsub = nil
|
91
|
-
end
|
92
|
-
|
93
|
-
def next_event(timeout = nil)
|
94
|
-
@pubsub&.next_event(timeout)
|
95
|
-
end
|
96
|
-
end
|
97
|
-
|
98
10
|
ZERO_CURSOR_FOR_SCAN = '0'
|
99
|
-
CMD_SCAN = 'SCAN'
|
100
|
-
CMD_SSCAN = 'SSCAN'
|
101
|
-
CMD_HSCAN = 'HSCAN'
|
102
|
-
CMD_ZSCAN = 'ZSCAN'
|
103
|
-
CMD_ASKING = 'ASKING'
|
104
|
-
REPLY_OK = 'OK'
|
105
|
-
REPLY_MOVED = 'MOVED'
|
106
|
-
REPLY_ASK = 'ASK'
|
107
11
|
|
108
12
|
def initialize(config, pool: nil, **kwargs)
|
109
|
-
@
|
110
|
-
@pool = pool
|
111
|
-
@client_kwargs = kwargs
|
112
|
-
@node = fetch_cluster_info!(@config, pool: @pool, **@client_kwargs)
|
113
|
-
@command = ::RedisClient::Cluster::Command.load(@node)
|
114
|
-
@mutex = Mutex.new
|
13
|
+
@router = ::RedisClient::Cluster::Router.new(config, pool: pool, **kwargs)
|
115
14
|
end
|
116
15
|
|
117
16
|
def inspect
|
118
|
-
"#<#{self.class.name} #{@node.node_keys.join(', ')}>"
|
17
|
+
"#<#{self.class.name} #{@router.node.node_keys.join(', ')}>"
|
119
18
|
end
|
120
19
|
|
121
20
|
def call(*command, **kwargs)
|
122
|
-
send_command(:call, *command, **kwargs)
|
21
|
+
@router.send_command(:call, *command, **kwargs)
|
123
22
|
end
|
124
23
|
|
125
24
|
def call_once(*command, **kwargs)
|
126
|
-
send_command(:call_once, *command, **kwargs)
|
25
|
+
@router.send_command(:call_once, *command, **kwargs)
|
127
26
|
end
|
128
27
|
|
129
28
|
def blocking_call(timeout, *command, **kwargs)
|
130
|
-
send_command(:blocking_call, timeout, *command, **kwargs)
|
29
|
+
@router.send_command(:blocking_call, timeout, *command, **kwargs)
|
131
30
|
end
|
132
31
|
|
133
32
|
def scan(*args, **kwargs, &block)
|
@@ -135,29 +34,29 @@ class RedisClient
|
|
135
34
|
|
136
35
|
cursor = ZERO_CURSOR_FOR_SCAN
|
137
36
|
loop do
|
138
|
-
cursor, keys =
|
37
|
+
cursor, keys = @router.scan('SCAN', cursor, *args, **kwargs)
|
139
38
|
keys.each(&block)
|
140
39
|
break if cursor == ZERO_CURSOR_FOR_SCAN
|
141
40
|
end
|
142
41
|
end
|
143
42
|
|
144
43
|
def sscan(key, *args, **kwargs, &block)
|
145
|
-
node = assign_node(
|
146
|
-
try_send(node, :sscan, key, *args, **kwargs, &block)
|
44
|
+
node = @router.assign_node('SSCAN', key)
|
45
|
+
@router.try_send(node, :sscan, key, *args, **kwargs, &block)
|
147
46
|
end
|
148
47
|
|
149
48
|
def hscan(key, *args, **kwargs, &block)
|
150
|
-
node = assign_node(
|
151
|
-
try_send(node, :hscan, key, *args, **kwargs, &block)
|
49
|
+
node = @router.assign_node('HSCAN', key)
|
50
|
+
@router.try_send(node, :hscan, key, *args, **kwargs, &block)
|
152
51
|
end
|
153
52
|
|
154
53
|
def zscan(key, *args, **kwargs, &block)
|
155
|
-
node = assign_node(
|
156
|
-
try_send(node, :zscan, key, *args, **kwargs, &block)
|
54
|
+
node = @router.assign_node('ZSCAN', key)
|
55
|
+
@router.try_send(node, :zscan, key, *args, **kwargs, &block)
|
157
56
|
end
|
158
57
|
|
159
58
|
def pipelined
|
160
|
-
pipeline = ::RedisClient::Cluster::Pipeline.new(
|
59
|
+
pipeline = ::RedisClient::Cluster::Pipeline.new(@router)
|
161
60
|
yield pipeline
|
162
61
|
return [] if pipeline.empty? == 0
|
163
62
|
|
@@ -165,239 +64,12 @@ class RedisClient
|
|
165
64
|
end
|
166
65
|
|
167
66
|
def pubsub
|
168
|
-
::RedisClient::Cluster::PubSub.new(
|
67
|
+
::RedisClient::Cluster::PubSub.new(@router)
|
169
68
|
end
|
170
69
|
|
171
70
|
def close
|
172
|
-
@node.call_all(:close)
|
71
|
+
@router.node.call_all(:close)
|
173
72
|
nil
|
174
|
-
rescue StandardError
|
175
|
-
# ignore
|
176
|
-
end
|
177
|
-
|
178
|
-
private
|
179
|
-
|
180
|
-
def fetch_cluster_info!(config, pool: nil, **kwargs)
|
181
|
-
node_info = ::RedisClient::Cluster::Node.load_info(config.per_node_key, **kwargs)
|
182
|
-
node_addrs = node_info.map { |info| ::RedisClient::Cluster::NodeKey.hashify(info[:node_key]) }
|
183
|
-
config.update_node(node_addrs)
|
184
|
-
::RedisClient::Cluster::Node.new(config.per_node_key,
|
185
|
-
node_info: node_info, pool: pool, with_replica: config.use_replica?, **kwargs)
|
186
|
-
end
|
187
|
-
|
188
|
-
def send_command(method, *args, **kwargs, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/MethodLength
|
189
|
-
command = method == :blocking_call && args.size > 1 ? args[1..] : args
|
190
|
-
|
191
|
-
cmd = command.first.to_s.downcase
|
192
|
-
case cmd
|
193
|
-
when 'acl', 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save'
|
194
|
-
@node.call_all(method, *args, **kwargs, &block).first
|
195
|
-
when 'flushall', 'flushdb'
|
196
|
-
@node.call_primaries(method, *args, **kwargs, &block).first
|
197
|
-
when 'ping' then @node.send_ping(method, *args, **kwargs, &block).first
|
198
|
-
when 'wait' then send_wait_command(method, *args, **kwargs, &block)
|
199
|
-
when 'keys' then @node.call_replicas(method, *args, **kwargs, &block).flatten.sort
|
200
|
-
when 'dbsize' then @node.call_replicas(method, *args, **kwargs, &block).sum
|
201
|
-
when 'scan' then _scan(*command, **kwargs)
|
202
|
-
when 'lastsave' then @node.call_all(method, *args, **kwargs, &block).sort
|
203
|
-
when 'role' then @node.call_all(method, *args, **kwargs, &block)
|
204
|
-
when 'config' then send_config_command(method, *args, **kwargs, &block)
|
205
|
-
when 'client' then send_client_command(method, *args, **kwargs, &block)
|
206
|
-
when 'cluster' then send_cluster_command(method, *args, **kwargs, &block)
|
207
|
-
when 'readonly', 'readwrite', 'shutdown'
|
208
|
-
raise ::RedisClient::Cluster::OrchestrationCommandNotSupported, cmd
|
209
|
-
when 'memory' then send_memory_command(method, *args, **kwargs, &block)
|
210
|
-
when 'script' then send_script_command(method, *args, **kwargs, &block)
|
211
|
-
when 'pubsub' then send_pubsub_command(method, *args, **kwargs, &block)
|
212
|
-
when 'discard', 'exec', 'multi', 'unwatch'
|
213
|
-
raise ::RedisClient::Cluster::AmbiguousNodeError, cmd
|
214
|
-
else
|
215
|
-
node = assign_node(*command)
|
216
|
-
try_send(node, method, *args, **kwargs, &block)
|
217
|
-
end
|
218
|
-
rescue RedisClient::Cluster::Node::ReloadNeeded
|
219
|
-
update_cluster_info!
|
220
|
-
raise ::RedisClient::Cluster::NodeMightBeDown
|
221
|
-
end
|
222
|
-
|
223
|
-
def send_wait_command(method, *args, retry_count: 3, **kwargs, &block)
|
224
|
-
@node.call_primaries(method, *args, **kwargs, &block).sum
|
225
|
-
rescue RedisClient::Cluster::ErrorCollection => e
|
226
|
-
raise if retry_count <= 0
|
227
|
-
raise if e.errors.values.none? do |err|
|
228
|
-
err.message.include?('WAIT cannot be used with replica instances')
|
229
|
-
end
|
230
|
-
|
231
|
-
update_cluster_info!
|
232
|
-
retry_count -= 1
|
233
|
-
retry
|
234
|
-
end
|
235
|
-
|
236
|
-
def send_config_command(method, *args, **kwargs, &block)
|
237
|
-
command = method == :blocking_call && args.size > 1 ? args[1..] : args
|
238
|
-
|
239
|
-
case command[1].to_s.downcase
|
240
|
-
when 'resetstat', 'rewrite', 'set'
|
241
|
-
@node.call_all(method, *args, **kwargs, &block).first
|
242
|
-
else assign_node(*command).send(method, *args, **kwargs, &block)
|
243
|
-
end
|
244
|
-
end
|
245
|
-
|
246
|
-
def send_memory_command(method, *args, **kwargs, &block)
|
247
|
-
command = method == :blocking_call && args.size > 1 ? args[1..] : args
|
248
|
-
|
249
|
-
case command[1].to_s.downcase
|
250
|
-
when 'stats' then @node.call_all(method, *args, **kwargs, &block)
|
251
|
-
when 'purge' then @node.call_all(method, *args, **kwargs, &block).first
|
252
|
-
else assign_node(*command).send(method, *args, **kwargs, &block)
|
253
|
-
end
|
254
|
-
end
|
255
|
-
|
256
|
-
def send_client_command(method, *args, **kwargs, &block)
|
257
|
-
command = method == :blocking_call && args.size > 1 ? args[1..] : args
|
258
|
-
|
259
|
-
case command[1].to_s.downcase
|
260
|
-
when 'list' then @node.call_all(method, *args, **kwargs, &block).flatten
|
261
|
-
when 'pause', 'reply', 'setname'
|
262
|
-
@node.call_all(method, *args, **kwargs, &block).first
|
263
|
-
else assign_node(*command).send(method, *args, **kwargs, &block)
|
264
|
-
end
|
265
|
-
end
|
266
|
-
|
267
|
-
def send_cluster_command(method, *args, **kwargs, &block) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
268
|
-
command = method == :blocking_call && args.size > 1 ? args[1..] : args
|
269
|
-
subcommand = command[1].to_s.downcase
|
270
|
-
|
271
|
-
case subcommand
|
272
|
-
when 'addslots', 'delslots', 'failover', 'forget', 'meet', 'replicate',
|
273
|
-
'reset', 'set-config-epoch', 'setslot'
|
274
|
-
raise ::RedisClient::Cluster::OrchestrationCommandNotSupported, ['cluster', subcommand]
|
275
|
-
when 'saveconfig' then @node.call_all(method, *args, **kwargs, &block).first
|
276
|
-
when 'getkeysinslot'
|
277
|
-
raise ArgumentError, command.join(' ') if command.size != 4
|
278
|
-
|
279
|
-
find_node(@node.find_node_key_of_replica(command[2])).send(method, *args, **kwargs, &block)
|
280
|
-
else assign_node(*command).send(method, *args, **kwargs, &block)
|
281
|
-
end
|
282
|
-
end
|
283
|
-
|
284
|
-
def send_script_command(method, *args, **kwargs, &block)
|
285
|
-
command = method == :blocking_call && args.size > 1 ? args[1..] : args
|
286
|
-
|
287
|
-
case command[1].to_s.downcase
|
288
|
-
when 'debug', 'kill'
|
289
|
-
@node.call_all(method, *args, **kwargs, &block).first
|
290
|
-
when 'flush', 'load'
|
291
|
-
@node.call_primaries(method, *args, **kwargs, &block).first
|
292
|
-
else assign_node(*command).send(method, *args, **kwargs, &block)
|
293
|
-
end
|
294
|
-
end
|
295
|
-
|
296
|
-
def send_pubsub_command(method, *args, **kwargs, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
297
|
-
command = method == :blocking_call && args.size > 1 ? args[1..] : args
|
298
|
-
|
299
|
-
case command[1].to_s.downcase
|
300
|
-
when 'channels' then @node.call_all(method, *args, **kwargs, &block).flatten.uniq.sort
|
301
|
-
when 'numsub'
|
302
|
-
@node.call_all(method, *args, **kwargs, &block).reject(&:empty?).map { |e| Hash[*e] }
|
303
|
-
.reduce({}) { |a, e| a.merge(e) { |_, v1, v2| v1 + v2 } }
|
304
|
-
when 'numpat' then @node.call_all(method, *args, **kwargs, &block).sum
|
305
|
-
else assign_node(*command).send(method, *args, **kwargs, &block)
|
306
|
-
end
|
307
|
-
end
|
308
|
-
|
309
|
-
# @see https://redis.io/topics/cluster-spec#redirection-and-resharding
|
310
|
-
# Redirection and resharding
|
311
|
-
def try_send(node, method, *args, retry_count: 3, **kwargs, &block) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
312
|
-
node.send(method, *args, **kwargs, &block)
|
313
|
-
rescue ::RedisClient::CommandError => e
|
314
|
-
raise if retry_count <= 0
|
315
|
-
|
316
|
-
if e.message.start_with?(REPLY_MOVED)
|
317
|
-
node = assign_redirection_node(e.message)
|
318
|
-
retry_count -= 1
|
319
|
-
retry
|
320
|
-
elsif e.message.start_with?(REPLY_ASK)
|
321
|
-
node = assign_asking_node(e.message)
|
322
|
-
node.call(CMD_ASKING)
|
323
|
-
retry_count -= 1
|
324
|
-
retry
|
325
|
-
else
|
326
|
-
raise
|
327
|
-
end
|
328
|
-
rescue ::RedisClient::ConnectionError
|
329
|
-
raise if retry_count <= 0
|
330
|
-
|
331
|
-
update_cluster_info!
|
332
|
-
retry_count -= 1
|
333
|
-
retry
|
334
|
-
end
|
335
|
-
|
336
|
-
def _scan(*command, **kwargs) # rubocop:disable Metrics/MethodLength, Metrics/AbcSize
|
337
|
-
command[1] = ZERO_CURSOR_FOR_SCAN if command.size == 1
|
338
|
-
input_cursor = Integer(command[1])
|
339
|
-
|
340
|
-
client_index = input_cursor % 256
|
341
|
-
raw_cursor = input_cursor >> 8
|
342
|
-
|
343
|
-
clients = @node.scale_reading_clients
|
344
|
-
|
345
|
-
client = clients[client_index]
|
346
|
-
return [ZERO_CURSOR_FOR_SCAN, []] unless client
|
347
|
-
|
348
|
-
command[1] = raw_cursor.to_s
|
349
|
-
|
350
|
-
result_cursor, result_keys = client.call(*command, **kwargs)
|
351
|
-
result_cursor = Integer(result_cursor)
|
352
|
-
|
353
|
-
client_index += 1 if result_cursor == 0
|
354
|
-
|
355
|
-
[((result_cursor << 8) + client_index).to_s, result_keys]
|
356
|
-
end
|
357
|
-
|
358
|
-
def assign_redirection_node(err_msg)
|
359
|
-
_, slot, node_key = err_msg.split
|
360
|
-
slot = slot.to_i
|
361
|
-
@node.update_slot(slot, node_key)
|
362
|
-
find_node(node_key)
|
363
|
-
end
|
364
|
-
|
365
|
-
def assign_asking_node(err_msg)
|
366
|
-
_, _, node_key = err_msg.split
|
367
|
-
find_node(node_key)
|
368
|
-
end
|
369
|
-
|
370
|
-
def assign_node(*command)
|
371
|
-
node_key = find_node_key(*command)
|
372
|
-
find_node(node_key)
|
373
|
-
end
|
374
|
-
|
375
|
-
def find_node_key(*command, primary_only: false)
|
376
|
-
key = @command.extract_first_key(command)
|
377
|
-
slot = key.empty? ? nil : ::RedisClient::Cluster::KeySlotConverter.convert(key)
|
378
|
-
|
379
|
-
if @command.should_send_to_primary?(command) || primary_only
|
380
|
-
@node.find_node_key_of_primary(slot) || @node.primary_node_keys.sample
|
381
|
-
else
|
382
|
-
@node.find_node_key_of_replica(slot) || @node.replica_node_keys.sample
|
383
|
-
end
|
384
|
-
end
|
385
|
-
|
386
|
-
def find_node(node_key, retry_count: 3)
|
387
|
-
@node.find_by(node_key)
|
388
|
-
rescue ::RedisClient::Cluster::Node::ReloadNeeded
|
389
|
-
raise ::RedieClient::Cluster::NodeMightBeDown if retry_count <= 0
|
390
|
-
|
391
|
-
update_cluster_info!
|
392
|
-
retry_count -= 1
|
393
|
-
retry
|
394
|
-
end
|
395
|
-
|
396
|
-
def update_cluster_info!
|
397
|
-
@mutex.synchronize do
|
398
|
-
close
|
399
|
-
@node = fetch_cluster_info!(@config, pool: @pool, **@client_kwargs)
|
400
|
-
end
|
401
73
|
end
|
402
74
|
end
|
403
75
|
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.0.
|
4
|
+
version: 0.0.12
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Taishi Kasuga
|
8
8
|
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2022-
|
11
|
+
date: 2022-07-02 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis-client
|
@@ -37,6 +37,9 @@ files:
|
|
37
37
|
- lib/redis_client/cluster/key_slot_converter.rb
|
38
38
|
- lib/redis_client/cluster/node.rb
|
39
39
|
- lib/redis_client/cluster/node_key.rb
|
40
|
+
- lib/redis_client/cluster/pipeline.rb
|
41
|
+
- lib/redis_client/cluster/pub_sub.rb
|
42
|
+
- lib/redis_client/cluster/router.rb
|
40
43
|
- lib/redis_client/cluster_config.rb
|
41
44
|
- lib/redis_cluster_client.rb
|
42
45
|
homepage: https://github.com/redis-rb/redis-cluster-client
|