redis 4.5.1 → 5.0.6

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +120 -0
  3. data/README.md +82 -153
  4. data/lib/redis/client.rb +77 -615
  5. data/lib/redis/commands/bitmaps.rb +66 -0
  6. data/lib/redis/commands/cluster.rb +28 -0
  7. data/lib/redis/commands/connection.rb +53 -0
  8. data/lib/redis/commands/geo.rb +84 -0
  9. data/lib/redis/commands/hashes.rb +254 -0
  10. data/lib/redis/commands/hyper_log_log.rb +37 -0
  11. data/lib/redis/commands/keys.rb +437 -0
  12. data/lib/redis/commands/lists.rb +285 -0
  13. data/lib/redis/commands/pubsub.rb +54 -0
  14. data/lib/redis/commands/scripting.rb +114 -0
  15. data/lib/redis/commands/server.rb +188 -0
  16. data/lib/redis/commands/sets.rb +214 -0
  17. data/lib/redis/commands/sorted_sets.rb +818 -0
  18. data/lib/redis/commands/streams.rb +402 -0
  19. data/lib/redis/commands/strings.rb +314 -0
  20. data/lib/redis/commands/transactions.rb +115 -0
  21. data/lib/redis/commands.rb +237 -0
  22. data/lib/redis/distributed.rb +140 -70
  23. data/lib/redis/errors.rb +15 -41
  24. data/lib/redis/hash_ring.rb +26 -26
  25. data/lib/redis/pipeline.rb +66 -120
  26. data/lib/redis/subscribe.rb +23 -15
  27. data/lib/redis/version.rb +1 -1
  28. data/lib/redis.rb +106 -3736
  29. metadata +26 -53
  30. data/lib/redis/cluster/command.rb +0 -81
  31. data/lib/redis/cluster/command_loader.rb +0 -33
  32. data/lib/redis/cluster/key_slot_converter.rb +0 -72
  33. data/lib/redis/cluster/node.rb +0 -108
  34. data/lib/redis/cluster/node_key.rb +0 -31
  35. data/lib/redis/cluster/node_loader.rb +0 -37
  36. data/lib/redis/cluster/option.rb +0 -93
  37. data/lib/redis/cluster/slot.rb +0 -86
  38. data/lib/redis/cluster/slot_loader.rb +0 -49
  39. data/lib/redis/cluster.rb +0 -291
  40. data/lib/redis/connection/command_helper.rb +0 -41
  41. data/lib/redis/connection/hiredis.rb +0 -67
  42. data/lib/redis/connection/registry.rb +0 -13
  43. data/lib/redis/connection/ruby.rb +0 -428
  44. data/lib/redis/connection/synchrony.rb +0 -146
  45. data/lib/redis/connection.rb +0 -11
@@ -1,49 +0,0 @@
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 = 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
- hash_with_default_arr = Hash.new { |h, k| h[k] = [] }
27
- node.call(%i[cluster slots])
28
- .flat_map { |arr| parse_slot_info(arr, default_ip: node.host) }
29
- .each_with_object(hash_with_default_arr) { |arr, h| h[arr[0]] << arr[1] }
30
- rescue CannotConnectError, ConnectionError, CommandError
31
- {} # can retry on another node
32
- end
33
-
34
- def parse_slot_info(arr, default_ip:)
35
- first_slot, last_slot = arr[0..1]
36
- slot_range = (first_slot..last_slot).freeze
37
- arr[2..-1].map { |addr| [stringify_node_key(addr, default_ip), slot_range] }
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
data/lib/redis/cluster.rb DELETED
@@ -1,291 +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 'lastsave' then @node.call_all(command, &block).sort
141
- when 'role' then @node.call_all(command, &block)
142
- when 'config' then send_config_command(command, &block)
143
- when 'client' then send_client_command(command, &block)
144
- when 'cluster' then send_cluster_command(command, &block)
145
- when 'readonly', 'readwrite', 'shutdown'
146
- raise OrchestrationCommandNotSupported, cmd
147
- when 'memory' then send_memory_command(command, &block)
148
- when 'script' then send_script_command(command, &block)
149
- when 'pubsub' then send_pubsub_command(command, &block)
150
- when 'discard', 'exec', 'multi', 'unwatch'
151
- raise AmbiguousNodeError, cmd
152
- else
153
- node = assign_node(command)
154
- try_send(node, :call, command, &block)
155
- end
156
- end
157
-
158
- def send_config_command(command, &block)
159
- case command[1].to_s.downcase
160
- when 'resetstat', 'rewrite', 'set'
161
- @node.call_all(command, &block).first
162
- else assign_node(command).call(command, &block)
163
- end
164
- end
165
-
166
- def send_memory_command(command, &block)
167
- case command[1].to_s.downcase
168
- when 'stats' then @node.call_all(command, &block)
169
- when 'purge' then @node.call_all(command, &block).first
170
- else assign_node(command).call(command, &block)
171
- end
172
- end
173
-
174
- def send_client_command(command, &block)
175
- case command[1].to_s.downcase
176
- when 'list' then @node.call_all(command, &block).flatten
177
- when 'pause', 'reply', 'setname'
178
- @node.call_all(command, &block).first
179
- else assign_node(command).call(command, &block)
180
- end
181
- end
182
-
183
- def send_cluster_command(command, &block)
184
- subcommand = command[1].to_s.downcase
185
- case subcommand
186
- when 'addslots', 'delslots', 'failover', 'forget', 'meet', 'replicate',
187
- 'reset', 'set-config-epoch', 'setslot'
188
- raise OrchestrationCommandNotSupported, 'cluster', subcommand
189
- when 'saveconfig' then @node.call_all(command, &block).first
190
- else assign_node(command).call(command, &block)
191
- end
192
- end
193
-
194
- def send_script_command(command, &block)
195
- case command[1].to_s.downcase
196
- when 'debug', 'kill'
197
- @node.call_all(command, &block).first
198
- when 'flush', 'load'
199
- @node.call_master(command, &block).first
200
- else assign_node(command).call(command, &block)
201
- end
202
- end
203
-
204
- def send_pubsub_command(command, &block)
205
- case command[1].to_s.downcase
206
- when 'channels' then @node.call_all(command, &block).flatten.uniq.sort
207
- when 'numsub'
208
- @node.call_all(command, &block).reject(&:empty?).map { |e| Hash[*e] }
209
- .reduce({}) { |a, e| a.merge(e) { |_, v1, v2| v1 + v2 } }
210
- when 'numpat' then @node.call_all(command, &block).reduce(:+)
211
- else assign_node(command).call(command, &block)
212
- end
213
- end
214
-
215
- # @see https://redis.io/topics/cluster-spec#redirection-and-resharding
216
- # Redirection and resharding
217
- def try_send(node, method_name, *args, retry_count: 3, &block)
218
- node.public_send(method_name, *args, &block)
219
- rescue CommandError => err
220
- if err.message.start_with?('MOVED')
221
- raise if retry_count <= 0
222
-
223
- node = assign_redirection_node(err.message)
224
- retry_count -= 1
225
- retry
226
- elsif err.message.start_with?('ASK')
227
- raise if retry_count <= 0
228
-
229
- node = assign_asking_node(err.message)
230
- node.call(%i[asking])
231
- retry_count -= 1
232
- retry
233
- else
234
- raise
235
- end
236
- rescue CannotConnectError
237
- update_cluster_info!
238
- raise
239
- end
240
-
241
- def assign_redirection_node(err_msg)
242
- _, slot, node_key = err_msg.split(' ')
243
- slot = slot.to_i
244
- @slot.put(slot, node_key)
245
- find_node(node_key)
246
- end
247
-
248
- def assign_asking_node(err_msg)
249
- _, _, node_key = err_msg.split(' ')
250
- find_node(node_key)
251
- end
252
-
253
- def assign_node(command)
254
- node_key = find_node_key(command)
255
- find_node(node_key)
256
- end
257
-
258
- def find_node_key(command, primary_only: false)
259
- key = @command.extract_first_key(command)
260
- return if key.empty?
261
-
262
- slot = KeySlotConverter.convert(key)
263
- return unless @slot.exists?(slot)
264
-
265
- if @command.should_send_to_master?(command) || primary_only
266
- @slot.find_node_key_of_master(slot)
267
- else
268
- @slot.find_node_key_of_slave(slot)
269
- end
270
- end
271
-
272
- def find_node(node_key)
273
- return @node.sample if node_key.nil?
274
-
275
- @node.find_by(node_key)
276
- rescue Node::ReloadNeeded
277
- update_cluster_info!(node_key)
278
- @node.find_by(node_key)
279
- end
280
-
281
- def update_cluster_info!(node_key = nil)
282
- unless node_key.nil?
283
- host, port = NodeKey.split(node_key)
284
- @option.add_node(host, port)
285
- end
286
-
287
- @node.map(&:disconnect)
288
- @node, @slot = fetch_cluster_info!(@option)
289
- end
290
- end
291
- 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,67 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative "registry"
4
- require_relative "../errors"
5
- require "hiredis/connection"
6
- require "timeout"
7
-
8
- class Redis
9
- module Connection
10
- class Hiredis
11
- def self.connect(config)
12
- connection = ::Hiredis::Connection.new
13
- connect_timeout = (config.fetch(:connect_timeout, 0) * 1_000_000).to_i
14
-
15
- if config[:scheme] == "unix"
16
- connection.connect_unix(config[:path], connect_timeout)
17
- elsif config[:scheme] == "rediss" || config[:ssl]
18
- raise NotImplementedError, "SSL not supported by hiredis driver"
19
- else
20
- connection.connect(config[:host], config[:port], connect_timeout)
21
- end
22
-
23
- instance = new(connection)
24
- instance.timeout = config[:read_timeout]
25
- instance
26
- rescue Errno::ETIMEDOUT
27
- raise TimeoutError
28
- end
29
-
30
- def initialize(connection)
31
- @connection = connection
32
- end
33
-
34
- def connected?
35
- @connection&.connected?
36
- end
37
-
38
- def timeout=(timeout)
39
- # Hiredis works with microsecond timeouts
40
- @connection.timeout = Integer(timeout * 1_000_000)
41
- end
42
-
43
- def disconnect
44
- @connection.disconnect
45
- @connection = nil
46
- end
47
-
48
- def write(command)
49
- @connection.write(command.flatten(1))
50
- rescue Errno::EAGAIN
51
- raise TimeoutError
52
- end
53
-
54
- def read
55
- reply = @connection.read
56
- reply = CommandError.new(reply.message) if reply.is_a?(RuntimeError)
57
- reply
58
- rescue Errno::EAGAIN
59
- raise TimeoutError
60
- rescue RuntimeError => err
61
- raise ProtocolError, err.message
62
- end
63
- end
64
- end
65
- end
66
-
67
- 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