redis 4.0.2 → 4.0.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/.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
|