redis 4.5.1 → 5.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/CHANGELOG.md +120 -0
- data/README.md +82 -153
- data/lib/redis/client.rb +77 -615
- data/lib/redis/commands/bitmaps.rb +66 -0
- data/lib/redis/commands/cluster.rb +28 -0
- data/lib/redis/commands/connection.rb +53 -0
- data/lib/redis/commands/geo.rb +84 -0
- data/lib/redis/commands/hashes.rb +254 -0
- data/lib/redis/commands/hyper_log_log.rb +37 -0
- data/lib/redis/commands/keys.rb +437 -0
- data/lib/redis/commands/lists.rb +285 -0
- data/lib/redis/commands/pubsub.rb +54 -0
- data/lib/redis/commands/scripting.rb +114 -0
- data/lib/redis/commands/server.rb +188 -0
- data/lib/redis/commands/sets.rb +214 -0
- data/lib/redis/commands/sorted_sets.rb +818 -0
- data/lib/redis/commands/streams.rb +402 -0
- data/lib/redis/commands/strings.rb +314 -0
- data/lib/redis/commands/transactions.rb +115 -0
- data/lib/redis/commands.rb +237 -0
- data/lib/redis/distributed.rb +140 -70
- data/lib/redis/errors.rb +15 -41
- data/lib/redis/hash_ring.rb +26 -26
- data/lib/redis/pipeline.rb +66 -120
- data/lib/redis/subscribe.rb +23 -15
- data/lib/redis/version.rb +1 -1
- data/lib/redis.rb +106 -3736
- metadata +26 -53
- data/lib/redis/cluster/command.rb +0 -81
- data/lib/redis/cluster/command_loader.rb +0 -33
- data/lib/redis/cluster/key_slot_converter.rb +0 -72
- data/lib/redis/cluster/node.rb +0 -108
- data/lib/redis/cluster/node_key.rb +0 -31
- data/lib/redis/cluster/node_loader.rb +0 -37
- data/lib/redis/cluster/option.rb +0 -93
- data/lib/redis/cluster/slot.rb +0 -86
- data/lib/redis/cluster/slot_loader.rb +0 -49
- data/lib/redis/cluster.rb +0 -291
- data/lib/redis/connection/command_helper.rb +0 -41
- data/lib/redis/connection/hiredis.rb +0 -67
- data/lib/redis/connection/registry.rb +0 -13
- data/lib/redis/connection/ruby.rb +0 -428
- data/lib/redis/connection/synchrony.rb +0 -146
- data/lib/redis/connection.rb +0 -11
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version:
|
4
|
+
version: 5.0.6
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Ezra Zygmuntowicz
|
@@ -16,50 +16,22 @@ authors:
|
|
16
16
|
autorequire:
|
17
17
|
bindir: bin
|
18
18
|
cert_chain: []
|
19
|
-
date:
|
19
|
+
date: 2023-01-16 00:00:00.000000000 Z
|
20
20
|
dependencies:
|
21
21
|
- !ruby/object:Gem::Dependency
|
22
|
-
name:
|
22
|
+
name: redis-client
|
23
23
|
requirement: !ruby/object:Gem::Requirement
|
24
24
|
requirements:
|
25
25
|
- - ">="
|
26
26
|
- !ruby/object:Gem::Version
|
27
|
-
version:
|
28
|
-
type: :
|
27
|
+
version: 0.9.0
|
28
|
+
type: :runtime
|
29
29
|
prerelease: false
|
30
30
|
version_requirements: !ruby/object:Gem::Requirement
|
31
31
|
requirements:
|
32
32
|
- - ">="
|
33
33
|
- !ruby/object:Gem::Version
|
34
|
-
version:
|
35
|
-
- !ruby/object:Gem::Dependency
|
36
|
-
name: hiredis
|
37
|
-
requirement: !ruby/object:Gem::Requirement
|
38
|
-
requirements:
|
39
|
-
- - ">="
|
40
|
-
- !ruby/object:Gem::Version
|
41
|
-
version: '0'
|
42
|
-
type: :development
|
43
|
-
prerelease: false
|
44
|
-
version_requirements: !ruby/object:Gem::Requirement
|
45
|
-
requirements:
|
46
|
-
- - ">="
|
47
|
-
- !ruby/object:Gem::Version
|
48
|
-
version: '0'
|
49
|
-
- !ruby/object:Gem::Dependency
|
50
|
-
name: mocha
|
51
|
-
requirement: !ruby/object:Gem::Requirement
|
52
|
-
requirements:
|
53
|
-
- - ">="
|
54
|
-
- !ruby/object:Gem::Version
|
55
|
-
version: '0'
|
56
|
-
type: :development
|
57
|
-
prerelease: false
|
58
|
-
version_requirements: !ruby/object:Gem::Requirement
|
59
|
-
requirements:
|
60
|
-
- - ">="
|
61
|
-
- !ruby/object:Gem::Version
|
62
|
-
version: '0'
|
34
|
+
version: 0.9.0
|
63
35
|
description: |2
|
64
36
|
A Ruby client that tries to match Redis' API one-to-one, while still
|
65
37
|
providing an idiomatic interface.
|
@@ -74,22 +46,23 @@ files:
|
|
74
46
|
- README.md
|
75
47
|
- lib/redis.rb
|
76
48
|
- lib/redis/client.rb
|
77
|
-
- lib/redis/
|
78
|
-
- lib/redis/
|
79
|
-
- lib/redis/cluster
|
80
|
-
- lib/redis/
|
81
|
-
- lib/redis/
|
82
|
-
- lib/redis/
|
83
|
-
- lib/redis/
|
84
|
-
- lib/redis/
|
85
|
-
- lib/redis/
|
86
|
-
- lib/redis/
|
87
|
-
- lib/redis/
|
88
|
-
- lib/redis/
|
89
|
-
- lib/redis/
|
90
|
-
- lib/redis/
|
91
|
-
- lib/redis/
|
92
|
-
- lib/redis/
|
49
|
+
- lib/redis/commands.rb
|
50
|
+
- lib/redis/commands/bitmaps.rb
|
51
|
+
- lib/redis/commands/cluster.rb
|
52
|
+
- lib/redis/commands/connection.rb
|
53
|
+
- lib/redis/commands/geo.rb
|
54
|
+
- lib/redis/commands/hashes.rb
|
55
|
+
- lib/redis/commands/hyper_log_log.rb
|
56
|
+
- lib/redis/commands/keys.rb
|
57
|
+
- lib/redis/commands/lists.rb
|
58
|
+
- lib/redis/commands/pubsub.rb
|
59
|
+
- lib/redis/commands/scripting.rb
|
60
|
+
- lib/redis/commands/server.rb
|
61
|
+
- lib/redis/commands/sets.rb
|
62
|
+
- lib/redis/commands/sorted_sets.rb
|
63
|
+
- lib/redis/commands/streams.rb
|
64
|
+
- lib/redis/commands/strings.rb
|
65
|
+
- lib/redis/commands/transactions.rb
|
93
66
|
- lib/redis/distributed.rb
|
94
67
|
- lib/redis/errors.rb
|
95
68
|
- lib/redis/hash_ring.rb
|
@@ -102,9 +75,9 @@ licenses:
|
|
102
75
|
metadata:
|
103
76
|
bug_tracker_uri: https://github.com/redis/redis-rb/issues
|
104
77
|
changelog_uri: https://github.com/redis/redis-rb/blob/master/CHANGELOG.md
|
105
|
-
documentation_uri: https://www.rubydoc.info/gems/redis/
|
78
|
+
documentation_uri: https://www.rubydoc.info/gems/redis/5.0.6
|
106
79
|
homepage_uri: https://github.com/redis/redis-rb
|
107
|
-
source_code_uri: https://github.com/redis/redis-rb/tree/
|
80
|
+
source_code_uri: https://github.com/redis/redis-rb/tree/v5.0.6
|
108
81
|
post_install_message:
|
109
82
|
rdoc_options: []
|
110
83
|
require_paths:
|
@@ -113,7 +86,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
113
86
|
requirements:
|
114
87
|
- - ">="
|
115
88
|
- !ruby/object:Gem::Version
|
116
|
-
version: 2.
|
89
|
+
version: 2.5.0
|
117
90
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
118
91
|
requirements:
|
119
92
|
- - ">="
|
@@ -1,81 +0,0 @@
|
|
1
|
-
# frozen_string_literal: true
|
2
|
-
|
3
|
-
require_relative '../errors'
|
4
|
-
|
5
|
-
class Redis
|
6
|
-
class Cluster
|
7
|
-
# Keep details about Redis commands for Redis Cluster Client.
|
8
|
-
# @see https://redis.io/commands/command
|
9
|
-
class Command
|
10
|
-
def initialize(details)
|
11
|
-
@details = pick_details(details)
|
12
|
-
end
|
13
|
-
|
14
|
-
def extract_first_key(command)
|
15
|
-
i = determine_first_key_position(command)
|
16
|
-
return '' if i == 0
|
17
|
-
|
18
|
-
key = command[i].to_s
|
19
|
-
hash_tag = extract_hash_tag(key)
|
20
|
-
hash_tag.empty? ? key : hash_tag
|
21
|
-
end
|
22
|
-
|
23
|
-
def should_send_to_master?(command)
|
24
|
-
dig_details(command, :write)
|
25
|
-
end
|
26
|
-
|
27
|
-
def should_send_to_slave?(command)
|
28
|
-
dig_details(command, :readonly)
|
29
|
-
end
|
30
|
-
|
31
|
-
private
|
32
|
-
|
33
|
-
def pick_details(details)
|
34
|
-
details.map do |command, detail|
|
35
|
-
[command, {
|
36
|
-
first_key_position: detail[:first],
|
37
|
-
write: detail[:flags].include?('write'),
|
38
|
-
readonly: detail[:flags].include?('readonly')
|
39
|
-
}]
|
40
|
-
end.to_h
|
41
|
-
end
|
42
|
-
|
43
|
-
def dig_details(command, key)
|
44
|
-
name = command.first.to_s
|
45
|
-
return unless @details.key?(name)
|
46
|
-
|
47
|
-
@details.fetch(name).fetch(key)
|
48
|
-
end
|
49
|
-
|
50
|
-
def determine_first_key_position(command)
|
51
|
-
case command.first.to_s.downcase
|
52
|
-
when 'eval', 'evalsha', 'migrate', 'zinterstore', 'zunionstore' then 3
|
53
|
-
when 'object' then 2
|
54
|
-
when 'memory'
|
55
|
-
command[1].to_s.casecmp('usage').zero? ? 2 : 0
|
56
|
-
when 'scan', 'sscan', 'hscan', 'zscan'
|
57
|
-
determine_optional_key_position(command, 'match')
|
58
|
-
when 'xread', 'xreadgroup'
|
59
|
-
determine_optional_key_position(command, 'streams')
|
60
|
-
else
|
61
|
-
dig_details(command, :first_key_position).to_i
|
62
|
-
end
|
63
|
-
end
|
64
|
-
|
65
|
-
def determine_optional_key_position(command, option_name)
|
66
|
-
idx = command.map(&:to_s).map(&:downcase).index(option_name)
|
67
|
-
idx.nil? ? 0 : idx + 1
|
68
|
-
end
|
69
|
-
|
70
|
-
# @see https://redis.io/topics/cluster-spec#keys-hash-tags Keys hash tags
|
71
|
-
def extract_hash_tag(key)
|
72
|
-
s = key.index('{')
|
73
|
-
e = key.index('}', s.to_i + 1)
|
74
|
-
|
75
|
-
return '' if s.nil? || e.nil?
|
76
|
-
|
77
|
-
key[s + 1..e - 1]
|
78
|
-
end
|
79
|
-
end
|
80
|
-
end
|
81
|
-
end
|
@@ -1,33 +0,0 @@
|
|
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
|
-
nodes.each do |node|
|
14
|
-
begin
|
15
|
-
return fetch_command_details(node)
|
16
|
-
rescue CannotConnectError, ConnectionError, CommandError
|
17
|
-
next # can retry on another node
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
raise CannotConnectError, 'Redis client could not connect to any cluster nodes'
|
22
|
-
end
|
23
|
-
|
24
|
-
def fetch_command_details(node)
|
25
|
-
node.call(%i[command]).map do |reply|
|
26
|
-
[reply[0], { arity: reply[1], flags: reply[2], first: reply[3], last: reply[4], step: reply[5] }]
|
27
|
-
end.to_h
|
28
|
-
end
|
29
|
-
|
30
|
-
private_class_method :fetch_command_details
|
31
|
-
end
|
32
|
-
end
|
33
|
-
end
|
@@ -1,72 +0,0 @@
|
|
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
|
data/lib/redis/cluster/node.rb
DELETED
@@ -1,108 +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
|
-
private
|
62
|
-
|
63
|
-
def replica_disabled?
|
64
|
-
!@with_replica
|
65
|
-
end
|
66
|
-
|
67
|
-
def master?(node_key)
|
68
|
-
!slave?(node_key)
|
69
|
-
end
|
70
|
-
|
71
|
-
def slave?(node_key)
|
72
|
-
@node_flags[node_key] == ROLE_SLAVE
|
73
|
-
end
|
74
|
-
|
75
|
-
def build_clients(options)
|
76
|
-
clients = options.map do |node_key, option|
|
77
|
-
next if replica_disabled? && slave?(node_key)
|
78
|
-
|
79
|
-
option = option.merge(readonly: true) if slave?(node_key)
|
80
|
-
|
81
|
-
client = Client.new(option)
|
82
|
-
[node_key, client]
|
83
|
-
end
|
84
|
-
|
85
|
-
clients.compact.to_h
|
86
|
-
end
|
87
|
-
|
88
|
-
def try_map
|
89
|
-
errors = {}
|
90
|
-
results = {}
|
91
|
-
|
92
|
-
@clients.each do |node_key, client|
|
93
|
-
begin
|
94
|
-
reply = yield(node_key, client)
|
95
|
-
results[node_key] = reply unless reply.nil?
|
96
|
-
rescue CommandError => err
|
97
|
-
errors[node_key] = err
|
98
|
-
next
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
return results if errors.empty?
|
103
|
-
|
104
|
-
raise CommandErrorCollection, errors
|
105
|
-
end
|
106
|
-
end
|
107
|
-
end
|
108
|
-
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
|
data/lib/redis/cluster/option.rb
DELETED
@@ -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
|
data/lib/redis/cluster/slot.rb
DELETED
@@ -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
|