redis 4.0.2 → 4.0.3
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/.gitignore +3 -0
- data/.travis.yml +2 -2
- data/CHANGELOG.md +6 -0
- data/lib/redis.rb +97 -11
- data/lib/redis/client.rb +19 -11
- data/lib/redis/cluster.rb +285 -0
- data/lib/redis/cluster/command.rb +81 -0
- data/lib/redis/cluster/command_loader.rb +32 -0
- data/lib/redis/cluster/key_slot_converter.rb +72 -0
- data/lib/redis/cluster/node.rb +104 -0
- data/lib/redis/cluster/node_key.rb +35 -0
- data/lib/redis/cluster/node_loader.rb +35 -0
- data/lib/redis/cluster/option.rb +76 -0
- data/lib/redis/cluster/slot.rb +69 -0
- data/lib/redis/cluster/slot_loader.rb +47 -0
- data/lib/redis/errors.rb +46 -0
- data/lib/redis/version.rb +1 -1
- data/makefile +54 -16
- data/redis.gemspec +2 -1
- data/test/client_test.rb +17 -0
- data/test/cluster_abnormal_state_test.rb +38 -0
- data/test/cluster_blocking_commands_test.rb +15 -0
- data/test/cluster_client_internals_test.rb +77 -0
- data/test/cluster_client_key_hash_tags_test.rb +88 -0
- data/test/cluster_client_options_test.rb +147 -0
- data/test/cluster_client_pipelining_test.rb +59 -0
- data/test/cluster_client_replicas_test.rb +36 -0
- data/test/cluster_client_slots_test.rb +94 -0
- data/test/cluster_client_transactions_test.rb +71 -0
- data/test/cluster_commands_on_cluster_test.rb +165 -0
- data/test/cluster_commands_on_connection_test.rb +40 -0
- data/test/cluster_commands_on_geo_test.rb +74 -0
- data/test/cluster_commands_on_hashes_test.rb +11 -0
- data/test/cluster_commands_on_hyper_log_log_test.rb +17 -0
- data/test/cluster_commands_on_keys_test.rb +134 -0
- data/test/cluster_commands_on_lists_test.rb +15 -0
- data/test/cluster_commands_on_pub_sub_test.rb +101 -0
- data/test/cluster_commands_on_scripting_test.rb +56 -0
- data/test/cluster_commands_on_server_test.rb +221 -0
- data/test/cluster_commands_on_sets_test.rb +39 -0
- data/test/cluster_commands_on_sorted_sets_test.rb +35 -0
- data/test/cluster_commands_on_streams_test.rb +196 -0
- data/test/cluster_commands_on_strings_test.rb +15 -0
- data/test/cluster_commands_on_transactions_test.rb +41 -0
- data/test/cluster_commands_on_value_types_test.rb +14 -0
- data/test/commands_on_hashes_test.rb +2 -14
- data/test/commands_on_hyper_log_log_test.rb +2 -14
- data/test/commands_on_lists_test.rb +2 -13
- data/test/commands_on_sets_test.rb +2 -70
- data/test/commands_on_sorted_sets_test.rb +2 -145
- data/test/commands_on_strings_test.rb +2 -94
- data/test/distributed_blocking_commands_test.rb +8 -0
- data/test/distributed_commands_on_hashes_test.rb +16 -3
- data/test/distributed_commands_on_hyper_log_log_test.rb +8 -13
- data/test/distributed_commands_on_lists_test.rb +4 -5
- data/test/distributed_commands_on_sets_test.rb +45 -46
- data/test/distributed_commands_on_sorted_sets_test.rb +51 -8
- data/test/distributed_commands_on_strings_test.rb +10 -0
- data/test/helper.rb +176 -32
- data/test/internals_test.rb +13 -0
- data/test/lint/blocking_commands.rb +40 -16
- data/test/lint/hashes.rb +26 -0
- data/test/lint/hyper_log_log.rb +15 -1
- data/test/lint/lists.rb +16 -0
- data/test/lint/sets.rb +142 -0
- data/test/lint/sorted_sets.rb +183 -2
- data/test/lint/strings.rb +102 -0
- data/test/support/cluster/orchestrator.rb +199 -0
- metadata +79 -4
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 1ec178932a6874e8ac7a4f6cfa4390c8223367ec
|
4
|
+
data.tar.gz: 986e6c5d1729b9ec8f7d66900e5d9ad9437e29f9
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 14db8cb42f08014ebd942c39b5f119a68a8ec50fec046572bb3afc7708634589311b9bea0d4153bfd1d7cb1c46a94a00d77613872b2cb8c10c33f132755cd62d
|
7
|
+
data.tar.gz: efe0db683450fcb30b6d23b8c9448d6925b5c5d95b847f9563405a61cc7e667bbf8aea22642dcc9d8a1b716dc9c8cb55c562c86a08223e020991f69eff7df7f7
|
data/.gitignore
CHANGED
data/.travis.yml
CHANGED
@@ -6,7 +6,7 @@ before_install:
|
|
6
6
|
- gem update --system 2.6.14
|
7
7
|
- gem --version
|
8
8
|
|
9
|
-
script: make
|
9
|
+
script: make
|
10
10
|
|
11
11
|
rvm:
|
12
12
|
- 2.2.2
|
@@ -25,7 +25,7 @@ before_script:
|
|
25
25
|
env:
|
26
26
|
global:
|
27
27
|
- VERBOSE=true
|
28
|
-
- TIMEOUT=
|
28
|
+
- TIMEOUT=9
|
29
29
|
matrix:
|
30
30
|
- DRIVER=ruby REDIS_BRANCH=3.0
|
31
31
|
- DRIVER=ruby REDIS_BRANCH=3.2
|
data/CHANGELOG.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
# 4.0.3
|
2
|
+
|
3
|
+
* Fix raising command error for first command in pipeline. See #788.
|
4
|
+
* Fix the gemspec to stop exposing a `build` executable. See #785.
|
5
|
+
* Add `:reconnect_delay` and `:reconnect_delay_max` options. See #778.
|
6
|
+
|
1
7
|
# 4.0.2
|
2
8
|
|
3
9
|
* Added `Redis#unlink`. See #766.
|
data/lib/redis.rb
CHANGED
@@ -31,11 +31,16 @@ class Redis
|
|
31
31
|
# @option options [Boolean] :inherit_socket (false) Whether to use socket in forked process or not
|
32
32
|
# @option options [Array] :sentinels List of sentinels to contact
|
33
33
|
# @option options [Symbol] :role (:master) Role to fetch via Sentinel, either `:master` or `:slave`
|
34
|
+
# @option options [Array<String, Hash{Symbol => String, Integer}>] :cluster List of cluster nodes to contact
|
35
|
+
# @option options [Boolean] :replica Whether to use readonly replica nodes in Redis Cluster or not
|
36
|
+
# @option options [Class] :connector Class of custom connector
|
34
37
|
#
|
35
38
|
# @return [Redis] a new client instance
|
36
39
|
def initialize(options = {})
|
37
40
|
@options = options.dup
|
38
|
-
@
|
41
|
+
@cluster_mode = options.key?(:cluster)
|
42
|
+
client = @cluster_mode ? Cluster : Client
|
43
|
+
@original_client = @client = client.new(options)
|
39
44
|
@queue = Hash.new { |h, k| h[k] = [] }
|
40
45
|
|
41
46
|
super() # Monitor#initialize
|
@@ -273,9 +278,7 @@ class Redis
|
|
273
278
|
synchronize do |client|
|
274
279
|
client.call([:info, cmd].compact) do |reply|
|
275
280
|
if reply.kind_of?(String)
|
276
|
-
reply =
|
277
|
-
line.split(":", 2) unless line =~ /^(#|$)/
|
278
|
-
end.compact]
|
281
|
+
reply = HashifyInfo.call(reply)
|
279
282
|
|
280
283
|
if cmd && cmd.to_s == "commandstats"
|
281
284
|
# Extract nested hashes for INFO COMMANDSTATS
|
@@ -2827,6 +2830,41 @@ class Redis
|
|
2827
2830
|
end
|
2828
2831
|
end
|
2829
2832
|
|
2833
|
+
# Sends `CLUSTER *` command to random node and returns its reply.
|
2834
|
+
#
|
2835
|
+
# @see https://redis.io/commands#cluster Reference of cluster command
|
2836
|
+
#
|
2837
|
+
# @param subcommand [String, Symbol] the subcommand of cluster command
|
2838
|
+
# e.g. `:slots`, `:nodes`, `:slaves`, `:info`
|
2839
|
+
#
|
2840
|
+
# @return [Object] depends on the subcommand
|
2841
|
+
def cluster(subcommand, *args)
|
2842
|
+
subcommand = subcommand.to_s.downcase
|
2843
|
+
block = case subcommand
|
2844
|
+
when 'slots' then HashifyClusterSlots
|
2845
|
+
when 'nodes' then HashifyClusterNodes
|
2846
|
+
when 'slaves' then HashifyClusterSlaves
|
2847
|
+
when 'info' then HashifyInfo
|
2848
|
+
else Noop
|
2849
|
+
end
|
2850
|
+
|
2851
|
+
# @see https://github.com/antirez/redis/blob/unstable/src/redis-trib.rb#L127 raw reply expected
|
2852
|
+
block = Noop unless @cluster_mode
|
2853
|
+
|
2854
|
+
synchronize do |client|
|
2855
|
+
client.call([:cluster, subcommand] + args, &block)
|
2856
|
+
end
|
2857
|
+
end
|
2858
|
+
|
2859
|
+
# Sends `ASKING` command to random node and returns its reply.
|
2860
|
+
#
|
2861
|
+
# @see https://redis.io/topics/cluster-spec#ask-redirection ASK redirection
|
2862
|
+
#
|
2863
|
+
# @return [String] `'OK'`
|
2864
|
+
def asking
|
2865
|
+
synchronize { |client| client.call(%i[asking]) }
|
2866
|
+
end
|
2867
|
+
|
2830
2868
|
def id
|
2831
2869
|
@original_client.id
|
2832
2870
|
end
|
@@ -2840,6 +2878,8 @@ class Redis
|
|
2840
2878
|
end
|
2841
2879
|
|
2842
2880
|
def connection
|
2881
|
+
return @original_client.connection_info if @cluster_mode
|
2882
|
+
|
2843
2883
|
{
|
2844
2884
|
host: @original_client.host,
|
2845
2885
|
port: @original_client.port,
|
@@ -2896,15 +2936,61 @@ private
|
|
2896
2936
|
|
2897
2937
|
FloatifyPairs =
|
2898
2938
|
lambda { |result|
|
2899
|
-
|
2900
|
-
|
2901
|
-
|
2902
|
-
|
2903
|
-
|
2904
|
-
|
2939
|
+
result.each_slice(2).map do |member, score|
|
2940
|
+
[member, Floatify.call(score)]
|
2941
|
+
end
|
2942
|
+
}
|
2943
|
+
|
2944
|
+
HashifyInfo =
|
2945
|
+
lambda { |reply|
|
2946
|
+
Hash[reply.split("\r\n").map do |line|
|
2947
|
+
line.split(':', 2) unless line =~ /^(#|$)/
|
2948
|
+
end.compact]
|
2949
|
+
}
|
2950
|
+
|
2951
|
+
HashifyClusterNodeInfo =
|
2952
|
+
lambda { |str|
|
2953
|
+
arr = str.split(' ')
|
2954
|
+
{
|
2955
|
+
'node_id' => arr[0],
|
2956
|
+
'ip_port' => arr[1],
|
2957
|
+
'flags' => arr[2].split(','),
|
2958
|
+
'master_node_id' => arr[3],
|
2959
|
+
'ping_sent' => arr[4],
|
2960
|
+
'pong_recv' => arr[5],
|
2961
|
+
'config_epoch' => arr[6],
|
2962
|
+
'link_state' => arr[7],
|
2963
|
+
'slots' => arr[8].nil? ? nil : Range.new(*arr[8].split('-'))
|
2964
|
+
}
|
2965
|
+
}
|
2966
|
+
|
2967
|
+
HashifyClusterSlots =
|
2968
|
+
lambda { |reply|
|
2969
|
+
reply.map do |arr|
|
2970
|
+
first_slot, last_slot = arr[0..1]
|
2971
|
+
master = { 'ip' => arr[2][0], 'port' => arr[2][1], 'node_id' => arr[2][2] }
|
2972
|
+
replicas = arr[3..-1].map { |r| { 'ip' => r[0], 'port' => r[1], 'node_id' => r[2] } }
|
2973
|
+
{
|
2974
|
+
'start_slot' => first_slot,
|
2975
|
+
'end_slot' => last_slot,
|
2976
|
+
'master' => master,
|
2977
|
+
'replicas' => replicas
|
2978
|
+
}
|
2905
2979
|
end
|
2906
2980
|
}
|
2907
2981
|
|
2982
|
+
HashifyClusterNodes =
|
2983
|
+
lambda { |reply|
|
2984
|
+
reply.split(/[\r\n]+/).map { |str| HashifyClusterNodeInfo.call(str) }
|
2985
|
+
}
|
2986
|
+
|
2987
|
+
HashifyClusterSlaves =
|
2988
|
+
lambda { |reply|
|
2989
|
+
reply.map { |str| HashifyClusterNodeInfo.call(str) }
|
2990
|
+
}
|
2991
|
+
|
2992
|
+
Noop = ->(reply) { reply }
|
2993
|
+
|
2908
2994
|
def _geoarguments(*args, options: nil, sort: nil, count: nil)
|
2909
2995
|
args.push sort if sort
|
2910
2996
|
args.push 'count', count if count
|
@@ -2927,11 +3013,11 @@ private
|
|
2927
3013
|
@client = original
|
2928
3014
|
end
|
2929
3015
|
end
|
2930
|
-
|
2931
3016
|
end
|
2932
3017
|
|
2933
3018
|
require_relative "redis/version"
|
2934
3019
|
require_relative "redis/connection"
|
2935
3020
|
require_relative "redis/client"
|
3021
|
+
require_relative "redis/cluster"
|
2936
3022
|
require_relative "redis/pipeline"
|
2937
3023
|
require_relative "redis/subscribe"
|
data/lib/redis/client.rb
CHANGED
@@ -18,6 +18,8 @@ class Redis
|
|
18
18
|
:id => nil,
|
19
19
|
:tcp_keepalive => 0,
|
20
20
|
:reconnect_attempts => 1,
|
21
|
+
:reconnect_delay => 0,
|
22
|
+
:reconnect_delay_max => 0.5,
|
21
23
|
:inherit_socket => false
|
22
24
|
}
|
23
25
|
|
@@ -84,11 +86,14 @@ class Redis
|
|
84
86
|
|
85
87
|
@pending_reads = 0
|
86
88
|
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
89
|
+
@connector =
|
90
|
+
if options.include?(:sentinels)
|
91
|
+
Connector::Sentinel.new(@options)
|
92
|
+
elsif options.include?(:connector) && options[:connector].respond_to?(:new)
|
93
|
+
options.delete(:connector).new(@options)
|
94
|
+
else
|
95
|
+
Connector.new(@options)
|
96
|
+
end
|
92
97
|
end
|
93
98
|
|
94
99
|
def connect
|
@@ -186,13 +191,10 @@ class Redis
|
|
186
191
|
exception = nil
|
187
192
|
|
188
193
|
process(commands) do
|
189
|
-
|
190
|
-
|
191
|
-
@reconnect = false
|
192
|
-
|
193
|
-
(commands.size - 1).times do |i|
|
194
|
+
commands.size.times do |i|
|
194
195
|
reply = read
|
195
|
-
result[i
|
196
|
+
result[i] = reply
|
197
|
+
@reconnect = false
|
196
198
|
exception = reply if exception.nil? && reply.is_a?(CommandError)
|
197
199
|
end
|
198
200
|
end
|
@@ -372,6 +374,10 @@ class Redis
|
|
372
374
|
disconnect
|
373
375
|
|
374
376
|
if attempts <= @options[:reconnect_attempts] && @reconnect
|
377
|
+
sleep_t = [(@options[:reconnect_delay] * 2**(attempts-1)),
|
378
|
+
@options[:reconnect_delay_max]].min
|
379
|
+
|
380
|
+
Kernel.sleep(sleep_t)
|
375
381
|
retry
|
376
382
|
else
|
377
383
|
raise
|
@@ -449,6 +455,8 @@ class Redis
|
|
449
455
|
options[:write_timeout] = Float(options[:write_timeout])
|
450
456
|
|
451
457
|
options[:reconnect_attempts] = options[:reconnect_attempts].to_i
|
458
|
+
options[:reconnect_delay] = options[:reconnect_delay].to_f
|
459
|
+
options[:reconnect_delay_max] = options[:reconnect_delay_max].to_f
|
452
460
|
|
453
461
|
options[:db] = options[:db].to_i
|
454
462
|
options[:driver] = _parse_driver(options[:driver]) || Connection.drivers.last
|
@@ -0,0 +1,285 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'errors'
|
4
|
+
require_relative 'client'
|
5
|
+
require_relative 'cluster/command'
|
6
|
+
require_relative 'cluster/command_loader'
|
7
|
+
require_relative 'cluster/key_slot_converter'
|
8
|
+
require_relative 'cluster/node'
|
9
|
+
require_relative 'cluster/node_key'
|
10
|
+
require_relative 'cluster/node_loader'
|
11
|
+
require_relative 'cluster/option'
|
12
|
+
require_relative 'cluster/slot'
|
13
|
+
require_relative 'cluster/slot_loader'
|
14
|
+
|
15
|
+
class Redis
|
16
|
+
# Redis Cluster client
|
17
|
+
#
|
18
|
+
# @see https://github.com/antirez/redis-rb-cluster POC implementation
|
19
|
+
# @see https://redis.io/topics/cluster-spec Redis Cluster specification
|
20
|
+
# @see https://redis.io/topics/cluster-tutorial Redis Cluster tutorial
|
21
|
+
#
|
22
|
+
# Copyright (C) 2013 Salvatore Sanfilippo <antirez@gmail.com>
|
23
|
+
class Cluster
|
24
|
+
def initialize(options = {})
|
25
|
+
@option = Option.new(options)
|
26
|
+
@node, @slot = fetch_cluster_info!(@option)
|
27
|
+
@command = fetch_command_details(@node)
|
28
|
+
end
|
29
|
+
|
30
|
+
def id
|
31
|
+
@node.map(&:id).sort.join(' ')
|
32
|
+
end
|
33
|
+
|
34
|
+
# db feature is disabled in cluster mode
|
35
|
+
def db
|
36
|
+
0
|
37
|
+
end
|
38
|
+
|
39
|
+
# db feature is disabled in cluster mode
|
40
|
+
def db=(_db); end
|
41
|
+
|
42
|
+
def timeout
|
43
|
+
@node.first.timeout
|
44
|
+
end
|
45
|
+
|
46
|
+
def connected?
|
47
|
+
@node.any?(&:connected?)
|
48
|
+
end
|
49
|
+
|
50
|
+
def disconnect
|
51
|
+
@node.each(&:disconnect)
|
52
|
+
true
|
53
|
+
end
|
54
|
+
|
55
|
+
def connection_info
|
56
|
+
@node.sort_by(&:id).map do |client|
|
57
|
+
{
|
58
|
+
host: client.host,
|
59
|
+
port: client.port,
|
60
|
+
db: client.db,
|
61
|
+
id: client.id,
|
62
|
+
location: client.location
|
63
|
+
}
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def with_reconnect(val = true, &block)
|
68
|
+
try_send(@node.sample, :with_reconnect, val, &block)
|
69
|
+
end
|
70
|
+
|
71
|
+
def call(command, &block)
|
72
|
+
send_command(command, &block)
|
73
|
+
end
|
74
|
+
|
75
|
+
def call_loop(command, timeout = 0, &block)
|
76
|
+
node = assign_node(command)
|
77
|
+
try_send(node, :call_loop, command, timeout, &block)
|
78
|
+
end
|
79
|
+
|
80
|
+
def call_pipeline(pipeline)
|
81
|
+
node_keys, command_keys = extract_keys_in_pipeline(pipeline)
|
82
|
+
raise CrossSlotPipeliningError, command_keys if node_keys.size > 1
|
83
|
+
node = find_node(node_keys.first)
|
84
|
+
try_send(node, :call_pipeline, pipeline)
|
85
|
+
end
|
86
|
+
|
87
|
+
def call_with_timeout(command, timeout, &block)
|
88
|
+
node = assign_node(command)
|
89
|
+
try_send(node, :call_with_timeout, command, timeout, &block)
|
90
|
+
end
|
91
|
+
|
92
|
+
def call_without_timeout(command, &block)
|
93
|
+
call_with_timeout(command, 0, &block)
|
94
|
+
end
|
95
|
+
|
96
|
+
def process(commands, &block)
|
97
|
+
if commands.size == 1 &&
|
98
|
+
%w[unsubscribe punsubscribe].include?(commands.first.first.to_s.downcase) &&
|
99
|
+
commands.first.size == 1
|
100
|
+
|
101
|
+
# Node is indeterminate. We do just a best-effort try here.
|
102
|
+
@node.process_all(commands, &block)
|
103
|
+
else
|
104
|
+
node = assign_node(commands.first)
|
105
|
+
try_send(node, :process, commands, &block)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
|
109
|
+
private
|
110
|
+
|
111
|
+
def fetch_cluster_info!(option)
|
112
|
+
node = Node.new(option.per_node_key)
|
113
|
+
available_slots = SlotLoader.load(node)
|
114
|
+
node_flags = NodeLoader.load_flags(node)
|
115
|
+
available_node_urls = NodeKey.to_node_urls(available_slots.keys, secure: option.secure?)
|
116
|
+
option.update_node(available_node_urls)
|
117
|
+
[Node.new(option.per_node_key, node_flags, option.use_replica?),
|
118
|
+
Slot.new(available_slots, node_flags, option.use_replica?)]
|
119
|
+
ensure
|
120
|
+
node.map(&:disconnect)
|
121
|
+
end
|
122
|
+
|
123
|
+
def fetch_command_details(nodes)
|
124
|
+
details = CommandLoader.load(nodes)
|
125
|
+
Command.new(details)
|
126
|
+
end
|
127
|
+
|
128
|
+
def send_command(command, &block)
|
129
|
+
cmd = command.first.to_s.downcase
|
130
|
+
case cmd
|
131
|
+
when 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save'
|
132
|
+
@node.call_all(command, &block).first
|
133
|
+
when 'flushall', 'flushdb'
|
134
|
+
@node.call_master(command, &block).first
|
135
|
+
when 'keys' then @node.call_slave(command, &block).flatten.sort
|
136
|
+
when 'dbsize' then @node.call_slave(command, &block).reduce(:+)
|
137
|
+
when 'lastsave' then @node.call_all(command, &block).sort
|
138
|
+
when 'role' then @node.call_all(command, &block)
|
139
|
+
when 'config' then send_config_command(command, &block)
|
140
|
+
when 'client' then send_client_command(command, &block)
|
141
|
+
when 'cluster' then send_cluster_command(command, &block)
|
142
|
+
when 'readonly', 'readwrite', 'shutdown'
|
143
|
+
raise OrchestrationCommandNotSupported, cmd
|
144
|
+
when 'memory' then send_memory_command(command, &block)
|
145
|
+
when 'script' then send_script_command(command, &block)
|
146
|
+
when 'pubsub' then send_pubsub_command(command, &block)
|
147
|
+
when 'discard', 'exec', 'multi', 'unwatch'
|
148
|
+
raise AmbiguousNodeError, cmd
|
149
|
+
else
|
150
|
+
node = assign_node(command)
|
151
|
+
try_send(node, :call, command, &block)
|
152
|
+
end
|
153
|
+
end
|
154
|
+
|
155
|
+
def send_config_command(command, &block)
|
156
|
+
case command[1].to_s.downcase
|
157
|
+
when 'resetstat', 'rewrite', 'set'
|
158
|
+
@node.call_all(command, &block).first
|
159
|
+
else assign_node(command).call(command, &block)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
def send_memory_command(command, &block)
|
164
|
+
case command[1].to_s.downcase
|
165
|
+
when 'stats' then @node.call_all(command, &block)
|
166
|
+
when 'purge' then @node.call_all(command, &block).first
|
167
|
+
else assign_node(command).call(command, &block)
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
def send_client_command(command, &block)
|
172
|
+
case command[1].to_s.downcase
|
173
|
+
when 'list' then @node.call_all(command, &block).flatten
|
174
|
+
when 'pause', 'reply', 'setname'
|
175
|
+
@node.call_all(command, &block).first
|
176
|
+
else assign_node(command).call(command, &block)
|
177
|
+
end
|
178
|
+
end
|
179
|
+
|
180
|
+
def send_cluster_command(command, &block)
|
181
|
+
subcommand = command[1].to_s.downcase
|
182
|
+
case subcommand
|
183
|
+
when 'addslots', 'delslots', 'failover', 'forget', 'meet', 'replicate',
|
184
|
+
'reset', 'set-config-epoch', 'setslot'
|
185
|
+
raise OrchestrationCommandNotSupported, 'cluster', subcommand
|
186
|
+
when 'saveconfig' then @node.call_all(command, &block).first
|
187
|
+
else assign_node(command).call(command, &block)
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
def send_script_command(command, &block)
|
192
|
+
case command[1].to_s.downcase
|
193
|
+
when 'debug', 'kill'
|
194
|
+
@node.call_all(command, &block).first
|
195
|
+
when 'flush', 'load'
|
196
|
+
@node.call_master(command, &block).first
|
197
|
+
else assign_node(command).call(command, &block)
|
198
|
+
end
|
199
|
+
end
|
200
|
+
|
201
|
+
def send_pubsub_command(command, &block)
|
202
|
+
case command[1].to_s.downcase
|
203
|
+
when 'channels' then @node.call_all(command, &block).flatten.uniq.sort
|
204
|
+
when 'numsub'
|
205
|
+
@node.call_all(command, &block).reject(&:empty?).map { |e| Hash[*e] }
|
206
|
+
.reduce({}) { |a, e| a.merge(e) { |_, v1, v2| v1 + v2 } }
|
207
|
+
when 'numpat' then @node.call_all(command, &block).reduce(:+)
|
208
|
+
else assign_node(command).call(command, &block)
|
209
|
+
end
|
210
|
+
end
|
211
|
+
|
212
|
+
# @see https://redis.io/topics/cluster-spec#redirection-and-resharding
|
213
|
+
# Redirection and resharding
|
214
|
+
def try_send(node, method_name, *args, retry_count: 3, &block)
|
215
|
+
node.public_send(method_name, *args, &block)
|
216
|
+
rescue CommandError => err
|
217
|
+
if err.message.start_with?('MOVED')
|
218
|
+
assign_redirection_node(err.message).public_send(method_name, *args, &block)
|
219
|
+
elsif err.message.start_with?('ASK')
|
220
|
+
raise if retry_count <= 0
|
221
|
+
node = assign_asking_node(err.message)
|
222
|
+
node.call(%i[asking])
|
223
|
+
retry_count -= 1
|
224
|
+
retry
|
225
|
+
else
|
226
|
+
raise
|
227
|
+
end
|
228
|
+
end
|
229
|
+
|
230
|
+
def assign_redirection_node(err_msg)
|
231
|
+
_, slot, node_key = err_msg.split(' ')
|
232
|
+
slot = slot.to_i
|
233
|
+
@slot.put(slot, node_key)
|
234
|
+
find_node(node_key)
|
235
|
+
end
|
236
|
+
|
237
|
+
def assign_asking_node(err_msg)
|
238
|
+
_, _, node_key = err_msg.split(' ')
|
239
|
+
find_node(node_key)
|
240
|
+
end
|
241
|
+
|
242
|
+
def assign_node(command)
|
243
|
+
node_key = find_node_key(command)
|
244
|
+
find_node(node_key)
|
245
|
+
end
|
246
|
+
|
247
|
+
def find_node_key(command)
|
248
|
+
key = @command.extract_first_key(command)
|
249
|
+
return if key.empty?
|
250
|
+
|
251
|
+
slot = KeySlotConverter.convert(key)
|
252
|
+
return unless @slot.exists?(slot)
|
253
|
+
|
254
|
+
if @command.should_send_to_master?(command)
|
255
|
+
@slot.find_node_key_of_master(slot)
|
256
|
+
else
|
257
|
+
@slot.find_node_key_of_slave(slot)
|
258
|
+
end
|
259
|
+
end
|
260
|
+
|
261
|
+
def find_node(node_key)
|
262
|
+
return @node.sample if node_key.nil?
|
263
|
+
@node.find_by(node_key)
|
264
|
+
rescue Node::ReloadNeeded
|
265
|
+
update_cluster_info!(node_key)
|
266
|
+
@node.find_by(node_key)
|
267
|
+
end
|
268
|
+
|
269
|
+
def update_cluster_info!(node_key = nil)
|
270
|
+
unless node_key.nil?
|
271
|
+
host, port = NodeKey.split(node_key)
|
272
|
+
@option.add_node(host, port)
|
273
|
+
end
|
274
|
+
|
275
|
+
@node.map(&:disconnect)
|
276
|
+
@node, @slot = fetch_cluster_info!(@option)
|
277
|
+
end
|
278
|
+
|
279
|
+
def extract_keys_in_pipeline(pipeline)
|
280
|
+
node_keys = pipeline.commands.map { |cmd| find_node_key(cmd) }.compact.uniq
|
281
|
+
command_keys = pipeline.commands.map { |cmd| @command.extract_first_key(cmd) }.reject(&:empty?)
|
282
|
+
[node_keys, command_keys]
|
283
|
+
end
|
284
|
+
end
|
285
|
+
end
|