redis 4.7.1 → 5.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (44) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +65 -0
  3. data/README.md +88 -168
  4. data/lib/redis/client.rb +84 -623
  5. data/lib/redis/commands/bitmaps.rb +4 -1
  6. data/lib/redis/commands/cluster.rb +1 -18
  7. data/lib/redis/commands/connection.rb +5 -10
  8. data/lib/redis/commands/geo.rb +3 -3
  9. data/lib/redis/commands/hashes.rb +9 -6
  10. data/lib/redis/commands/hyper_log_log.rb +1 -1
  11. data/lib/redis/commands/keys.rb +53 -27
  12. data/lib/redis/commands/lists.rb +73 -23
  13. data/lib/redis/commands/pubsub.rb +7 -25
  14. data/lib/redis/commands/server.rb +15 -15
  15. data/lib/redis/commands/sets.rb +43 -36
  16. data/lib/redis/commands/sorted_sets.rb +84 -12
  17. data/lib/redis/commands/streams.rb +39 -19
  18. data/lib/redis/commands/strings.rb +18 -17
  19. data/lib/redis/commands/transactions.rb +7 -31
  20. data/lib/redis/commands.rb +4 -9
  21. data/lib/redis/distributed.rb +128 -67
  22. data/lib/redis/errors.rb +15 -50
  23. data/lib/redis/hash_ring.rb +26 -26
  24. data/lib/redis/pipeline.rb +43 -222
  25. data/lib/redis/subscribe.rb +23 -15
  26. data/lib/redis/version.rb +1 -1
  27. data/lib/redis.rb +90 -182
  28. metadata +10 -54
  29. data/lib/redis/cluster/command.rb +0 -79
  30. data/lib/redis/cluster/command_loader.rb +0 -33
  31. data/lib/redis/cluster/key_slot_converter.rb +0 -72
  32. data/lib/redis/cluster/node.rb +0 -120
  33. data/lib/redis/cluster/node_key.rb +0 -31
  34. data/lib/redis/cluster/node_loader.rb +0 -34
  35. data/lib/redis/cluster/option.rb +0 -100
  36. data/lib/redis/cluster/slot.rb +0 -86
  37. data/lib/redis/cluster/slot_loader.rb +0 -46
  38. data/lib/redis/cluster.rb +0 -315
  39. data/lib/redis/connection/command_helper.rb +0 -41
  40. data/lib/redis/connection/hiredis.rb +0 -66
  41. data/lib/redis/connection/registry.rb +0 -13
  42. data/lib/redis/connection/ruby.rb +0 -437
  43. data/lib/redis/connection/synchrony.rb +0 -148
  44. 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,66 +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
- else
19
- connection.connect(config[:host], config[:port], connect_timeout)
20
- end
21
-
22
- instance = new(connection)
23
- instance.timeout = config[:read_timeout]
24
- instance
25
- rescue Errno::ETIMEDOUT
26
- raise TimeoutError
27
- end
28
-
29
- def initialize(connection)
30
- @connection = connection
31
- end
32
-
33
- def connected?
34
- @connection&.connected?
35
- end
36
-
37
- def timeout=(timeout)
38
- # Hiredis works with microsecond timeouts
39
- @connection.timeout = Integer(timeout * 1_000_000)
40
- end
41
-
42
- def disconnect
43
- @connection.disconnect
44
- @connection = nil
45
- end
46
-
47
- def write(command)
48
- @connection.write(command.flatten(1))
49
- rescue Errno::EAGAIN
50
- raise TimeoutError
51
- end
52
-
53
- def read
54
- reply = @connection.read
55
- reply = CommandError.new(reply.message) if reply.is_a?(RuntimeError)
56
- reply
57
- rescue Errno::EAGAIN
58
- raise TimeoutError
59
- rescue RuntimeError => err
60
- raise ProtocolError, err.message
61
- end
62
- end
63
- end
64
- end
65
-
66
- 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