redis 4.0.1 → 4.0.3
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/.gitignore +3 -0
- data/.travis.yml +17 -29
- data/.travis/Gemfile +5 -0
- data/CHANGELOG.md +29 -0
- data/Gemfile +5 -0
- data/README.md +1 -1
- data/bin/build +71 -0
- data/lib/redis.rb +198 -12
- data/lib/redis/client.rb +26 -12
- data/lib/redis/cluster.rb +285 -0
- data/lib/redis/cluster/command.rb +81 -0
- data/lib/redis/cluster/command_loader.rb +32 -0
- data/lib/redis/cluster/key_slot_converter.rb +72 -0
- data/lib/redis/cluster/node.rb +104 -0
- data/lib/redis/cluster/node_key.rb +35 -0
- data/lib/redis/cluster/node_loader.rb +35 -0
- data/lib/redis/cluster/option.rb +76 -0
- data/lib/redis/cluster/slot.rb +69 -0
- data/lib/redis/cluster/slot_loader.rb +47 -0
- data/lib/redis/connection/ruby.rb +5 -2
- data/lib/redis/distributed.rb +10 -2
- data/lib/redis/errors.rb +46 -0
- data/lib/redis/pipeline.rb +9 -1
- data/lib/redis/version.rb +1 -1
- data/makefile +54 -22
- data/redis.gemspec +2 -1
- data/test/client_test.rb +17 -0
- data/test/cluster_abnormal_state_test.rb +38 -0
- data/test/cluster_blocking_commands_test.rb +15 -0
- data/test/cluster_client_internals_test.rb +77 -0
- data/test/cluster_client_key_hash_tags_test.rb +88 -0
- data/test/cluster_client_options_test.rb +147 -0
- data/test/cluster_client_pipelining_test.rb +59 -0
- data/test/cluster_client_replicas_test.rb +36 -0
- data/test/cluster_client_slots_test.rb +94 -0
- data/test/cluster_client_transactions_test.rb +71 -0
- data/test/cluster_commands_on_cluster_test.rb +165 -0
- data/test/cluster_commands_on_connection_test.rb +40 -0
- data/test/cluster_commands_on_geo_test.rb +74 -0
- data/test/cluster_commands_on_hashes_test.rb +11 -0
- data/test/cluster_commands_on_hyper_log_log_test.rb +17 -0
- data/test/cluster_commands_on_keys_test.rb +134 -0
- data/test/cluster_commands_on_lists_test.rb +15 -0
- data/test/cluster_commands_on_pub_sub_test.rb +101 -0
- data/test/cluster_commands_on_scripting_test.rb +56 -0
- data/test/cluster_commands_on_server_test.rb +221 -0
- data/test/cluster_commands_on_sets_test.rb +39 -0
- data/test/cluster_commands_on_sorted_sets_test.rb +35 -0
- data/test/cluster_commands_on_streams_test.rb +196 -0
- data/test/cluster_commands_on_strings_test.rb +15 -0
- data/test/cluster_commands_on_transactions_test.rb +41 -0
- data/test/cluster_commands_on_value_types_test.rb +14 -0
- data/test/commands_on_geo_test.rb +116 -0
- data/test/commands_on_hashes_test.rb +2 -14
- data/test/commands_on_hyper_log_log_test.rb +2 -14
- data/test/commands_on_lists_test.rb +2 -13
- data/test/commands_on_sets_test.rb +2 -70
- data/test/commands_on_sorted_sets_test.rb +2 -145
- data/test/commands_on_strings_test.rb +2 -94
- data/test/commands_on_value_types_test.rb +36 -0
- data/test/distributed_blocking_commands_test.rb +8 -0
- data/test/distributed_commands_on_hashes_test.rb +16 -3
- data/test/distributed_commands_on_hyper_log_log_test.rb +8 -13
- data/test/distributed_commands_on_lists_test.rb +4 -5
- data/test/distributed_commands_on_sets_test.rb +45 -46
- data/test/distributed_commands_on_sorted_sets_test.rb +51 -8
- data/test/distributed_commands_on_strings_test.rb +10 -0
- data/test/distributed_commands_on_value_types_test.rb +36 -0
- data/test/helper.rb +176 -32
- data/test/internals_test.rb +20 -1
- data/test/lint/blocking_commands.rb +40 -16
- data/test/lint/hashes.rb +41 -0
- data/test/lint/hyper_log_log.rb +15 -1
- data/test/lint/lists.rb +16 -0
- data/test/lint/sets.rb +142 -0
- data/test/lint/sorted_sets.rb +183 -2
- data/test/lint/strings.rb +102 -0
- data/test/pipelining_commands_test.rb +8 -0
- data/test/support/cluster/orchestrator.rb +199 -0
- data/test/support/redis_mock.rb +1 -1
- data/test/transactions_test.rb +10 -0
- metadata +81 -2
@@ -0,0 +1,32 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../errors'
|
4
|
+
|
5
|
+
class Redis
|
6
|
+
class Cluster
|
7
|
+
# Load details about Redis commands for Redis Cluster Client
|
8
|
+
# @see https://redis.io/commands/command
|
9
|
+
module CommandLoader
|
10
|
+
module_function
|
11
|
+
|
12
|
+
def load(nodes)
|
13
|
+
details = {}
|
14
|
+
|
15
|
+
nodes.each do |node|
|
16
|
+
details = fetch_command_details(node)
|
17
|
+
details.empty? ? next : break
|
18
|
+
end
|
19
|
+
|
20
|
+
details
|
21
|
+
end
|
22
|
+
|
23
|
+
def fetch_command_details(node)
|
24
|
+
node.call(%i[command]).map do |reply|
|
25
|
+
[reply[0], { arity: reply[1], flags: reply[2], first: reply[3], last: reply[4], step: reply[5] }]
|
26
|
+
end.to_h
|
27
|
+
rescue CannotConnectError, ConnectionError, CommandError
|
28
|
+
{} # can retry on another node
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
@@ -0,0 +1,72 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
class Redis
|
4
|
+
class Cluster
|
5
|
+
# Key to slot converter for Redis Cluster Client
|
6
|
+
#
|
7
|
+
# We can test it by `CLUSTER KEYSLOT` command.
|
8
|
+
#
|
9
|
+
# @see https://github.com/antirez/redis-rb-cluster
|
10
|
+
# Reference implementation in Ruby
|
11
|
+
# @see https://redis.io/topics/cluster-spec#appendix
|
12
|
+
# Reference implementation in ANSI C
|
13
|
+
# @see https://redis.io/commands/cluster-keyslot
|
14
|
+
# CLUSTER KEYSLOT command reference
|
15
|
+
#
|
16
|
+
# Copyright (C) 2013 Salvatore Sanfilippo <antirez@gmail.com>
|
17
|
+
module KeySlotConverter
|
18
|
+
XMODEM_CRC16_LOOKUP = [
|
19
|
+
0x0000, 0x1021, 0x2042, 0x3063, 0x4084, 0x50a5, 0x60c6, 0x70e7,
|
20
|
+
0x8108, 0x9129, 0xa14a, 0xb16b, 0xc18c, 0xd1ad, 0xe1ce, 0xf1ef,
|
21
|
+
0x1231, 0x0210, 0x3273, 0x2252, 0x52b5, 0x4294, 0x72f7, 0x62d6,
|
22
|
+
0x9339, 0x8318, 0xb37b, 0xa35a, 0xd3bd, 0xc39c, 0xf3ff, 0xe3de,
|
23
|
+
0x2462, 0x3443, 0x0420, 0x1401, 0x64e6, 0x74c7, 0x44a4, 0x5485,
|
24
|
+
0xa56a, 0xb54b, 0x8528, 0x9509, 0xe5ee, 0xf5cf, 0xc5ac, 0xd58d,
|
25
|
+
0x3653, 0x2672, 0x1611, 0x0630, 0x76d7, 0x66f6, 0x5695, 0x46b4,
|
26
|
+
0xb75b, 0xa77a, 0x9719, 0x8738, 0xf7df, 0xe7fe, 0xd79d, 0xc7bc,
|
27
|
+
0x48c4, 0x58e5, 0x6886, 0x78a7, 0x0840, 0x1861, 0x2802, 0x3823,
|
28
|
+
0xc9cc, 0xd9ed, 0xe98e, 0xf9af, 0x8948, 0x9969, 0xa90a, 0xb92b,
|
29
|
+
0x5af5, 0x4ad4, 0x7ab7, 0x6a96, 0x1a71, 0x0a50, 0x3a33, 0x2a12,
|
30
|
+
0xdbfd, 0xcbdc, 0xfbbf, 0xeb9e, 0x9b79, 0x8b58, 0xbb3b, 0xab1a,
|
31
|
+
0x6ca6, 0x7c87, 0x4ce4, 0x5cc5, 0x2c22, 0x3c03, 0x0c60, 0x1c41,
|
32
|
+
0xedae, 0xfd8f, 0xcdec, 0xddcd, 0xad2a, 0xbd0b, 0x8d68, 0x9d49,
|
33
|
+
0x7e97, 0x6eb6, 0x5ed5, 0x4ef4, 0x3e13, 0x2e32, 0x1e51, 0x0e70,
|
34
|
+
0xff9f, 0xefbe, 0xdfdd, 0xcffc, 0xbf1b, 0xaf3a, 0x9f59, 0x8f78,
|
35
|
+
0x9188, 0x81a9, 0xb1ca, 0xa1eb, 0xd10c, 0xc12d, 0xf14e, 0xe16f,
|
36
|
+
0x1080, 0x00a1, 0x30c2, 0x20e3, 0x5004, 0x4025, 0x7046, 0x6067,
|
37
|
+
0x83b9, 0x9398, 0xa3fb, 0xb3da, 0xc33d, 0xd31c, 0xe37f, 0xf35e,
|
38
|
+
0x02b1, 0x1290, 0x22f3, 0x32d2, 0x4235, 0x5214, 0x6277, 0x7256,
|
39
|
+
0xb5ea, 0xa5cb, 0x95a8, 0x8589, 0xf56e, 0xe54f, 0xd52c, 0xc50d,
|
40
|
+
0x34e2, 0x24c3, 0x14a0, 0x0481, 0x7466, 0x6447, 0x5424, 0x4405,
|
41
|
+
0xa7db, 0xb7fa, 0x8799, 0x97b8, 0xe75f, 0xf77e, 0xc71d, 0xd73c,
|
42
|
+
0x26d3, 0x36f2, 0x0691, 0x16b0, 0x6657, 0x7676, 0x4615, 0x5634,
|
43
|
+
0xd94c, 0xc96d, 0xf90e, 0xe92f, 0x99c8, 0x89e9, 0xb98a, 0xa9ab,
|
44
|
+
0x5844, 0x4865, 0x7806, 0x6827, 0x18c0, 0x08e1, 0x3882, 0x28a3,
|
45
|
+
0xcb7d, 0xdb5c, 0xeb3f, 0xfb1e, 0x8bf9, 0x9bd8, 0xabbb, 0xbb9a,
|
46
|
+
0x4a75, 0x5a54, 0x6a37, 0x7a16, 0x0af1, 0x1ad0, 0x2ab3, 0x3a92,
|
47
|
+
0xfd2e, 0xed0f, 0xdd6c, 0xcd4d, 0xbdaa, 0xad8b, 0x9de8, 0x8dc9,
|
48
|
+
0x7c26, 0x6c07, 0x5c64, 0x4c45, 0x3ca2, 0x2c83, 0x1ce0, 0x0cc1,
|
49
|
+
0xef1f, 0xff3e, 0xcf5d, 0xdf7c, 0xaf9b, 0xbfba, 0x8fd9, 0x9ff8,
|
50
|
+
0x6e17, 0x7e36, 0x4e55, 0x5e74, 0x2e93, 0x3eb2, 0x0ed1, 0x1ef0
|
51
|
+
].freeze
|
52
|
+
|
53
|
+
HASH_SLOTS = 16_384
|
54
|
+
|
55
|
+
module_function
|
56
|
+
|
57
|
+
# Convert key into slot.
|
58
|
+
#
|
59
|
+
# @param key [String] the key of the redis command
|
60
|
+
#
|
61
|
+
# @return [Integer] slot number
|
62
|
+
def convert(key)
|
63
|
+
crc = 0
|
64
|
+
key.each_byte do |b|
|
65
|
+
crc = ((crc << 8) & 0xffff) ^ XMODEM_CRC16_LOOKUP[((crc >> 8) ^ b) & 0xff]
|
66
|
+
end
|
67
|
+
|
68
|
+
crc % HASH_SLOTS
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
@@ -0,0 +1,104 @@
|
|
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
|
+
client.call(command, &block)
|
43
|
+
end.values
|
44
|
+
end
|
45
|
+
|
46
|
+
def call_slave(command, &block)
|
47
|
+
return call_master(command, &block) if replica_disabled?
|
48
|
+
|
49
|
+
try_map do |node_key, client|
|
50
|
+
next if master?(node_key)
|
51
|
+
client.call(command, &block)
|
52
|
+
end.values
|
53
|
+
end
|
54
|
+
|
55
|
+
def process_all(commands, &block)
|
56
|
+
try_map { |_, client| client.process(commands, &block) }.values
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
|
61
|
+
def replica_disabled?
|
62
|
+
!@with_replica
|
63
|
+
end
|
64
|
+
|
65
|
+
def master?(node_key)
|
66
|
+
!slave?(node_key)
|
67
|
+
end
|
68
|
+
|
69
|
+
def slave?(node_key)
|
70
|
+
@node_flags[node_key] == ROLE_SLAVE
|
71
|
+
end
|
72
|
+
|
73
|
+
def build_clients(options)
|
74
|
+
clients = options.map do |node_key, option|
|
75
|
+
next if replica_disabled? && slave?(node_key)
|
76
|
+
|
77
|
+
client = Client.new(option)
|
78
|
+
client.call(%i[readonly]) if slave?(node_key)
|
79
|
+
[node_key, client]
|
80
|
+
end
|
81
|
+
|
82
|
+
clients.compact.to_h
|
83
|
+
end
|
84
|
+
|
85
|
+
def try_map
|
86
|
+
errors = {}
|
87
|
+
results = {}
|
88
|
+
|
89
|
+
@clients.each do |node_key, client|
|
90
|
+
begin
|
91
|
+
reply = yield(node_key, client)
|
92
|
+
results[node_key] = reply unless reply.nil?
|
93
|
+
rescue CommandError => err
|
94
|
+
errors[node_key] = err
|
95
|
+
next
|
96
|
+
end
|
97
|
+
end
|
98
|
+
|
99
|
+
return results if errors.empty?
|
100
|
+
raise CommandErrorCollection, errors
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
@@ -0,0 +1,35 @@
|
|
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
|
+
DEFAULT_SCHEME = 'redis'
|
10
|
+
SECURE_SCHEME = 'rediss'
|
11
|
+
DELIMITER = ':'
|
12
|
+
|
13
|
+
module_function
|
14
|
+
|
15
|
+
def to_node_urls(node_keys, secure:)
|
16
|
+
scheme = secure ? SECURE_SCHEME : DEFAULT_SCHEME
|
17
|
+
node_keys
|
18
|
+
.map { |k| k.split(DELIMITER) }
|
19
|
+
.map { |k| URI::Generic.build(scheme: scheme, host: k[0], port: k[1].to_i).to_s }
|
20
|
+
end
|
21
|
+
|
22
|
+
def split(node_key)
|
23
|
+
node_key.split(DELIMITER)
|
24
|
+
end
|
25
|
+
|
26
|
+
def build_from_uri(uri)
|
27
|
+
"#{uri.host}#{DELIMITER}#{uri.port}"
|
28
|
+
end
|
29
|
+
|
30
|
+
def build_from_host_port(host, port)
|
31
|
+
"#{host}#{DELIMITER}#{port}"
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,35 @@
|
|
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
|
+
end
|
34
|
+
end
|
35
|
+
end
|
@@ -0,0 +1,76 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../errors'
|
4
|
+
require_relative 'node_key'
|
5
|
+
|
6
|
+
class Redis
|
7
|
+
class Cluster
|
8
|
+
# Keep options for Redis Cluster Client
|
9
|
+
class Option
|
10
|
+
DEFAULT_SCHEME = 'redis'
|
11
|
+
SECURE_SCHEME = 'rediss'
|
12
|
+
VALID_SCHEMES = [DEFAULT_SCHEME, SECURE_SCHEME].freeze
|
13
|
+
|
14
|
+
def initialize(options)
|
15
|
+
options = options.dup
|
16
|
+
node_addrs = options.delete(:cluster)
|
17
|
+
@node_uris = build_node_uris(node_addrs)
|
18
|
+
@replica = options.delete(:replica) == true
|
19
|
+
@options = options
|
20
|
+
end
|
21
|
+
|
22
|
+
def per_node_key
|
23
|
+
@node_uris.map { |uri| [NodeKey.build_from_uri(uri), @options.merge(url: uri.to_s)] }
|
24
|
+
.to_h
|
25
|
+
end
|
26
|
+
|
27
|
+
def secure?
|
28
|
+
@node_uris.any? { |uri| uri.scheme == SECURE_SCHEME } || @options[:ssl_params] || false
|
29
|
+
end
|
30
|
+
|
31
|
+
def use_replica?
|
32
|
+
@replica
|
33
|
+
end
|
34
|
+
|
35
|
+
def update_node(addrs)
|
36
|
+
@node_uris = build_node_uris(addrs)
|
37
|
+
end
|
38
|
+
|
39
|
+
def add_node(host, port)
|
40
|
+
@node_uris << parse_node_hash(host: host, port: port)
|
41
|
+
end
|
42
|
+
|
43
|
+
private
|
44
|
+
|
45
|
+
def build_node_uris(addrs)
|
46
|
+
raise InvalidClientOptionError, 'Redis option of `cluster` must be an Array' unless addrs.is_a?(Array)
|
47
|
+
addrs.map { |addr| parse_node_addr(addr) }
|
48
|
+
end
|
49
|
+
|
50
|
+
def parse_node_addr(addr)
|
51
|
+
case addr
|
52
|
+
when String
|
53
|
+
parse_node_url(addr)
|
54
|
+
when Hash
|
55
|
+
parse_node_hash(addr)
|
56
|
+
else
|
57
|
+
raise InvalidClientOptionError, 'Redis option of `cluster` must includes String or Hash'
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
def parse_node_url(addr)
|
62
|
+
uri = URI(addr)
|
63
|
+
raise InvalidClientOptionError, "Invalid uri scheme #{addr}" unless VALID_SCHEMES.include?(uri.scheme)
|
64
|
+
uri
|
65
|
+
rescue URI::InvalidURIError => err
|
66
|
+
raise InvalidClientOptionError, err.message
|
67
|
+
end
|
68
|
+
|
69
|
+
def parse_node_hash(addr)
|
70
|
+
addr = addr.map { |k, v| [k.to_sym, v] }.to_h
|
71
|
+
raise InvalidClientOptionError, 'Redis option of `cluster` must includes `:host` and `:port` keys' if addr.values_at(:host, :port).any?(&:nil?)
|
72
|
+
URI::Generic.build(scheme: DEFAULT_SCHEME, host: addr[:host], port: addr[:port].to_i)
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
end
|
@@ -0,0 +1,69 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'set'
|
4
|
+
|
5
|
+
class Redis
|
6
|
+
class Cluster
|
7
|
+
# Keep slot and node key map for Redis Cluster Client
|
8
|
+
class Slot
|
9
|
+
ROLE_SLAVE = 'slave'
|
10
|
+
|
11
|
+
def initialize(available_slots, node_flags = {}, with_replica = false)
|
12
|
+
@with_replica = with_replica
|
13
|
+
@node_flags = node_flags
|
14
|
+
@map = build_slot_node_key_map(available_slots)
|
15
|
+
end
|
16
|
+
|
17
|
+
def exists?(slot)
|
18
|
+
@map.key?(slot)
|
19
|
+
end
|
20
|
+
|
21
|
+
def find_node_key_of_master(slot)
|
22
|
+
return nil unless exists?(slot)
|
23
|
+
|
24
|
+
@map[slot][:master]
|
25
|
+
end
|
26
|
+
|
27
|
+
def find_node_key_of_slave(slot)
|
28
|
+
return nil unless exists?(slot)
|
29
|
+
return find_node_key_of_master(slot) if replica_disabled?
|
30
|
+
|
31
|
+
@map[slot][:slaves].to_a.sample
|
32
|
+
end
|
33
|
+
|
34
|
+
def put(slot, node_key)
|
35
|
+
assign_node_key(@map, slot, node_key)
|
36
|
+
nil
|
37
|
+
end
|
38
|
+
|
39
|
+
private
|
40
|
+
|
41
|
+
def replica_disabled?
|
42
|
+
!@with_replica
|
43
|
+
end
|
44
|
+
|
45
|
+
def master?(node_key)
|
46
|
+
!slave?(node_key)
|
47
|
+
end
|
48
|
+
|
49
|
+
def slave?(node_key)
|
50
|
+
@node_flags[node_key] == ROLE_SLAVE
|
51
|
+
end
|
52
|
+
|
53
|
+
def build_slot_node_key_map(available_slots)
|
54
|
+
available_slots.each_with_object({}) do |(node_key, slots), acc|
|
55
|
+
slots.each { |slot| assign_node_key(acc, slot, node_key) }
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
def assign_node_key(mappings, slot, node_key)
|
60
|
+
mappings[slot] ||= { master: nil, slaves: Set.new }
|
61
|
+
if master?(node_key)
|
62
|
+
mappings[slot][:master] = node_key
|
63
|
+
else
|
64
|
+
mappings[slot][:slaves].add(node_key)
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
end
|
@@ -0,0 +1,47 @@
|
|
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 = Hash[*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
|
+
node.call(%i[cluster slots])
|
27
|
+
.map { |arr| parse_slot_info(arr, default_ip: node.host) }
|
28
|
+
.flatten
|
29
|
+
rescue CannotConnectError, ConnectionError, CommandError
|
30
|
+
{} # can retry on another node
|
31
|
+
end
|
32
|
+
|
33
|
+
def parse_slot_info(arr, default_ip:)
|
34
|
+
first_slot, last_slot = arr[0..1]
|
35
|
+
slot_range = (first_slot..last_slot).freeze
|
36
|
+
arr[2..-1].map { |addr| [stringify_node_key(addr, default_ip), slot_range] }
|
37
|
+
.flatten
|
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
|
+
end
|
46
|
+
end
|
47
|
+
end
|