redis 4.6.0 → 5.0.4

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 +60 -1
  3. data/README.md +75 -146
  4. data/lib/redis/client.rb +92 -608
  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 +8 -5
  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 +19 -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 +27 -13
  17. data/lib/redis/commands/streams.rb +12 -10
  18. data/lib/redis/commands/strings.rb +16 -15
  19. data/lib/redis/commands/transactions.rb +26 -3
  20. data/lib/redis/commands.rb +1 -8
  21. data/lib/redis/distributed.rb +100 -67
  22. data/lib/redis/errors.rb +14 -41
  23. data/lib/redis/hash_ring.rb +26 -26
  24. data/lib/redis/pipeline.rb +56 -203
  25. data/lib/redis/subscribe.rb +23 -15
  26. data/lib/redis/version.rb +1 -1
  27. data/lib/redis.rb +90 -178
  28. metadata +9 -53
  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 -37
  35. data/lib/redis/cluster/option.rb +0 -93
  36. data/lib/redis/cluster/slot.rb +0 -86
  37. data/lib/redis/cluster/slot_loader.rb +0 -49
  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 -431
  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,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../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
- info = {}
13
-
14
- nodes.each do |node|
15
- info = fetch_node_info(node)
16
- info.empty? ? next : break
17
- end
18
-
19
- return info unless info.empty?
20
-
21
- raise CannotConnectError, 'Redis client could not connect to any cluster nodes'
22
- end
23
-
24
- def fetch_node_info(node)
25
- node.call(%i[cluster nodes])
26
- .split("\n")
27
- .map { |str| str.split(' ') }
28
- .map { |arr| [arr[1].split('@').first, (arr[2].split(',') & %w[master slave]).first] }
29
- .to_h
30
- rescue CannotConnectError, ConnectionError, CommandError
31
- {} # can retry on another node
32
- end
33
-
34
- private_class_method :fetch_node_info
35
- end
36
- end
37
- end
@@ -1,93 +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
- add_common_node_option_if_needed(options, @node_opts, :scheme)
21
- add_common_node_option_if_needed(options, @node_opts, :username)
22
- add_common_node_option_if_needed(options, @node_opts, :password)
23
- @options = options
24
- end
25
-
26
- def per_node_key
27
- @node_opts.map { |opt| [NodeKey.build_from_host_port(opt[:host], opt[:port]), @options.merge(opt)] }
28
- .to_h
29
- end
30
-
31
- def use_replica?
32
- @replica
33
- end
34
-
35
- def update_node(addrs)
36
- @node_opts = build_node_options(addrs)
37
- end
38
-
39
- def add_node(host, port)
40
- @node_opts << { host: host, port: port }
41
- end
42
-
43
- private
44
-
45
- def build_node_options(addrs)
46
- raise InvalidClientOptionError, 'Redis option of `cluster` must be an Array' unless addrs.is_a?(Array)
47
-
48
- addrs.map { |addr| parse_node_addr(addr) }
49
- end
50
-
51
- def parse_node_addr(addr)
52
- case addr
53
- when String
54
- parse_node_url(addr)
55
- when Hash
56
- parse_node_option(addr)
57
- else
58
- raise InvalidClientOptionError, 'Redis option of `cluster` must includes String or Hash'
59
- end
60
- end
61
-
62
- def parse_node_url(addr)
63
- uri = URI(addr)
64
- raise InvalidClientOptionError, "Invalid uri scheme #{addr}" unless VALID_SCHEMES.include?(uri.scheme)
65
-
66
- db = uri.path.split('/')[1]&.to_i
67
-
68
- { scheme: uri.scheme, username: uri.user, password: uri.password, host: uri.host, port: uri.port, db: db }
69
- .reject { |_, v| v.nil? || v == '' }
70
- rescue URI::InvalidURIError => err
71
- raise InvalidClientOptionError, err.message
72
- end
73
-
74
- def parse_node_option(addr)
75
- addr = addr.map { |k, v| [k.to_sym, v] }.to_h
76
- if addr.values_at(:host, :port).any?(&:nil?)
77
- raise InvalidClientOptionError, 'Redis option of `cluster` must includes `:host` and `:port` keys'
78
- end
79
-
80
- addr
81
- end
82
-
83
- # Redis cluster node returns only host and port information.
84
- # So we should complement additional information such as:
85
- # scheme, username, password and so on.
86
- def add_common_node_option_if_needed(options, node_opts, key)
87
- return options if options[key].nil? && node_opts.first[key].nil?
88
-
89
- options[key] ||= node_opts.first[key]
90
- end
91
- end
92
- end
93
- 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,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