redis 4.8.1 → 5.4.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 +82 -0
- data/README.md +125 -162
- data/lib/redis/client.rb +82 -616
- data/lib/redis/commands/bitmaps.rb +14 -4
- data/lib/redis/commands/cluster.rb +1 -18
- data/lib/redis/commands/connection.rb +5 -10
- data/lib/redis/commands/geo.rb +3 -3
- data/lib/redis/commands/hashes.rb +13 -6
- data/lib/redis/commands/hyper_log_log.rb +1 -1
- data/lib/redis/commands/keys.rb +27 -23
- data/lib/redis/commands/lists.rb +74 -25
- data/lib/redis/commands/pubsub.rb +34 -25
- data/lib/redis/commands/server.rb +15 -15
- data/lib/redis/commands/sets.rb +35 -40
- data/lib/redis/commands/sorted_sets.rb +128 -18
- data/lib/redis/commands/streams.rb +48 -21
- data/lib/redis/commands/strings.rb +18 -17
- data/lib/redis/commands/transactions.rb +7 -31
- data/lib/redis/commands.rb +11 -12
- data/lib/redis/distributed.rb +136 -72
- data/lib/redis/errors.rb +15 -50
- data/lib/redis/hash_ring.rb +26 -26
- data/lib/redis/pipeline.rb +47 -222
- data/lib/redis/subscribe.rb +50 -14
- data/lib/redis/version.rb +1 -1
- data/lib/redis.rb +77 -184
- metadata +10 -57
- data/lib/redis/cluster/command.rb +0 -79
- data/lib/redis/cluster/command_loader.rb +0 -33
- data/lib/redis/cluster/key_slot_converter.rb +0 -72
- data/lib/redis/cluster/node.rb +0 -120
- data/lib/redis/cluster/node_key.rb +0 -31
- data/lib/redis/cluster/node_loader.rb +0 -34
- data/lib/redis/cluster/option.rb +0 -100
- data/lib/redis/cluster/slot.rb +0 -86
- data/lib/redis/cluster/slot_loader.rb +0 -46
- data/lib/redis/cluster.rb +0 -315
- data/lib/redis/connection/command_helper.rb +0 -41
- data/lib/redis/connection/hiredis.rb +0 -68
- data/lib/redis/connection/registry.rb +0 -13
- data/lib/redis/connection/ruby.rb +0 -437
- data/lib/redis/connection/synchrony.rb +0 -148
- data/lib/redis/connection.rb +0 -11
data/lib/redis/cluster.rb
DELETED
@@ -1,315 +0,0 @@
|
|
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 = pipeline.commands.map { |cmd| find_node_key(cmd, primary_only: true) }.compact.uniq
|
82
|
-
if node_keys.size > 1
|
83
|
-
raise(CrossSlotPipeliningError,
|
84
|
-
pipeline.commands.map { |cmd| @command.extract_first_key(cmd) }.reject(&:empty?).uniq)
|
85
|
-
end
|
86
|
-
|
87
|
-
try_send(find_node(node_keys.first), :call_pipeline, pipeline)
|
88
|
-
end
|
89
|
-
|
90
|
-
def call_with_timeout(command, timeout, &block)
|
91
|
-
node = assign_node(command)
|
92
|
-
try_send(node, :call_with_timeout, command, timeout, &block)
|
93
|
-
end
|
94
|
-
|
95
|
-
def call_without_timeout(command, &block)
|
96
|
-
call_with_timeout(command, 0, &block)
|
97
|
-
end
|
98
|
-
|
99
|
-
def process(commands, &block)
|
100
|
-
if commands.size == 1 &&
|
101
|
-
%w[unsubscribe punsubscribe].include?(commands.first.first.to_s.downcase) &&
|
102
|
-
commands.first.size == 1
|
103
|
-
|
104
|
-
# Node is indeterminate. We do just a best-effort try here.
|
105
|
-
@node.process_all(commands, &block)
|
106
|
-
else
|
107
|
-
node = assign_node(commands.first)
|
108
|
-
try_send(node, :process, commands, &block)
|
109
|
-
end
|
110
|
-
end
|
111
|
-
|
112
|
-
private
|
113
|
-
|
114
|
-
def fetch_cluster_info!(option)
|
115
|
-
node = Node.new(option.per_node_key)
|
116
|
-
available_slots = SlotLoader.load(node)
|
117
|
-
node_flags = NodeLoader.load_flags(node)
|
118
|
-
option.update_node(available_slots.keys.map { |k| NodeKey.optionize(k) })
|
119
|
-
[Node.new(option.per_node_key, node_flags, option.use_replica?),
|
120
|
-
Slot.new(available_slots, node_flags, option.use_replica?)]
|
121
|
-
ensure
|
122
|
-
node&.each(&:disconnect)
|
123
|
-
end
|
124
|
-
|
125
|
-
def fetch_command_details(nodes)
|
126
|
-
details = CommandLoader.load(nodes)
|
127
|
-
Command.new(details)
|
128
|
-
end
|
129
|
-
|
130
|
-
def send_command(command, &block)
|
131
|
-
cmd = command.first.to_s.downcase
|
132
|
-
case cmd
|
133
|
-
when 'acl', 'auth', 'bgrewriteaof', 'bgsave', 'quit', 'save'
|
134
|
-
@node.call_all(command, &block).first
|
135
|
-
when 'flushall', 'flushdb'
|
136
|
-
@node.call_master(command, &block).first
|
137
|
-
when 'wait' then @node.call_master(command, &block).reduce(:+)
|
138
|
-
when 'keys' then @node.call_slave(command, &block).flatten.sort
|
139
|
-
when 'dbsize' then @node.call_slave(command, &block).reduce(:+)
|
140
|
-
when 'scan' then _scan(command, &block)
|
141
|
-
when 'lastsave' then @node.call_all(command, &block).sort
|
142
|
-
when 'role' then @node.call_all(command, &block)
|
143
|
-
when 'config' then send_config_command(command, &block)
|
144
|
-
when 'client' then send_client_command(command, &block)
|
145
|
-
when 'cluster' then send_cluster_command(command, &block)
|
146
|
-
when 'readonly', 'readwrite', 'shutdown'
|
147
|
-
raise OrchestrationCommandNotSupported, cmd
|
148
|
-
when 'memory' then send_memory_command(command, &block)
|
149
|
-
when 'script' then send_script_command(command, &block)
|
150
|
-
when 'pubsub' then send_pubsub_command(command, &block)
|
151
|
-
when 'discard', 'exec', 'multi', 'unwatch'
|
152
|
-
raise AmbiguousNodeError, cmd
|
153
|
-
else
|
154
|
-
node = assign_node(command)
|
155
|
-
try_send(node, :call, command, &block)
|
156
|
-
end
|
157
|
-
end
|
158
|
-
|
159
|
-
def send_config_command(command, &block)
|
160
|
-
case command[1].to_s.downcase
|
161
|
-
when 'resetstat', 'rewrite', 'set'
|
162
|
-
@node.call_all(command, &block).first
|
163
|
-
else assign_node(command).call(command, &block)
|
164
|
-
end
|
165
|
-
end
|
166
|
-
|
167
|
-
def send_memory_command(command, &block)
|
168
|
-
case command[1].to_s.downcase
|
169
|
-
when 'stats' then @node.call_all(command, &block)
|
170
|
-
when 'purge' then @node.call_all(command, &block).first
|
171
|
-
else assign_node(command).call(command, &block)
|
172
|
-
end
|
173
|
-
end
|
174
|
-
|
175
|
-
def send_client_command(command, &block)
|
176
|
-
case command[1].to_s.downcase
|
177
|
-
when 'list' then @node.call_all(command, &block).flatten
|
178
|
-
when 'pause', 'reply', 'setname'
|
179
|
-
@node.call_all(command, &block).first
|
180
|
-
else assign_node(command).call(command, &block)
|
181
|
-
end
|
182
|
-
end
|
183
|
-
|
184
|
-
def send_cluster_command(command, &block)
|
185
|
-
subcommand = command[1].to_s.downcase
|
186
|
-
case subcommand
|
187
|
-
when 'addslots', 'delslots', 'failover', 'forget', 'meet', 'replicate',
|
188
|
-
'reset', 'set-config-epoch', 'setslot'
|
189
|
-
raise OrchestrationCommandNotSupported, 'cluster', subcommand
|
190
|
-
when 'saveconfig' then @node.call_all(command, &block).first
|
191
|
-
else assign_node(command).call(command, &block)
|
192
|
-
end
|
193
|
-
end
|
194
|
-
|
195
|
-
def send_script_command(command, &block)
|
196
|
-
case command[1].to_s.downcase
|
197
|
-
when 'debug', 'kill'
|
198
|
-
@node.call_all(command, &block).first
|
199
|
-
when 'flush', 'load'
|
200
|
-
@node.call_master(command, &block).first
|
201
|
-
else assign_node(command).call(command, &block)
|
202
|
-
end
|
203
|
-
end
|
204
|
-
|
205
|
-
def send_pubsub_command(command, &block)
|
206
|
-
case command[1].to_s.downcase
|
207
|
-
when 'channels' then @node.call_all(command, &block).flatten.uniq.sort
|
208
|
-
when 'numsub'
|
209
|
-
@node.call_all(command, &block).reject(&:empty?).map { |e| Hash[*e] }
|
210
|
-
.reduce({}) { |a, e| a.merge(e) { |_, v1, v2| v1 + v2 } }
|
211
|
-
when 'numpat' then @node.call_all(command, &block).reduce(:+)
|
212
|
-
else assign_node(command).call(command, &block)
|
213
|
-
end
|
214
|
-
end
|
215
|
-
|
216
|
-
# @see https://redis.io/topics/cluster-spec#redirection-and-resharding
|
217
|
-
# Redirection and resharding
|
218
|
-
def try_send(node, method_name, *args, retry_count: 3, &block)
|
219
|
-
node.public_send(method_name, *args, &block)
|
220
|
-
rescue CommandError => err
|
221
|
-
if err.message.start_with?('MOVED')
|
222
|
-
raise if retry_count <= 0
|
223
|
-
|
224
|
-
node = assign_redirection_node(err.message)
|
225
|
-
retry_count -= 1
|
226
|
-
retry
|
227
|
-
elsif err.message.start_with?('ASK')
|
228
|
-
raise if retry_count <= 0
|
229
|
-
|
230
|
-
node = assign_asking_node(err.message)
|
231
|
-
node.call(%i[asking])
|
232
|
-
retry_count -= 1
|
233
|
-
retry
|
234
|
-
else
|
235
|
-
raise
|
236
|
-
end
|
237
|
-
rescue CannotConnectError
|
238
|
-
update_cluster_info!
|
239
|
-
raise
|
240
|
-
end
|
241
|
-
|
242
|
-
def _scan(command, &block)
|
243
|
-
input_cursor = Integer(command[1])
|
244
|
-
|
245
|
-
client_index = input_cursor % 256
|
246
|
-
raw_cursor = input_cursor >> 8
|
247
|
-
|
248
|
-
clients = @node.scale_reading_clients
|
249
|
-
|
250
|
-
client = clients[client_index]
|
251
|
-
return ['0', []] unless client
|
252
|
-
|
253
|
-
command[1] = raw_cursor.to_s
|
254
|
-
|
255
|
-
result_cursor, result_keys = client.call(command, &block)
|
256
|
-
result_cursor = Integer(result_cursor)
|
257
|
-
|
258
|
-
if result_cursor == 0
|
259
|
-
client_index += 1
|
260
|
-
end
|
261
|
-
|
262
|
-
[((result_cursor << 8) + client_index).to_s, result_keys]
|
263
|
-
end
|
264
|
-
|
265
|
-
def assign_redirection_node(err_msg)
|
266
|
-
_, slot, node_key = err_msg.split(' ')
|
267
|
-
slot = slot.to_i
|
268
|
-
@slot.put(slot, node_key)
|
269
|
-
find_node(node_key)
|
270
|
-
end
|
271
|
-
|
272
|
-
def assign_asking_node(err_msg)
|
273
|
-
_, _, node_key = err_msg.split(' ')
|
274
|
-
find_node(node_key)
|
275
|
-
end
|
276
|
-
|
277
|
-
def assign_node(command)
|
278
|
-
node_key = find_node_key(command)
|
279
|
-
find_node(node_key)
|
280
|
-
end
|
281
|
-
|
282
|
-
def find_node_key(command, primary_only: false)
|
283
|
-
key = @command.extract_first_key(command)
|
284
|
-
return if key.empty?
|
285
|
-
|
286
|
-
slot = KeySlotConverter.convert(key)
|
287
|
-
return unless @slot.exists?(slot)
|
288
|
-
|
289
|
-
if @command.should_send_to_master?(command) || primary_only
|
290
|
-
@slot.find_node_key_of_master(slot)
|
291
|
-
else
|
292
|
-
@slot.find_node_key_of_slave(slot)
|
293
|
-
end
|
294
|
-
end
|
295
|
-
|
296
|
-
def find_node(node_key)
|
297
|
-
return @node.sample if node_key.nil?
|
298
|
-
|
299
|
-
@node.find_by(node_key)
|
300
|
-
rescue Node::ReloadNeeded
|
301
|
-
update_cluster_info!(node_key)
|
302
|
-
@node.find_by(node_key)
|
303
|
-
end
|
304
|
-
|
305
|
-
def update_cluster_info!(node_key = nil)
|
306
|
-
unless node_key.nil?
|
307
|
-
host, port = NodeKey.split(node_key)
|
308
|
-
@option.add_node(host, port)
|
309
|
-
end
|
310
|
-
|
311
|
-
@node.map(&:disconnect)
|
312
|
-
@node, @slot = fetch_cluster_info!(@option)
|
313
|
-
end
|
314
|
-
end
|
315
|
-
end
|
@@ -1,41 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class Redis
|
4
|
-
module Connection
|
5
|
-
module CommandHelper
|
6
|
-
COMMAND_DELIMITER = "\r\n"
|
7
|
-
|
8
|
-
def build_command(args)
|
9
|
-
command = [nil]
|
10
|
-
|
11
|
-
args.each do |i|
|
12
|
-
if i.is_a? Array
|
13
|
-
i.each do |j|
|
14
|
-
j = j.to_s
|
15
|
-
j = j.encoding == Encoding::BINARY ? j : j.b
|
16
|
-
command << "$#{j.bytesize}"
|
17
|
-
command << j
|
18
|
-
end
|
19
|
-
else
|
20
|
-
i = i.to_s
|
21
|
-
i = i.encoding == Encoding::BINARY ? i : i.b
|
22
|
-
command << "$#{i.bytesize}"
|
23
|
-
command << i
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
|
-
command[0] = "*#{(command.length - 1) / 2}"
|
28
|
-
|
29
|
-
# Trailing delimiter
|
30
|
-
command << ""
|
31
|
-
command.join(COMMAND_DELIMITER)
|
32
|
-
end
|
33
|
-
|
34
|
-
protected
|
35
|
-
|
36
|
-
def encode(string)
|
37
|
-
string.force_encoding(Encoding.default_external)
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
@@ -1,68 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require "redis/connection/registry"
|
4
|
-
require "redis/errors"
|
5
|
-
|
6
|
-
require "hiredis/connection"
|
7
|
-
require "timeout"
|
8
|
-
|
9
|
-
class Redis
|
10
|
-
module Connection
|
11
|
-
class Hiredis
|
12
|
-
def self.connect(config)
|
13
|
-
connection = ::Hiredis::Connection.new
|
14
|
-
connect_timeout = (config.fetch(:connect_timeout, 0) * 1_000_000).to_i
|
15
|
-
|
16
|
-
if config[:scheme] == "unix"
|
17
|
-
connection.connect_unix(config[:path], connect_timeout)
|
18
|
-
elsif config[:scheme] == "rediss" || config[:ssl]
|
19
|
-
raise NotImplementedError, "SSL not supported by hiredis driver"
|
20
|
-
else
|
21
|
-
connection.connect(config[:host], config[:port], connect_timeout)
|
22
|
-
end
|
23
|
-
|
24
|
-
instance = new(connection)
|
25
|
-
instance.timeout = config[:read_timeout]
|
26
|
-
instance
|
27
|
-
rescue Errno::ETIMEDOUT
|
28
|
-
raise TimeoutError
|
29
|
-
end
|
30
|
-
|
31
|
-
def initialize(connection)
|
32
|
-
@connection = connection
|
33
|
-
end
|
34
|
-
|
35
|
-
def connected?
|
36
|
-
@connection&.connected?
|
37
|
-
end
|
38
|
-
|
39
|
-
def timeout=(timeout)
|
40
|
-
# Hiredis works with microsecond timeouts
|
41
|
-
@connection.timeout = Integer(timeout * 1_000_000)
|
42
|
-
end
|
43
|
-
|
44
|
-
def disconnect
|
45
|
-
@connection.disconnect
|
46
|
-
@connection = nil
|
47
|
-
end
|
48
|
-
|
49
|
-
def write(command)
|
50
|
-
@connection.write(command.flatten(1))
|
51
|
-
rescue Errno::EAGAIN
|
52
|
-
raise TimeoutError
|
53
|
-
end
|
54
|
-
|
55
|
-
def read
|
56
|
-
reply = @connection.read
|
57
|
-
reply = CommandError.new(reply.message) if reply.is_a?(RuntimeError)
|
58
|
-
reply
|
59
|
-
rescue Errno::EAGAIN
|
60
|
-
raise TimeoutError
|
61
|
-
rescue RuntimeError => err
|
62
|
-
raise ProtocolError, err.message
|
63
|
-
end
|
64
|
-
end
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
|
-
Redis::Connection.drivers << Redis::Connection::Hiredis
|
@@ -1,13 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
class Redis
|
4
|
-
module Connection
|
5
|
-
# Store a list of loaded connection drivers in the Connection module.
|
6
|
-
# Redis::Client uses the last required driver by default, and will be aware
|
7
|
-
# of the loaded connection drivers if the user chooses to override the
|
8
|
-
# default connection driver.
|
9
|
-
def self.drivers
|
10
|
-
@drivers ||= []
|
11
|
-
end
|
12
|
-
end
|
13
|
-
end
|