redis 4.8.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 +54 -0
  3. data/README.md +88 -168
  4. data/lib/redis/client.rb +84 -614
  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 +5 -23
  12. data/lib/redis/commands/lists.rb +74 -25
  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 +31 -40
  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 -7
  21. data/lib/redis/distributed.rb +114 -64
  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 +91 -184
  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 -68
  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
@@ -1,120 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../errors'
4
-
5
- class Redis
6
- class Cluster
7
- # Keep client list of node for Redis Cluster Client
8
- class Node
9
- include Enumerable
10
-
11
- ReloadNeeded = Class.new(StandardError)
12
-
13
- ROLE_SLAVE = 'slave'
14
-
15
- def initialize(options, node_flags = {}, with_replica = false)
16
- @with_replica = with_replica
17
- @node_flags = node_flags
18
- @clients = build_clients(options)
19
- end
20
-
21
- def each(&block)
22
- @clients.values.each(&block)
23
- end
24
-
25
- def sample
26
- @clients.values.sample
27
- end
28
-
29
- def find_by(node_key)
30
- @clients.fetch(node_key)
31
- rescue KeyError
32
- raise ReloadNeeded
33
- end
34
-
35
- def call_all(command, &block)
36
- try_map { |_, client| client.call(command, &block) }.values
37
- end
38
-
39
- def call_master(command, &block)
40
- try_map do |node_key, client|
41
- next if slave?(node_key)
42
-
43
- client.call(command, &block)
44
- end.values
45
- end
46
-
47
- def call_slave(command, &block)
48
- return call_master(command, &block) if replica_disabled?
49
-
50
- try_map do |node_key, client|
51
- next if master?(node_key)
52
-
53
- client.call(command, &block)
54
- end.values
55
- end
56
-
57
- def process_all(commands, &block)
58
- try_map { |_, client| client.process(commands, &block) }.values
59
- end
60
-
61
- def scale_reading_clients
62
- reading_clients = []
63
-
64
- @clients.each do |node_key, client|
65
- next unless replica_disabled? ? master?(node_key) : slave?(node_key)
66
-
67
- reading_clients << client
68
- end
69
-
70
- reading_clients
71
- end
72
-
73
- private
74
-
75
- def replica_disabled?
76
- !@with_replica
77
- end
78
-
79
- def master?(node_key)
80
- !slave?(node_key)
81
- end
82
-
83
- def slave?(node_key)
84
- @node_flags[node_key] == ROLE_SLAVE
85
- end
86
-
87
- def build_clients(options)
88
- clients = options.map do |node_key, option|
89
- next if replica_disabled? && slave?(node_key)
90
-
91
- option = option.merge(readonly: true) if slave?(node_key)
92
-
93
- client = Client.new(option)
94
- [node_key, client]
95
- end
96
-
97
- clients.compact.to_h
98
- end
99
-
100
- def try_map
101
- errors = {}
102
- results = {}
103
-
104
- @clients.each do |node_key, client|
105
- begin
106
- reply = yield(node_key, client)
107
- results[node_key] = reply unless reply.nil?
108
- rescue CommandError => err
109
- errors[node_key] = err
110
- next
111
- end
112
- end
113
-
114
- return results if errors.empty?
115
-
116
- raise CommandErrorCollection, errors
117
- end
118
- end
119
- end
120
- end
@@ -1,31 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Redis
4
- class Cluster
5
- # Node key's format is `<ip>:<port>`.
6
- # It is different from node id.
7
- # Node id is internal identifying code in Redis Cluster.
8
- module NodeKey
9
- DELIMITER = ':'
10
-
11
- module_function
12
-
13
- def optionize(node_key)
14
- host, port = split(node_key)
15
- { host: host, port: port }
16
- end
17
-
18
- def split(node_key)
19
- node_key.split(DELIMITER)
20
- end
21
-
22
- def build_from_uri(uri)
23
- "#{uri.host}#{DELIMITER}#{uri.port}"
24
- end
25
-
26
- def build_from_host_port(host, port)
27
- "#{host}#{DELIMITER}#{port}"
28
- end
29
- end
30
- end
31
- end
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'redis/errors'
4
-
5
- class Redis
6
- class Cluster
7
- # Load and hashify node info for Redis Cluster Client
8
- module NodeLoader
9
- module_function
10
-
11
- def load_flags(nodes)
12
- errors = nodes.map do |node|
13
- begin
14
- return fetch_node_info(node)
15
- rescue CannotConnectError, ConnectionError, CommandError => error
16
- error
17
- end
18
- end
19
-
20
- raise InitialSetupError, errors
21
- end
22
-
23
- def fetch_node_info(node)
24
- node.call(%i[cluster nodes])
25
- .split("\n")
26
- .map { |str| str.split(' ') }
27
- .map { |arr| [arr[1].split('@').first, (arr[2].split(',') & %w[master slave]).first] }
28
- .to_h
29
- end
30
-
31
- private_class_method :fetch_node_info
32
- end
33
- end
34
- end
@@ -1,100 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../errors'
4
- require_relative 'node_key'
5
- require 'uri'
6
-
7
- class Redis
8
- class Cluster
9
- # Keep options for Redis Cluster Client
10
- class Option
11
- DEFAULT_SCHEME = 'redis'
12
- SECURE_SCHEME = 'rediss'
13
- VALID_SCHEMES = [DEFAULT_SCHEME, SECURE_SCHEME].freeze
14
-
15
- def initialize(options)
16
- options = options.dup
17
- node_addrs = options.delete(:cluster)
18
- @node_opts = build_node_options(node_addrs)
19
- @replica = options.delete(:replica) == true
20
- @fixed_hostname = options.delete(:fixed_hostname)
21
- add_common_node_option_if_needed(options, @node_opts, :scheme)
22
- add_common_node_option_if_needed(options, @node_opts, :username)
23
- add_common_node_option_if_needed(options, @node_opts, :password)
24
- @options = options
25
- end
26
-
27
- def per_node_key
28
- @node_opts.map do |opt|
29
- node_key = NodeKey.build_from_host_port(opt[:host], opt[:port])
30
- options = @options.merge(opt)
31
- options = options.merge(host: @fixed_hostname) if @fixed_hostname && !@fixed_hostname.empty?
32
- [node_key, options]
33
- end.to_h
34
- end
35
-
36
- def use_replica?
37
- @replica
38
- end
39
-
40
- def update_node(addrs)
41
- @node_opts = build_node_options(addrs)
42
- end
43
-
44
- def add_node(host, port)
45
- @node_opts << { host: host, port: port }
46
- end
47
-
48
- private
49
-
50
- def build_node_options(addrs)
51
- raise InvalidClientOptionError, 'Redis option of `cluster` must be an Array' unless addrs.is_a?(Array)
52
-
53
- addrs.map { |addr| parse_node_addr(addr) }
54
- end
55
-
56
- def parse_node_addr(addr)
57
- case addr
58
- when String
59
- parse_node_url(addr)
60
- when Hash
61
- parse_node_option(addr)
62
- else
63
- raise InvalidClientOptionError, 'Redis option of `cluster` must includes String or Hash'
64
- end
65
- end
66
-
67
- def parse_node_url(addr)
68
- uri = URI(addr)
69
- raise InvalidClientOptionError, "Invalid uri scheme #{addr}" unless VALID_SCHEMES.include?(uri.scheme)
70
-
71
- db = uri.path.split('/')[1]&.to_i
72
- username = uri.user ? URI.decode_www_form_component(uri.user) : nil
73
- password = uri.password ? URI.decode_www_form_component(uri.password) : nil
74
-
75
- { scheme: uri.scheme, username: username, password: password, host: uri.host, port: uri.port, db: db }
76
- .reject { |_, v| v.nil? || v == '' }
77
- rescue URI::InvalidURIError => err
78
- raise InvalidClientOptionError, err.message
79
- end
80
-
81
- def parse_node_option(addr)
82
- addr = addr.map { |k, v| [k.to_sym, v] }.to_h
83
- if addr.values_at(:host, :port).any?(&:nil?)
84
- raise InvalidClientOptionError, 'Redis option of `cluster` must includes `:host` and `:port` keys'
85
- end
86
-
87
- addr
88
- end
89
-
90
- # Redis cluster node returns only host and port information.
91
- # So we should complement additional information such as:
92
- # scheme, username, password and so on.
93
- def add_common_node_option_if_needed(options, node_opts, key)
94
- return options if options[key].nil? && node_opts.first[key].nil?
95
-
96
- options[key] ||= node_opts.first[key]
97
- end
98
- end
99
- end
100
- end
@@ -1,86 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- class Redis
4
- class Cluster
5
- # Keep slot and node key map for Redis Cluster Client
6
- class Slot
7
- ROLE_SLAVE = 'slave'
8
-
9
- def initialize(available_slots, node_flags = {}, with_replica = false)
10
- @with_replica = with_replica
11
- @node_flags = node_flags
12
- @map = build_slot_node_key_map(available_slots)
13
- end
14
-
15
- def exists?(slot)
16
- @map.key?(slot)
17
- end
18
-
19
- def find_node_key_of_master(slot)
20
- return nil unless exists?(slot)
21
-
22
- @map[slot][:master]
23
- end
24
-
25
- def find_node_key_of_slave(slot)
26
- return nil unless exists?(slot)
27
- return find_node_key_of_master(slot) if replica_disabled?
28
-
29
- @map[slot][:slaves].sample
30
- end
31
-
32
- def put(slot, node_key)
33
- # Since we're sharing a hash for build_slot_node_key_map, duplicate it
34
- # if it already exists instead of preserving as-is.
35
- @map[slot] = @map[slot] ? @map[slot].dup : { master: nil, slaves: [] }
36
-
37
- if master?(node_key)
38
- @map[slot][:master] = node_key
39
- elsif !@map[slot][:slaves].include?(node_key)
40
- @map[slot][:slaves] << node_key
41
- end
42
-
43
- nil
44
- end
45
-
46
- private
47
-
48
- def replica_disabled?
49
- !@with_replica
50
- end
51
-
52
- def master?(node_key)
53
- !slave?(node_key)
54
- end
55
-
56
- def slave?(node_key)
57
- @node_flags[node_key] == ROLE_SLAVE
58
- end
59
-
60
- # available_slots is mapping of node_key to list of slot ranges
61
- def build_slot_node_key_map(available_slots)
62
- by_ranges = {}
63
- available_slots.each do |node_key, slots_arr|
64
- by_ranges[slots_arr] ||= { master: nil, slaves: [] }
65
-
66
- if master?(node_key)
67
- by_ranges[slots_arr][:master] = node_key
68
- elsif !by_ranges[slots_arr][:slaves].include?(node_key)
69
- by_ranges[slots_arr][:slaves] << node_key
70
- end
71
- end
72
-
73
- by_slot = {}
74
- by_ranges.each do |slots_arr, nodes|
75
- slots_arr.each do |slots|
76
- slots.each do |slot|
77
- by_slot[slot] = nodes
78
- end
79
- end
80
- end
81
-
82
- by_slot
83
- end
84
- end
85
- end
86
- end
@@ -1,46 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'redis/errors'
4
- require 'redis/cluster/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
- errors = nodes.map do |node|
14
- begin
15
- return fetch_slot_info(node)
16
- rescue CannotConnectError, ConnectionError, CommandError => error
17
- error
18
- end
19
- end
20
-
21
- raise InitialSetupError, errors
22
- end
23
-
24
- def fetch_slot_info(node)
25
- hash_with_default_arr = Hash.new { |h, k| h[k] = [] }
26
- node.call(%i[cluster slots])
27
- .flat_map { |arr| parse_slot_info(arr, default_ip: node.host) }
28
- .each_with_object(hash_with_default_arr) { |arr, h| h[arr[0]] << arr[1] }
29
- end
30
-
31
- def parse_slot_info(arr, default_ip:)
32
- first_slot, last_slot = arr[0..1]
33
- slot_range = (first_slot..last_slot).freeze
34
- arr[2..-1].map { |addr| [stringify_node_key(addr, default_ip), slot_range] }
35
- end
36
-
37
- def stringify_node_key(arr, default_ip)
38
- ip, port = arr
39
- ip = default_ip if ip.empty? # When cluster is down
40
- NodeKey.build_from_host_port(ip, port)
41
- end
42
-
43
- private_class_method :fetch_slot_info, :parse_slot_info, :stringify_node_key
44
- end
45
- end
46
- end