redis-cluster-client 0.12.1 → 0.13.0
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 +37 -17
- data/lib/redis_client/cluster/concurrent_worker/on_demand.rb +2 -0
- data/lib/redis_client/cluster/concurrent_worker/pooled.rb +2 -0
- data/lib/redis_client/cluster/concurrent_worker.rb +4 -6
- data/lib/redis_client/cluster/errors.rb +7 -2
- data/lib/redis_client/cluster/node/latency_replica.rb +1 -1
- data/lib/redis_client/cluster/node.rb +2 -2
- data/lib/redis_client/cluster/optimistic_locking.rb +3 -3
- data/lib/redis_client/cluster/pipeline.rb +1 -1
- data/lib/redis_client/cluster/pub_sub.rb +15 -7
- data/lib/redis_client/cluster/router.rb +147 -70
- data/lib/redis_client/cluster/transaction.rb +5 -5
- data/lib/redis_client/cluster.rb +3 -2
- data/lib/redis_client/cluster_config.rb +10 -6
- metadata +2 -3
- data/lib/redis_client/cluster/normalized_cmd_name.rb +0 -71
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 8aa9148e1dedc8ccb5087590b5b693ff2d32ab1b4a16bf25227ab0d030bea56c
|
4
|
+
data.tar.gz: f8d699a826f263cb5ff1d99f08fd1be1cec551f4306759e14165f800a7ffec57
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 4630e1a9819484cf9675b4d6b77bcf39d437cd821ff3875c5717cfaccc827fc8482f71a6da41e5032227a02b19c214c339956d865ffa942eb8f901d329f3ffea
|
7
|
+
data.tar.gz: 3e2a2424421543b08623e76dcea06d2a646c3cfadbace64b6dce0adfd8bead2b24d78aa568d1ee36c45bc80c3cdcf473c7fa74e7f30976b0005f1d34791d6707
|
@@ -3,7 +3,6 @@
|
|
3
3
|
require 'redis_client'
|
4
4
|
require 'redis_client/cluster/errors'
|
5
5
|
require 'redis_client/cluster/key_slot_converter'
|
6
|
-
require 'redis_client/cluster/normalized_cmd_name'
|
7
6
|
|
8
7
|
class RedisClient
|
9
8
|
class Cluster
|
@@ -30,7 +29,7 @@ class RedisClient
|
|
30
29
|
nodes&.each do |node|
|
31
30
|
regular_timeout = node.read_timeout
|
32
31
|
node.read_timeout = slow_command_timeout > 0.0 ? slow_command_timeout : regular_timeout
|
33
|
-
reply = node.call('
|
32
|
+
reply = node.call('command')
|
34
33
|
node.read_timeout = regular_timeout
|
35
34
|
commands = parse_command_reply(reply)
|
36
35
|
cmd = ::RedisClient::Cluster::Command.new(commands)
|
@@ -51,7 +50,7 @@ class RedisClient
|
|
51
50
|
rows&.each_with_object({}) do |row, acc|
|
52
51
|
next if row[0].nil?
|
53
52
|
|
54
|
-
acc[row
|
53
|
+
acc[row.first] = ::RedisClient::Cluster::Command::Detail.new(
|
55
54
|
first_key_position: row[3],
|
56
55
|
key_step: row[5],
|
57
56
|
write?: row[2].include?('write'),
|
@@ -73,38 +72,59 @@ class RedisClient
|
|
73
72
|
end
|
74
73
|
|
75
74
|
def should_send_to_primary?(command)
|
76
|
-
|
77
|
-
@commands[name]&.write?
|
75
|
+
find_command_info(command.first)&.write?
|
78
76
|
end
|
79
77
|
|
80
78
|
def should_send_to_replica?(command)
|
81
|
-
|
82
|
-
@commands[name]&.readonly?
|
79
|
+
find_command_info(command.first)&.readonly?
|
83
80
|
end
|
84
81
|
|
85
82
|
def exists?(name)
|
86
|
-
@commands.key?(
|
83
|
+
@commands.key?(name) || @commands.key?(name.to_s.downcase(:ascii))
|
87
84
|
end
|
88
85
|
|
89
86
|
private
|
90
87
|
|
91
|
-
def
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
88
|
+
def find_command_info(name)
|
89
|
+
@commands[name] || @commands[name.to_s.downcase(:ascii)]
|
90
|
+
end
|
91
|
+
|
92
|
+
def determine_first_key_position(command) # rubocop:disable Metrics/CyclomaticComplexity, Metrics/AbcSize, Metrics/PerceivedComplexity
|
93
|
+
if command.first.casecmp('get').zero?
|
94
|
+
find_command_info(command.first)&.first_key_position.to_i
|
95
|
+
elsif command.first.casecmp('mget').zero?
|
96
|
+
find_command_info(command.first)&.first_key_position.to_i
|
97
|
+
elsif command.first.casecmp('set').zero?
|
98
|
+
find_command_info(command.first)&.first_key_position.to_i
|
99
|
+
elsif command.first.casecmp('mset').zero?
|
100
|
+
find_command_info(command.first)&.first_key_position.to_i
|
101
|
+
elsif command.first.casecmp('del').zero?
|
102
|
+
find_command_info(command.first)&.first_key_position.to_i
|
103
|
+
elsif command.first.casecmp('eval').zero?
|
104
|
+
3
|
105
|
+
elsif command.first.casecmp('evalsha').zero?
|
106
|
+
3
|
107
|
+
elsif command.first.casecmp('zinterstore').zero?
|
108
|
+
3
|
109
|
+
elsif command.first.casecmp('zunionstore').zero?
|
110
|
+
3
|
111
|
+
elsif command.first.casecmp('object').zero?
|
112
|
+
2
|
113
|
+
elsif command.first.casecmp('memory').zero?
|
96
114
|
command[1].to_s.casecmp('usage').zero? ? 2 : 0
|
97
|
-
|
115
|
+
elsif command.first.casecmp('migrate').zero?
|
98
116
|
command[3].empty? ? determine_optional_key_position(command, 'keys') : 3
|
99
|
-
|
117
|
+
elsif command.first.casecmp('xread').zero?
|
118
|
+
determine_optional_key_position(command, 'streams')
|
119
|
+
elsif command.first.casecmp('xreadgroup').zero?
|
100
120
|
determine_optional_key_position(command, 'streams')
|
101
121
|
else
|
102
|
-
|
122
|
+
find_command_info(command.first)&.first_key_position.to_i
|
103
123
|
end
|
104
124
|
end
|
105
125
|
|
106
126
|
def determine_optional_key_position(command, option_name)
|
107
|
-
idx = command.map { |e| e.to_s.downcase }.index(option_name
|
127
|
+
idx = command.map { |e| e.to_s.downcase(:ascii) }.index(option_name)
|
108
128
|
idx.nil? ? 0 : idx + 1
|
109
129
|
end
|
110
130
|
end
|
@@ -71,14 +71,12 @@ class RedisClient
|
|
71
71
|
|
72
72
|
module_function
|
73
73
|
|
74
|
-
def create(model: :
|
75
|
-
size = size.positive? ? size : 5
|
76
|
-
|
74
|
+
def create(model: :none, size: 5)
|
77
75
|
case model
|
78
|
-
when :on_demand, nil then ::RedisClient::Cluster::ConcurrentWorker::OnDemand.new(size: size)
|
79
|
-
when :pooled then ::RedisClient::Cluster::ConcurrentWorker::Pooled.new(size: size)
|
80
76
|
when :none then ::RedisClient::Cluster::ConcurrentWorker::None.new
|
81
|
-
|
77
|
+
when :on_demand then ::RedisClient::Cluster::ConcurrentWorker::OnDemand.new(size: size)
|
78
|
+
when :pooled then ::RedisClient::Cluster::ConcurrentWorker::Pooled.new(size: size)
|
79
|
+
else raise ArgumentError, "unknown model: #{model}"
|
82
80
|
end
|
83
81
|
end
|
84
82
|
end
|
@@ -43,11 +43,16 @@ class RedisClient
|
|
43
43
|
if !errors.is_a?(Hash) || errors.empty?
|
44
44
|
new(errors.to_s).with_errors(EMPTY_HASH)
|
45
45
|
else
|
46
|
-
messages = errors.map { |node_key, error| "#{node_key}: (#{error.class}) #{error.message}" }
|
46
|
+
messages = errors.map { |node_key, error| "#{node_key}: (#{error.class}) #{error.message}" }.freeze
|
47
47
|
new(messages.join(', ')).with_errors(errors)
|
48
48
|
end
|
49
49
|
end
|
50
50
|
|
51
|
+
def initialize(error_message = nil)
|
52
|
+
@errors = nil
|
53
|
+
super
|
54
|
+
end
|
55
|
+
|
51
56
|
def with_errors(errors)
|
52
57
|
@errors = errors if @errors.nil?
|
53
58
|
self
|
@@ -61,7 +66,7 @@ class RedisClient
|
|
61
66
|
end
|
62
67
|
|
63
68
|
class NodeMightBeDown < Error
|
64
|
-
def initialize(
|
69
|
+
def initialize(_error_message = nil)
|
65
70
|
super(
|
66
71
|
'The client is trying to fetch the latest cluster state ' \
|
67
72
|
'because a subset of nodes might be down. ' \
|
@@ -90,7 +90,7 @@ class RedisClient
|
|
90
90
|
|
91
91
|
def build_connection_prelude
|
92
92
|
prelude = super.dup
|
93
|
-
prelude << ['
|
93
|
+
prelude << ['readonly'] if @scale_read
|
94
94
|
prelude.freeze
|
95
95
|
end
|
96
96
|
end
|
@@ -309,7 +309,7 @@ class RedisClient
|
|
309
309
|
work_group.push(i, raw_client) do |client|
|
310
310
|
regular_timeout = client.read_timeout
|
311
311
|
client.read_timeout = @config.slow_command_timeout > 0.0 ? @config.slow_command_timeout : regular_timeout
|
312
|
-
reply = client.call_once('
|
312
|
+
reply = client.call_once('cluster', 'nodes')
|
313
313
|
client.read_timeout = regular_timeout
|
314
314
|
parse_cluster_node_reply(reply)
|
315
315
|
rescue StandardError => e
|
@@ -18,15 +18,15 @@ class RedisClient
|
|
18
18
|
handle_redirection(slot, retry_count: 1) do |nd|
|
19
19
|
nd.with do |c|
|
20
20
|
c.ensure_connected_cluster_scoped(retryable: false) do
|
21
|
-
c.call('
|
22
|
-
c.call('
|
21
|
+
c.call('asking') if @asking
|
22
|
+
c.call('watch', *keys)
|
23
23
|
begin
|
24
24
|
yield(c, slot, @asking)
|
25
25
|
rescue ::RedisClient::ConnectionError
|
26
26
|
# No need to unwatch on a connection error.
|
27
27
|
raise
|
28
28
|
rescue StandardError
|
29
|
-
c.call('
|
29
|
+
c.call('unwatch')
|
30
30
|
raise
|
31
31
|
end
|
32
32
|
rescue ::RedisClient::CommandError => e
|
@@ -2,7 +2,6 @@
|
|
2
2
|
|
3
3
|
require 'redis_client'
|
4
4
|
require 'redis_client/cluster/errors'
|
5
|
-
require 'redis_client/cluster/normalized_cmd_name'
|
6
5
|
|
7
6
|
class RedisClient
|
8
7
|
class Cluster
|
@@ -108,12 +107,21 @@ class RedisClient
|
|
108
107
|
|
109
108
|
private
|
110
109
|
|
111
|
-
def _call(command)
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
110
|
+
def _call(command) # rubocop:disable Metrics/AbcSize
|
111
|
+
if command.first.casecmp('subscribe').zero?
|
112
|
+
call_to_single_state(command)
|
113
|
+
elsif command.first.casecmp('psubscribe').zero?
|
114
|
+
call_to_single_state(command)
|
115
|
+
elsif command.first.casecmp('ssubscribe').zero?
|
116
|
+
call_to_single_state(command)
|
117
|
+
elsif command.first.casecmp('unsubscribe').zero?
|
118
|
+
call_to_all_states(command)
|
119
|
+
elsif command.first.casecmp('punsubscribe').zero?
|
120
|
+
call_to_all_states(command)
|
121
|
+
elsif command.first.casecmp('sunsubscribe').zero?
|
122
|
+
call_for_sharded_states(command)
|
123
|
+
else
|
124
|
+
call_to_single_state(command)
|
117
125
|
end
|
118
126
|
end
|
119
127
|
|
@@ -7,7 +7,6 @@ require 'redis_client/cluster/errors'
|
|
7
7
|
require 'redis_client/cluster/key_slot_converter'
|
8
8
|
require 'redis_client/cluster/node'
|
9
9
|
require 'redis_client/cluster/node_key'
|
10
|
-
require 'redis_client/cluster/normalized_cmd_name'
|
11
10
|
require 'redis_client/cluster/transaction'
|
12
11
|
require 'redis_client/cluster/optimistic_locking'
|
13
12
|
require 'redis_client/cluster/pipeline'
|
@@ -37,33 +36,78 @@ class RedisClient
|
|
37
36
|
raise
|
38
37
|
end
|
39
38
|
|
40
|
-
def send_command(method, command, *args, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
39
|
+
def send_command(method, command, *args, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity, Metrics/MethodLength
|
40
|
+
cmd_name = command.first
|
41
|
+
if cmd_name.casecmp('get').zero?
|
42
|
+
node = assign_node(command)
|
43
|
+
try_send(node, method, command, args, &block)
|
44
|
+
elsif cmd_name.casecmp('mget').zero?
|
45
|
+
send_multiple_keys_command(command.first, method, command, args, &block)
|
46
|
+
elsif cmd_name.casecmp('set').zero?
|
47
|
+
node = assign_node(command)
|
48
|
+
try_send(node, method, command, args, &block)
|
49
|
+
elsif cmd_name.casecmp('mset').zero?
|
50
|
+
send_multiple_keys_command(command.first, method, command, args, &block)
|
51
|
+
elsif cmd_name.casecmp('del').zero?
|
52
|
+
send_multiple_keys_command(command.first, method, command, args, &block)
|
53
|
+
elsif cmd_name.casecmp('ping').zero?
|
54
|
+
@node.send_ping(method, command, args).first.then(&TSF.call(block))
|
55
|
+
elsif cmd_name.casecmp('wait').zero?
|
56
|
+
send_wait_command(method, command, args, &block)
|
57
|
+
elsif cmd_name.casecmp('keys').zero?
|
58
|
+
@node.call_replicas(method, command, args).flatten.sort_by(&:to_s).then(&TSF.call(block))
|
59
|
+
elsif cmd_name.casecmp('dbsize').zero?
|
60
|
+
@node.call_replicas(method, command, args).select { |e| e.is_a?(Integer) }.sum.then(&TSF.call(block))
|
61
|
+
elsif cmd_name.casecmp('scan').zero?
|
62
|
+
scan(command, seed: 1)
|
63
|
+
elsif cmd_name.casecmp('lastsave').zero?
|
64
|
+
@node.call_all(method, command, args).sort_by(&:to_i).then(&TSF.call(block))
|
65
|
+
elsif cmd_name.casecmp('role').zero?
|
66
|
+
@node.call_all(method, command, args, &block)
|
67
|
+
elsif cmd_name.casecmp('config').zero?
|
68
|
+
send_config_command(method, command, args, &block)
|
69
|
+
elsif cmd_name.casecmp('client').zero?
|
70
|
+
send_client_command(method, command, args, &block)
|
71
|
+
elsif cmd_name.casecmp('cluster').zero?
|
72
|
+
send_cluster_command(method, command, args, &block)
|
73
|
+
elsif cmd_name.casecmp('memory').zero?
|
74
|
+
send_memory_command(method, command, args, &block)
|
75
|
+
elsif cmd_name.casecmp('script').zero?
|
76
|
+
send_script_command(method, command, args, &block)
|
77
|
+
elsif cmd_name.casecmp('pubsub').zero?
|
78
|
+
send_pubsub_command(method, command, args, &block)
|
79
|
+
elsif cmd_name.casecmp('watch').zero?
|
80
|
+
send_watch_command(command, &block)
|
81
|
+
elsif cmd_name.casecmp('acl').zero?
|
82
|
+
@node.call_all(method, command, args).first.then(&TSF.call(block))
|
83
|
+
elsif cmd_name.casecmp('auth').zero?
|
84
|
+
@node.call_all(method, command, args).first.then(&TSF.call(block))
|
85
|
+
elsif cmd_name.casecmp('bgrewriteaof').zero?
|
86
|
+
@node.call_all(method, command, args).first.then(&TSF.call(block))
|
87
|
+
elsif cmd_name.casecmp('bgsave').zero?
|
60
88
|
@node.call_all(method, command, args).first.then(&TSF.call(block))
|
61
|
-
|
89
|
+
elsif cmd_name.casecmp('quit').zero?
|
90
|
+
@node.call_all(method, command, args).first.then(&TSF.call(block))
|
91
|
+
elsif cmd_name.casecmp('save').zero?
|
92
|
+
@node.call_all(method, command, args).first.then(&TSF.call(block))
|
93
|
+
elsif cmd_name.casecmp('flushall').zero?
|
94
|
+
@node.call_primaries(method, command, args).first.then(&TSF.call(block))
|
95
|
+
elsif cmd_name.casecmp('flushdb').zero?
|
62
96
|
@node.call_primaries(method, command, args).first.then(&TSF.call(block))
|
63
|
-
|
64
|
-
raise ::RedisClient::Cluster::OrchestrationCommandNotSupported.from_command(
|
65
|
-
|
66
|
-
raise ::RedisClient::Cluster::
|
97
|
+
elsif cmd_name.casecmp('readonly').zero?
|
98
|
+
raise ::RedisClient::Cluster::OrchestrationCommandNotSupported.from_command(command.first).with_config(@config)
|
99
|
+
elsif cmd_name.casecmp('readwrite').zero?
|
100
|
+
raise ::RedisClient::Cluster::OrchestrationCommandNotSupported.from_command(command.first).with_config(@config)
|
101
|
+
elsif cmd_name.casecmp('shutdown').zero?
|
102
|
+
raise ::RedisClient::Cluster::OrchestrationCommandNotSupported.from_command(command.first).with_config(@config)
|
103
|
+
elsif cmd_name.casecmp('discard').zero?
|
104
|
+
raise ::RedisClient::Cluster::AmbiguousNodeError.from_command(command.first).with_config(@config)
|
105
|
+
elsif cmd_name.casecmp('exec').zero?
|
106
|
+
raise ::RedisClient::Cluster::AmbiguousNodeError.from_command(command.first).with_config(@config)
|
107
|
+
elsif cmd_name.casecmp('multi').zero?
|
108
|
+
raise ::RedisClient::Cluster::AmbiguousNodeError.from_command(command.first).with_config(@config)
|
109
|
+
elsif cmd_name.casecmp('unwatch').zero?
|
110
|
+
raise ::RedisClient::Cluster::AmbiguousNodeError.from_command(command.first).with_config(@config)
|
67
111
|
else
|
68
112
|
node = assign_node(command)
|
69
113
|
try_send(node, method, command, args, &block)
|
@@ -118,7 +162,7 @@ class RedisClient
|
|
118
162
|
elsif e.message.start_with?('ASK')
|
119
163
|
node = assign_asking_node(e.message)
|
120
164
|
if retry_count >= 0
|
121
|
-
node.call('
|
165
|
+
node.call('asking')
|
122
166
|
retry
|
123
167
|
end
|
124
168
|
elsif e.message.start_with?('CLUSTERDOWN')
|
@@ -280,75 +324,106 @@ class RedisClient
|
|
280
324
|
retry
|
281
325
|
end
|
282
326
|
|
283
|
-
def send_config_command(method, command, args, &block)
|
284
|
-
|
285
|
-
when 'resetstat', 'rewrite', 'set'
|
327
|
+
def send_config_command(method, command, args, &block) # rubocop:disable Metrics/AbcSize
|
328
|
+
if command[1].casecmp('resetstat').zero?
|
286
329
|
@node.call_all(method, command, args).first.then(&TSF.call(block))
|
287
|
-
|
330
|
+
elsif command[1].casecmp('rewrite').zero?
|
331
|
+
@node.call_all(method, command, args).first.then(&TSF.call(block))
|
332
|
+
elsif command[1].casecmp('set').zero?
|
333
|
+
@node.call_all(method, command, args).first.then(&TSF.call(block))
|
334
|
+
else
|
335
|
+
assign_node(command).public_send(method, *args, command, &block)
|
288
336
|
end
|
289
337
|
end
|
290
338
|
|
291
339
|
def send_memory_command(method, command, args, &block)
|
292
|
-
|
293
|
-
|
294
|
-
|
295
|
-
|
340
|
+
if command[1].casecmp('stats').zero?
|
341
|
+
@node.call_all(method, command, args, &block)
|
342
|
+
elsif command[1].casecmp('purge').zero?
|
343
|
+
@node.call_all(method, command, args).first.then(&TSF.call(block))
|
344
|
+
else
|
345
|
+
assign_node(command).public_send(method, *args, command, &block)
|
296
346
|
end
|
297
347
|
end
|
298
348
|
|
299
|
-
def send_client_command(method, command, args, &block)
|
300
|
-
|
301
|
-
|
302
|
-
|
349
|
+
def send_client_command(method, command, args, &block) # rubocop:disable Metrics/AbcSize
|
350
|
+
if command[1].casecmp('list').zero?
|
351
|
+
@node.call_all(method, command, args, &block).flatten
|
352
|
+
elsif command[1].casecmp('pause').zero?
|
353
|
+
@node.call_all(method, command, args).first.then(&TSF.call(block))
|
354
|
+
elsif command[1].casecmp('reply').zero?
|
303
355
|
@node.call_all(method, command, args).first.then(&TSF.call(block))
|
304
|
-
|
356
|
+
elsif command[1].casecmp('setname').zero?
|
357
|
+
@node.call_all(method, command, args).first.then(&TSF.call(block))
|
358
|
+
else
|
359
|
+
assign_node(command).public_send(method, *args, command, &block)
|
305
360
|
end
|
306
361
|
end
|
307
362
|
|
308
|
-
def send_cluster_command(method, command, args, &block) # rubocop:disable Metrics/AbcSize
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
raise ::RedisClient::Cluster::OrchestrationCommandNotSupported.from_command(['cluster',
|
313
|
-
|
314
|
-
|
363
|
+
def send_cluster_command(method, command, args, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
364
|
+
if command[1].casecmp('addslots').zero?
|
365
|
+
raise ::RedisClient::Cluster::OrchestrationCommandNotSupported.from_command(['cluster', command[1]]).with_config(@config)
|
366
|
+
elsif command[1].casecmp('delslots').zero?
|
367
|
+
raise ::RedisClient::Cluster::OrchestrationCommandNotSupported.from_command(['cluster', command[1]]).with_config(@config)
|
368
|
+
elsif command[1].casecmp('failover').zero?
|
369
|
+
raise ::RedisClient::Cluster::OrchestrationCommandNotSupported.from_command(['cluster', command[1]]).with_config(@config)
|
370
|
+
elsif command[1].casecmp('forget').zero?
|
371
|
+
raise ::RedisClient::Cluster::OrchestrationCommandNotSupported.from_command(['cluster', command[1]]).with_config(@config)
|
372
|
+
elsif command[1].casecmp('meet').zero?
|
373
|
+
raise ::RedisClient::Cluster::OrchestrationCommandNotSupported.from_command(['cluster', command[1]]).with_config(@config)
|
374
|
+
elsif command[1].casecmp('replicate').zero?
|
375
|
+
raise ::RedisClient::Cluster::OrchestrationCommandNotSupported.from_command(['cluster', command[1]]).with_config(@config)
|
376
|
+
elsif command[1].casecmp('reset').zero?
|
377
|
+
raise ::RedisClient::Cluster::OrchestrationCommandNotSupported.from_command(['cluster', command[1]]).with_config(@config)
|
378
|
+
elsif command[1].casecmp('set-config-epoch').zero?
|
379
|
+
raise ::RedisClient::Cluster::OrchestrationCommandNotSupported.from_command(['cluster', command[1]]).with_config(@config)
|
380
|
+
elsif command[1].casecmp('setslot').zero?
|
381
|
+
raise ::RedisClient::Cluster::OrchestrationCommandNotSupported.from_command(['cluster', command[1]]).with_config(@config)
|
382
|
+
elsif command[1].casecmp('saveconfig').zero?
|
383
|
+
@node.call_all(method, command, args).first.then(&TSF.call(block))
|
384
|
+
elsif command[1].casecmp('getkeysinslot').zero?
|
315
385
|
raise ArgumentError, command.join(' ') if command.size != 4
|
316
386
|
|
317
387
|
handle_node_reload_error do
|
318
388
|
node_key = @node.find_node_key_of_replica(command[2])
|
319
389
|
@node.find_by(node_key).public_send(method, *args, command, &block)
|
320
390
|
end
|
321
|
-
else
|
391
|
+
else
|
392
|
+
assign_node(command).public_send(method, *args, command, &block)
|
322
393
|
end
|
323
394
|
end
|
324
395
|
|
325
|
-
def send_script_command(method, command, args, &block) # rubocop:disable Metrics/AbcSize
|
326
|
-
|
327
|
-
|
396
|
+
def send_script_command(method, command, args, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
397
|
+
if command[1].casecmp('debug').zero?
|
398
|
+
@node.call_all(method, command, args).first.then(&TSF.call(block))
|
399
|
+
elsif command[1].casecmp('kill').zero?
|
328
400
|
@node.call_all(method, command, args).first.then(&TSF.call(block))
|
329
|
-
|
401
|
+
elsif command[1].casecmp('flush').zero?
|
402
|
+
@node.call_primaries(method, command, args).first.then(&TSF.call(block))
|
403
|
+
elsif command[1].casecmp('load').zero?
|
330
404
|
@node.call_primaries(method, command, args).first.then(&TSF.call(block))
|
331
|
-
|
405
|
+
elsif command[1].casecmp('exists').zero?
|
332
406
|
@node.call_all(method, command, args).transpose.map { |arr| arr.any?(&:zero?) ? 0 : 1 }.then(&TSF.call(block))
|
333
|
-
else
|
407
|
+
else
|
408
|
+
assign_node(command).public_send(method, *args, command, &block)
|
334
409
|
end
|
335
410
|
end
|
336
411
|
|
337
412
|
def send_pubsub_command(method, command, args, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
338
|
-
|
339
|
-
when 'channels'
|
413
|
+
if command[1].casecmp('channels').zero?
|
340
414
|
@node.call_all(method, command, args).flatten.uniq.sort_by(&:to_s).then(&TSF.call(block))
|
341
|
-
|
415
|
+
elsif command[1].casecmp('shardchannels').zero?
|
342
416
|
@node.call_replicas(method, command, args).flatten.uniq.sort_by(&:to_s).then(&TSF.call(block))
|
343
|
-
|
417
|
+
elsif command[1].casecmp('numpat').zero?
|
344
418
|
@node.call_all(method, command, args).select { |e| e.is_a?(Integer) }.sum.then(&TSF.call(block))
|
345
|
-
|
419
|
+
elsif command[1].casecmp('numsub').zero?
|
346
420
|
@node.call_all(method, command, args).reject(&:empty?).map { |e| Hash[*e] }
|
347
421
|
.reduce({}) { |a, e| a.merge(e) { |_, v1, v2| v1 + v2 } }.then(&TSF.call(block))
|
348
|
-
|
422
|
+
elsif command[1].casecmp('shardnumsub').zero?
|
349
423
|
@node.call_replicas(method, command, args).reject(&:empty?).map { |e| Hash[*e] }
|
350
424
|
.reduce({}) { |a, e| a.merge(e) { |_, v1, v2| v1 + v2 } }.then(&TSF.call(block))
|
351
|
-
else
|
425
|
+
else
|
426
|
+
assign_node(command).public_send(method, *args, command, &block)
|
352
427
|
end
|
353
428
|
end
|
354
429
|
|
@@ -369,17 +444,17 @@ class RedisClient
|
|
369
444
|
|
370
445
|
def send_multiple_keys_command(cmd, method, command, args, &block) # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
|
371
446
|
# This implementation is prioritized performance rather than readability or so.
|
372
|
-
|
373
|
-
when 'mget'
|
447
|
+
if cmd.casecmp('mget').zero?
|
374
448
|
single_key_cmd = 'get'
|
375
449
|
keys_step = 1
|
376
|
-
|
450
|
+
elsif cmd.casecmp('mset').zero?
|
377
451
|
single_key_cmd = 'set'
|
378
452
|
keys_step = 2
|
379
|
-
|
453
|
+
elsif cmd.casecmp('del').zero?
|
380
454
|
single_key_cmd = 'del'
|
381
455
|
keys_step = 1
|
382
|
-
else
|
456
|
+
else
|
457
|
+
raise NotImplementedError, cmd
|
383
458
|
end
|
384
459
|
|
385
460
|
return try_send(assign_node(command), method, command, args, &block) if command.size <= keys_step + 1 || ::RedisClient::Cluster::KeySlotConverter.hash_tag_included?(command[1])
|
@@ -402,10 +477,12 @@ class RedisClient
|
|
402
477
|
end
|
403
478
|
|
404
479
|
replies = pipeline.execute
|
405
|
-
result =
|
406
|
-
|
407
|
-
|
408
|
-
|
480
|
+
result = if cmd.casecmp('mset').zero?
|
481
|
+
replies.first
|
482
|
+
elsif cmd.casecmp('del').zero?
|
483
|
+
replies.sum
|
484
|
+
else
|
485
|
+
replies
|
409
486
|
end
|
410
487
|
block_given? ? yield(result) : result
|
411
488
|
end
|
@@ -93,24 +93,24 @@ class RedisClient
|
|
93
93
|
end
|
94
94
|
|
95
95
|
def prepare_tx
|
96
|
-
@pipeline.call('
|
96
|
+
@pipeline.call('multi')
|
97
97
|
@pending_commands.each(&:call)
|
98
98
|
@pending_commands.clear
|
99
99
|
end
|
100
100
|
|
101
101
|
def commit
|
102
|
-
@pipeline.call('
|
102
|
+
@pipeline.call('exec')
|
103
103
|
settle
|
104
104
|
end
|
105
105
|
|
106
106
|
def cancel
|
107
|
-
@pipeline.call('
|
107
|
+
@pipeline.call('discard')
|
108
108
|
settle
|
109
109
|
end
|
110
110
|
|
111
111
|
def settle
|
112
112
|
# If we needed ASKING on the watch, we need ASKING on the multi as well.
|
113
|
-
@node.call('
|
113
|
+
@node.call('asking') if @asking
|
114
114
|
# Don't handle redirections at this level if we're in a watch (the watcher handles redirections
|
115
115
|
# at the whole-transaction level.)
|
116
116
|
send_transaction(@node, redirect: !!@watching_slot ? 0 : MAX_REDIRECTION)
|
@@ -189,7 +189,7 @@ class RedisClient
|
|
189
189
|
end
|
190
190
|
|
191
191
|
def try_asking(node)
|
192
|
-
node.call('
|
192
|
+
node.call('asking') == 'OK'
|
193
193
|
rescue StandardError
|
194
194
|
false
|
195
195
|
end
|
data/lib/redis_client/cluster.rb
CHANGED
@@ -152,8 +152,9 @@ class RedisClient
|
|
152
152
|
end
|
153
153
|
|
154
154
|
def method_missing(name, *args, **kwargs, &block)
|
155
|
-
|
156
|
-
|
155
|
+
cmd = name.respond_to?(:name) ? name.name : name.to_s
|
156
|
+
if router.command_exists?(cmd)
|
157
|
+
args.unshift(cmd)
|
157
158
|
command = @command_builder.generate(args, kwargs)
|
158
159
|
return router.send_command(:call_v, command, &block)
|
159
160
|
end
|
@@ -19,7 +19,7 @@ class RedisClient
|
|
19
19
|
VALID_NODES_KEYS = %i[ssl username password host port db].freeze
|
20
20
|
MERGE_CONFIG_KEYS = %i[ssl username password].freeze
|
21
21
|
IGNORE_GENERIC_CONFIG_KEYS = %i[url host port path].freeze
|
22
|
-
MAX_WORKERS = Integer(ENV.fetch('REDIS_CLIENT_MAX_THREADS',
|
22
|
+
MAX_WORKERS = Integer(ENV.fetch('REDIS_CLIENT_MAX_THREADS', -1)) # for backward compatibility
|
23
23
|
# It's used with slow queries of fetching meta data like CLUSTER NODES, COMMAND and so on.
|
24
24
|
SLOW_COMMAND_TIMEOUT = Float(ENV.fetch('REDIS_CLIENT_SLOW_COMMAND_TIMEOUT', -1))
|
25
25
|
# It affects to strike a balance between load and stability in initialization or changed states.
|
@@ -110,12 +110,16 @@ class RedisClient
|
|
110
110
|
private
|
111
111
|
|
112
112
|
def merge_concurrency_option(option)
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
113
|
+
opts = {}
|
114
|
+
|
115
|
+
if MAX_WORKERS.positive?
|
116
|
+
opts[:model] = :on_demand
|
117
|
+
opts[:size] = MAX_WORKERS
|
118
118
|
end
|
119
|
+
|
120
|
+
opts.merge!(option.transform_keys(&:to_sym)) if option.is_a?(Hash)
|
121
|
+
opts[:model] = :none if opts.empty?
|
122
|
+
opts.freeze
|
119
123
|
end
|
120
124
|
|
121
125
|
def build_node_configs(addrs)
|
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.13.0
|
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-
|
11
|
+
date: 2024-11-20 00:00:00.000000000 Z
|
12
12
|
dependencies:
|
13
13
|
- !ruby/object:Gem::Dependency
|
14
14
|
name: redis-client
|
@@ -49,7 +49,6 @@ files:
|
|
49
49
|
- lib/redis_client/cluster/node/random_replica_or_primary.rb
|
50
50
|
- lib/redis_client/cluster/node_key.rb
|
51
51
|
- lib/redis_client/cluster/noop_command_builder.rb
|
52
|
-
- lib/redis_client/cluster/normalized_cmd_name.rb
|
53
52
|
- lib/redis_client/cluster/optimistic_locking.rb
|
54
53
|
- lib/redis_client/cluster/pipeline.rb
|
55
54
|
- lib/redis_client/cluster/pub_sub.rb
|
@@ -1,71 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require 'singleton'
|
4
|
-
|
5
|
-
class RedisClient
|
6
|
-
class Cluster
|
7
|
-
class NormalizedCmdName
|
8
|
-
include Singleton
|
9
|
-
|
10
|
-
EMPTY_STRING = ''
|
11
|
-
|
12
|
-
private_constant :EMPTY_STRING
|
13
|
-
|
14
|
-
def initialize
|
15
|
-
@cache = {}
|
16
|
-
@mutex = Mutex.new
|
17
|
-
end
|
18
|
-
|
19
|
-
def get_by_command(command)
|
20
|
-
get(command, index: 0)
|
21
|
-
end
|
22
|
-
|
23
|
-
def get_by_subcommand(command)
|
24
|
-
get(command, index: 1)
|
25
|
-
end
|
26
|
-
|
27
|
-
def get_by_name(name)
|
28
|
-
get(name, index: 0)
|
29
|
-
end
|
30
|
-
|
31
|
-
def clear
|
32
|
-
@mutex.synchronize { @cache.clear }
|
33
|
-
true
|
34
|
-
end
|
35
|
-
|
36
|
-
private
|
37
|
-
|
38
|
-
def get(command, index:)
|
39
|
-
name = extract_name(command, index: index)
|
40
|
-
return EMPTY_STRING if name.nil? || name.empty?
|
41
|
-
|
42
|
-
normalize(name)
|
43
|
-
end
|
44
|
-
|
45
|
-
def extract_name(command, index:)
|
46
|
-
case command
|
47
|
-
when String, Symbol then index.zero? ? command : nil
|
48
|
-
when Array then extract_name_from_array(command, index: index)
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
|
-
def extract_name_from_array(command, index:)
|
53
|
-
return if command.size - 1 < index
|
54
|
-
|
55
|
-
case e = command[index]
|
56
|
-
when String, Symbol then e
|
57
|
-
when Array then e[index]
|
58
|
-
end
|
59
|
-
end
|
60
|
-
|
61
|
-
def normalize(name)
|
62
|
-
return @cache[name] || name.to_s.downcase if @cache.key?(name)
|
63
|
-
return name.to_s.downcase if @mutex.locked?
|
64
|
-
|
65
|
-
str = name.to_s.downcase
|
66
|
-
@mutex.synchronize { @cache[name] = str }
|
67
|
-
str
|
68
|
-
end
|
69
|
-
end
|
70
|
-
end
|
71
|
-
end
|