redis 4.0.1 → 4.1.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/CHANGELOG.md +38 -0
- data/README.md +46 -1
- data/lib/redis/client.rb +29 -12
- data/lib/redis/cluster/command.rb +81 -0
- data/lib/redis/cluster/command_loader.rb +34 -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 +37 -0
- data/lib/redis/cluster/option.rb +77 -0
- data/lib/redis/cluster/slot.rb +69 -0
- data/lib/redis/cluster/slot_loader.rb +49 -0
- data/lib/redis/cluster.rb +286 -0
- data/lib/redis/connection/ruby.rb +5 -2
- data/lib/redis/distributed.rb +13 -6
- data/lib/redis/errors.rb +46 -0
- data/lib/redis/pipeline.rb +9 -1
- data/lib/redis/version.rb +1 -1
- data/lib/redis.rb +692 -25
- metadata +27 -184
- data/.gitignore +0 -16
- data/.travis/Gemfile +0 -13
- data/.travis.yml +0 -73
- data/.yardopts +0 -3
- data/Gemfile +0 -3
- data/benchmarking/logging.rb +0 -71
- data/benchmarking/pipeline.rb +0 -51
- data/benchmarking/speed.rb +0 -21
- data/benchmarking/suite.rb +0 -24
- data/benchmarking/worker.rb +0 -71
- data/bors.toml +0 -14
- data/examples/basic.rb +0 -15
- data/examples/consistency.rb +0 -114
- data/examples/dist_redis.rb +0 -43
- data/examples/incr-decr.rb +0 -17
- data/examples/list.rb +0 -26
- data/examples/pubsub.rb +0 -37
- data/examples/sentinel/sentinel.conf +0 -9
- data/examples/sentinel/start +0 -49
- data/examples/sentinel.rb +0 -41
- data/examples/sets.rb +0 -36
- data/examples/unicorn/config.ru +0 -3
- data/examples/unicorn/unicorn.rb +0 -20
- data/makefile +0 -42
- data/redis.gemspec +0 -42
- data/test/bitpos_test.rb +0 -63
- data/test/blocking_commands_test.rb +0 -40
- data/test/client_test.rb +0 -59
- data/test/command_map_test.rb +0 -28
- data/test/commands_on_hashes_test.rb +0 -19
- data/test/commands_on_hyper_log_log_test.rb +0 -19
- data/test/commands_on_lists_test.rb +0 -18
- data/test/commands_on_sets_test.rb +0 -75
- data/test/commands_on_sorted_sets_test.rb +0 -150
- data/test/commands_on_strings_test.rb +0 -99
- data/test/commands_on_value_types_test.rb +0 -171
- data/test/connection_handling_test.rb +0 -275
- data/test/connection_test.rb +0 -57
- data/test/db/.gitkeep +0 -0
- data/test/distributed_blocking_commands_test.rb +0 -44
- data/test/distributed_commands_on_hashes_test.rb +0 -8
- data/test/distributed_commands_on_hyper_log_log_test.rb +0 -31
- data/test/distributed_commands_on_lists_test.rb +0 -20
- data/test/distributed_commands_on_sets_test.rb +0 -106
- data/test/distributed_commands_on_sorted_sets_test.rb +0 -16
- data/test/distributed_commands_on_strings_test.rb +0 -69
- data/test/distributed_commands_on_value_types_test.rb +0 -93
- data/test/distributed_commands_requiring_clustering_test.rb +0 -162
- data/test/distributed_connection_handling_test.rb +0 -21
- data/test/distributed_internals_test.rb +0 -68
- data/test/distributed_key_tags_test.rb +0 -50
- data/test/distributed_persistence_control_commands_test.rb +0 -24
- data/test/distributed_publish_subscribe_test.rb +0 -90
- data/test/distributed_remote_server_control_commands_test.rb +0 -64
- data/test/distributed_scripting_test.rb +0 -100
- data/test/distributed_sorting_test.rb +0 -18
- data/test/distributed_test.rb +0 -56
- data/test/distributed_transactions_test.rb +0 -30
- data/test/encoding_test.rb +0 -14
- data/test/error_replies_test.rb +0 -57
- data/test/fork_safety_test.rb +0 -60
- data/test/helper.rb +0 -201
- data/test/helper_test.rb +0 -22
- data/test/internals_test.rb +0 -389
- data/test/lint/blocking_commands.rb +0 -150
- data/test/lint/hashes.rb +0 -162
- data/test/lint/hyper_log_log.rb +0 -60
- data/test/lint/lists.rb +0 -143
- data/test/lint/sets.rb +0 -140
- data/test/lint/sorted_sets.rb +0 -316
- data/test/lint/strings.rb +0 -246
- data/test/lint/value_types.rb +0 -130
- data/test/persistence_control_commands_test.rb +0 -24
- data/test/pipelining_commands_test.rb +0 -238
- data/test/publish_subscribe_test.rb +0 -280
- data/test/remote_server_control_commands_test.rb +0 -175
- data/test/scanning_test.rb +0 -407
- data/test/scripting_test.rb +0 -76
- data/test/sentinel_command_test.rb +0 -78
- data/test/sentinel_test.rb +0 -253
- data/test/sorting_test.rb +0 -57
- data/test/ssl_test.rb +0 -69
- data/test/support/connection/hiredis.rb +0 -1
- data/test/support/connection/ruby.rb +0 -1
- data/test/support/connection/synchrony.rb +0 -17
- data/test/support/redis_mock.rb +0 -130
- data/test/support/ssl/gen_certs.sh +0 -31
- data/test/support/ssl/trusted-ca.crt +0 -25
- data/test/support/ssl/trusted-ca.key +0 -27
- data/test/support/ssl/trusted-cert.crt +0 -81
- data/test/support/ssl/trusted-cert.key +0 -28
- data/test/support/ssl/untrusted-ca.crt +0 -26
- data/test/support/ssl/untrusted-ca.key +0 -27
- data/test/support/ssl/untrusted-cert.crt +0 -82
- data/test/support/ssl/untrusted-cert.key +0 -28
- data/test/support/wire/synchrony.rb +0 -24
- data/test/support/wire/thread.rb +0 -5
- data/test/synchrony_driver.rb +0 -85
- data/test/test.conf.erb +0 -9
- data/test/thread_safety_test.rb +0 -60
- data/test/transactions_test.rb +0 -262
- data/test/unknown_commands_test.rb +0 -12
- data/test/url_param_test.rb +0 -136
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
class Redis
|
6
|
+
class Cluster
|
7
|
+
# Keep slot and node key map for Redis Cluster Client
|
8
|
+
class Slot
|
9
|
+
ROLE_SLAVE = 'slave'
|
10
|
+
|
11
|
+
def initialize(available_slots, node_flags = {}, with_replica = false)
|
12
|
+
@with_replica = with_replica
|
13
|
+
@node_flags = node_flags
|
14
|
+
@map = build_slot_node_key_map(available_slots)
|
15
|
+
end
|
16
|
+
|
17
|
+
def exists?(slot)
|
18
|
+
@map.key?(slot)
|
19
|
+
end
|
20
|
+
|
21
|
+
def find_node_key_of_master(slot)
|
22
|
+
return nil unless exists?(slot)
|
23
|
+
|
24
|
+
@map[slot][:master]
|
25
|
+
end
|
26
|
+
|
27
|
+
def find_node_key_of_slave(slot)
|
28
|
+
return nil unless exists?(slot)
|
29
|
+
return find_node_key_of_master(slot) if replica_disabled?
|
30
|
+
|
31
|
+
@map[slot][:slaves].to_a.sample
|
32
|
+
end
|
33
|
+
|
34
|
+
def put(slot, node_key)
|
35
|
+
assign_node_key(@map, slot, node_key)
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def replica_disabled?
|
42
|
+
!@with_replica
|
43
|
+
end
|
44
|
+
|
45
|
+
def master?(node_key)
|
46
|
+
!slave?(node_key)
|
47
|
+
end
|
48
|
+
|
49
|
+
def slave?(node_key)
|
50
|
+
@node_flags[node_key] == ROLE_SLAVE
|
51
|
+
end
|
52
|
+
|
53
|
+
def build_slot_node_key_map(available_slots)
|
54
|
+
available_slots.each_with_object({}) do |(node_key, slots), acc|
|
55
|
+
slots.each { |slot| assign_node_key(acc, slot, node_key) }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def assign_node_key(mappings, slot, node_key)
|
60
|
+
mappings[slot] ||= { master: nil, slaves: Set.new }
|
61
|
+
if master?(node_key)
|
62
|
+
mappings[slot][:master] = node_key
|
63
|
+
else
|
64
|
+
mappings[slot][:slaves].add(node_key)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../errors'
|
4
|
+
require_relative 'node_key'
|
5
|
+
|
6
|
+
class Redis
|
7
|
+
class Cluster
|
8
|
+
# Load and hashify slot info for Redis Cluster Client
|
9
|
+
module SlotLoader
|
10
|
+
module_function
|
11
|
+
|
12
|
+
def load(nodes)
|
13
|
+
info = {}
|
14
|
+
|
15
|
+
nodes.each do |node|
|
16
|
+
info = Hash[*fetch_slot_info(node)]
|
17
|
+
info.empty? ? next : break
|
18
|
+
end
|
19
|
+
|
20
|
+
return info unless info.empty?
|
21
|
+
|
22
|
+
raise CannotConnectError, 'Redis client could not connect to any cluster nodes'
|
23
|
+
end
|
24
|
+
|
25
|
+
def fetch_slot_info(node)
|
26
|
+
node.call(%i[cluster slots])
|
27
|
+
.map { |arr| parse_slot_info(arr, default_ip: node.host) }
|
28
|
+
.flatten
|
29
|
+
rescue CannotConnectError, ConnectionError, CommandError
|
30
|
+
{} # can retry on another node
|
31
|
+
end
|
32
|
+
|
33
|
+
def parse_slot_info(arr, default_ip:)
|
34
|
+
first_slot, last_slot = arr[0..1]
|
35
|
+
slot_range = (first_slot..last_slot).freeze
|
36
|
+
arr[2..-1].map { |addr| [stringify_node_key(addr, default_ip), slot_range] }
|
37
|
+
.flatten
|
38
|
+
end
|
39
|
+
|
40
|
+
def stringify_node_key(arr, default_ip)
|
41
|
+
ip, port = arr
|
42
|
+
ip = default_ip if ip.empty? # When cluster is down
|
43
|
+
NodeKey.build_from_host_port(ip, port)
|
44
|
+
end
|
45
|
+
|
46
|
+
private_class_method :fetch_slot_info, :parse_slot_info, :stringify_node_key
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -0,0 +1,286 @@
|
|
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 'wait' then @node.call_master(command, &block).reduce(:+)
|
136
|
+
when 'keys' then @node.call_slave(command, &block).flatten.sort
|
137
|
+
when 'dbsize' then @node.call_slave(command, &block).reduce(:+)
|
138
|
+
when 'lastsave' then @node.call_all(command, &block).sort
|
139
|
+
when 'role' then @node.call_all(command, &block)
|
140
|
+
when 'config' then send_config_command(command, &block)
|
141
|
+
when 'client' then send_client_command(command, &block)
|
142
|
+
when 'cluster' then send_cluster_command(command, &block)
|
143
|
+
when 'readonly', 'readwrite', 'shutdown'
|
144
|
+
raise OrchestrationCommandNotSupported, cmd
|
145
|
+
when 'memory' then send_memory_command(command, &block)
|
146
|
+
when 'script' then send_script_command(command, &block)
|
147
|
+
when 'pubsub' then send_pubsub_command(command, &block)
|
148
|
+
when 'discard', 'exec', 'multi', 'unwatch'
|
149
|
+
raise AmbiguousNodeError, cmd
|
150
|
+
else
|
151
|
+
node = assign_node(command)
|
152
|
+
try_send(node, :call, command, &block)
|
153
|
+
end
|
154
|
+
end
|
155
|
+
|
156
|
+
def send_config_command(command, &block)
|
157
|
+
case command[1].to_s.downcase
|
158
|
+
when 'resetstat', 'rewrite', 'set'
|
159
|
+
@node.call_all(command, &block).first
|
160
|
+
else assign_node(command).call(command, &block)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
def send_memory_command(command, &block)
|
165
|
+
case command[1].to_s.downcase
|
166
|
+
when 'stats' then @node.call_all(command, &block)
|
167
|
+
when 'purge' then @node.call_all(command, &block).first
|
168
|
+
else assign_node(command).call(command, &block)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
172
|
+
def send_client_command(command, &block)
|
173
|
+
case command[1].to_s.downcase
|
174
|
+
when 'list' then @node.call_all(command, &block).flatten
|
175
|
+
when 'pause', 'reply', 'setname'
|
176
|
+
@node.call_all(command, &block).first
|
177
|
+
else assign_node(command).call(command, &block)
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
def send_cluster_command(command, &block)
|
182
|
+
subcommand = command[1].to_s.downcase
|
183
|
+
case subcommand
|
184
|
+
when 'addslots', 'delslots', 'failover', 'forget', 'meet', 'replicate',
|
185
|
+
'reset', 'set-config-epoch', 'setslot'
|
186
|
+
raise OrchestrationCommandNotSupported, 'cluster', subcommand
|
187
|
+
when 'saveconfig' then @node.call_all(command, &block).first
|
188
|
+
else assign_node(command).call(command, &block)
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
def send_script_command(command, &block)
|
193
|
+
case command[1].to_s.downcase
|
194
|
+
when 'debug', 'kill'
|
195
|
+
@node.call_all(command, &block).first
|
196
|
+
when 'flush', 'load'
|
197
|
+
@node.call_master(command, &block).first
|
198
|
+
else assign_node(command).call(command, &block)
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
202
|
+
def send_pubsub_command(command, &block)
|
203
|
+
case command[1].to_s.downcase
|
204
|
+
when 'channels' then @node.call_all(command, &block).flatten.uniq.sort
|
205
|
+
when 'numsub'
|
206
|
+
@node.call_all(command, &block).reject(&:empty?).map { |e| Hash[*e] }
|
207
|
+
.reduce({}) { |a, e| a.merge(e) { |_, v1, v2| v1 + v2 } }
|
208
|
+
when 'numpat' then @node.call_all(command, &block).reduce(:+)
|
209
|
+
else assign_node(command).call(command, &block)
|
210
|
+
end
|
211
|
+
end
|
212
|
+
|
213
|
+
# @see https://redis.io/topics/cluster-spec#redirection-and-resharding
|
214
|
+
# Redirection and resharding
|
215
|
+
def try_send(node, method_name, *args, retry_count: 3, &block)
|
216
|
+
node.public_send(method_name, *args, &block)
|
217
|
+
rescue CommandError => err
|
218
|
+
if err.message.start_with?('MOVED')
|
219
|
+
assign_redirection_node(err.message).public_send(method_name, *args, &block)
|
220
|
+
elsif err.message.start_with?('ASK')
|
221
|
+
raise if retry_count <= 0
|
222
|
+
node = assign_asking_node(err.message)
|
223
|
+
node.call(%i[asking])
|
224
|
+
retry_count -= 1
|
225
|
+
retry
|
226
|
+
else
|
227
|
+
raise
|
228
|
+
end
|
229
|
+
end
|
230
|
+
|
231
|
+
def assign_redirection_node(err_msg)
|
232
|
+
_, slot, node_key = err_msg.split(' ')
|
233
|
+
slot = slot.to_i
|
234
|
+
@slot.put(slot, node_key)
|
235
|
+
find_node(node_key)
|
236
|
+
end
|
237
|
+
|
238
|
+
def assign_asking_node(err_msg)
|
239
|
+
_, _, node_key = err_msg.split(' ')
|
240
|
+
find_node(node_key)
|
241
|
+
end
|
242
|
+
|
243
|
+
def assign_node(command)
|
244
|
+
node_key = find_node_key(command)
|
245
|
+
find_node(node_key)
|
246
|
+
end
|
247
|
+
|
248
|
+
def find_node_key(command)
|
249
|
+
key = @command.extract_first_key(command)
|
250
|
+
return if key.empty?
|
251
|
+
|
252
|
+
slot = KeySlotConverter.convert(key)
|
253
|
+
return unless @slot.exists?(slot)
|
254
|
+
|
255
|
+
if @command.should_send_to_master?(command)
|
256
|
+
@slot.find_node_key_of_master(slot)
|
257
|
+
else
|
258
|
+
@slot.find_node_key_of_slave(slot)
|
259
|
+
end
|
260
|
+
end
|
261
|
+
|
262
|
+
def find_node(node_key)
|
263
|
+
return @node.sample if node_key.nil?
|
264
|
+
@node.find_by(node_key)
|
265
|
+
rescue Node::ReloadNeeded
|
266
|
+
update_cluster_info!(node_key)
|
267
|
+
@node.find_by(node_key)
|
268
|
+
end
|
269
|
+
|
270
|
+
def update_cluster_info!(node_key = nil)
|
271
|
+
unless node_key.nil?
|
272
|
+
host, port = NodeKey.split(node_key)
|
273
|
+
@option.add_node(host, port)
|
274
|
+
end
|
275
|
+
|
276
|
+
@node.map(&:disconnect)
|
277
|
+
@node, @slot = fetch_cluster_info!(@option)
|
278
|
+
end
|
279
|
+
|
280
|
+
def extract_keys_in_pipeline(pipeline)
|
281
|
+
node_keys = pipeline.commands.map { |cmd| find_node_key(cmd) }.compact.uniq
|
282
|
+
command_keys = pipeline.commands.map { |cmd| @command.extract_first_key(cmd) }.reject(&:empty?)
|
283
|
+
[node_keys, command_keys]
|
284
|
+
end
|
285
|
+
end
|
286
|
+
end
|
@@ -267,7 +267,10 @@ class Redis
|
|
267
267
|
ssl_sock = new(tcp_sock, ctx)
|
268
268
|
ssl_sock.hostname = host
|
269
269
|
ssl_sock.connect
|
270
|
-
|
270
|
+
|
271
|
+
unless ctx.verify_mode == OpenSSL::SSL::VERIFY_NONE
|
272
|
+
ssl_sock.post_connection_check(host)
|
273
|
+
end
|
271
274
|
|
272
275
|
ssl_sock
|
273
276
|
end
|
@@ -294,7 +297,7 @@ class Redis
|
|
294
297
|
end
|
295
298
|
|
296
299
|
instance = new(sock)
|
297
|
-
instance.timeout = config[:
|
300
|
+
instance.timeout = config[:read_timeout]
|
298
301
|
instance.write_timeout = config[:write_timeout]
|
299
302
|
instance.set_tcp_keepalive config[:tcp_keepalive]
|
300
303
|
instance
|
data/lib/redis/distributed.rb
CHANGED
@@ -161,6 +161,14 @@ class Redis
|
|
161
161
|
end
|
162
162
|
end
|
163
163
|
|
164
|
+
# Unlink keys.
|
165
|
+
def unlink(*args)
|
166
|
+
keys_per_node = args.group_by { |key| node_for(key) }
|
167
|
+
keys_per_node.inject(0) do |sum, (node, keys)|
|
168
|
+
sum + node.unlink(*keys)
|
169
|
+
end
|
170
|
+
end
|
171
|
+
|
164
172
|
# Determine if a key exists.
|
165
173
|
def exists(key)
|
166
174
|
node_for(key).exists(key)
|
@@ -395,12 +403,11 @@ class Redis
|
|
395
403
|
def _bpop(cmd, args)
|
396
404
|
options = {}
|
397
405
|
|
398
|
-
|
399
|
-
when Hash
|
406
|
+
if args.last.is_a?(Hash)
|
400
407
|
options = args.pop
|
401
|
-
|
408
|
+
elsif args.last.respond_to?(:to_int)
|
402
409
|
# Issue deprecation notice in obnoxious mode...
|
403
|
-
options[:timeout] = args.pop
|
410
|
+
options[:timeout] = args.pop.to_int
|
404
411
|
end
|
405
412
|
|
406
413
|
if args.size > 1
|
@@ -692,8 +699,8 @@ class Redis
|
|
692
699
|
end
|
693
700
|
|
694
701
|
# Delete one or more hash fields.
|
695
|
-
def hdel(key,
|
696
|
-
node_for(key).hdel(key,
|
702
|
+
def hdel(key, *fields)
|
703
|
+
node_for(key).hdel(key, *fields)
|
697
704
|
end
|
698
705
|
|
699
706
|
# Determine if a hash field exists.
|
data/lib/redis/errors.rb
CHANGED
@@ -37,4 +37,50 @@ class Redis
|
|
37
37
|
# Raised when the connection was inherited by a child process.
|
38
38
|
class InheritedError < BaseConnectionError
|
39
39
|
end
|
40
|
+
|
41
|
+
# Raised when client options are invalid.
|
42
|
+
class InvalidClientOptionError < BaseError
|
43
|
+
end
|
44
|
+
|
45
|
+
class Cluster
|
46
|
+
# Raised when client connected to redis as cluster mode
|
47
|
+
# and some cluster subcommands were called.
|
48
|
+
class OrchestrationCommandNotSupported < BaseError
|
49
|
+
def initialize(command, subcommand = '')
|
50
|
+
str = [command, subcommand].map(&:to_s).reject(&:empty?).join(' ').upcase
|
51
|
+
msg = "#{str} command should be used with care "\
|
52
|
+
'only by applications orchestrating Redis Cluster, like redis-trib, '\
|
53
|
+
'and the command if used out of the right context can leave the cluster '\
|
54
|
+
'in a wrong state or cause data loss.'
|
55
|
+
super(msg)
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
# Raised when error occurs on any node of cluster.
|
60
|
+
class CommandErrorCollection < BaseError
|
61
|
+
attr_reader :errors
|
62
|
+
|
63
|
+
# @param errors [Hash{String => Redis::CommandError}]
|
64
|
+
# @param error_message [String]
|
65
|
+
def initialize(errors, error_message = 'Command errors were replied on any node')
|
66
|
+
@errors = errors
|
67
|
+
super(error_message)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
# Raised when cluster client can't select node.
|
72
|
+
class AmbiguousNodeError < BaseError
|
73
|
+
def initialize(command)
|
74
|
+
super("Cluster client doesn't know which node the #{command} command should be sent to.")
|
75
|
+
end
|
76
|
+
end
|
77
|
+
|
78
|
+
# Raised when commands in pipelining include cross slot keys.
|
79
|
+
class CrossSlotPipeliningError < BaseError
|
80
|
+
def initialize(keys)
|
81
|
+
super("Cluster client couldn't send pipelining to single node. "\
|
82
|
+
"The commands include cross slot keys. #{keys}")
|
83
|
+
end
|
84
|
+
end
|
85
|
+
end
|
40
86
|
end
|
data/lib/redis/pipeline.rb
CHANGED
@@ -22,6 +22,10 @@ class Redis
|
|
22
22
|
@shutdown
|
23
23
|
end
|
24
24
|
|
25
|
+
def empty?
|
26
|
+
@futures.empty?
|
27
|
+
end
|
28
|
+
|
25
29
|
def call(command, &block)
|
26
30
|
# A pipeline that contains a shutdown should not raise ECONNRESET when
|
27
31
|
# the connection is gone.
|
@@ -86,7 +90,11 @@ class Redis
|
|
86
90
|
end
|
87
91
|
|
88
92
|
def commands
|
89
|
-
|
93
|
+
if empty?
|
94
|
+
[]
|
95
|
+
else
|
96
|
+
[[:multi]] + super + [[:exec]]
|
97
|
+
end
|
90
98
|
end
|
91
99
|
end
|
92
100
|
end
|
data/lib/redis/version.rb
CHANGED