redis-clustering 5.0.0.beta3
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 +7 -0
- data/CHANGELOG.md +0 -0
- data/LICENSE +20 -0
- data/README.md +71 -0
- data/lib/redis/cluster/client.rb +94 -0
- data/lib/redis/cluster/version.rb +9 -0
- data/lib/redis/cluster.rb +101 -0
- data/lib/redis-clustering.rb +3 -0
- metadata +79 -0
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA256:
|
3
|
+
metadata.gz: a1f30a75985b5fc7e5ea4f5dfa1b8daaf85e2f0e387c669977c9602035e940cf
|
4
|
+
data.tar.gz: 316047ae9330f0fee89d4870a6bbf56a79158bd8050d5f382956bae971618398
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 9de07f2ae00e29220f13a09aad0f2741c87e4f41597385775ec4f2dcc049fc92b7a1c559b8230d5519c8f91f41d880575017c4e0f4519fb04d5ca3f16c005906
|
7
|
+
data.tar.gz: e02b002e2c7b61c6acba9fc586fbd9b3f47c1cdf6e81f8e4f40d3d366dd740994040e4d9ad642c97c3a1527ae25947d90feb61f49d9fce2424af979171504002
|
data/CHANGELOG.md
ADDED
File without changes
|
data/LICENSE
ADDED
@@ -0,0 +1,20 @@
|
|
1
|
+
Copyright (c) 2009 Ezra Zygmuntowicz
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
4
|
+
a copy of this software and associated documentation files (the
|
5
|
+
"Software"), to deal in the Software without restriction, including
|
6
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
7
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
8
|
+
permit persons to whom the Software is furnished to do so, subject to
|
9
|
+
the following conditions:
|
10
|
+
|
11
|
+
The above copyright notice and this permission notice shall be
|
12
|
+
included in all copies or substantial portions of the Software.
|
13
|
+
|
14
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
15
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
16
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
17
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
18
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
19
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
20
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
# Redis::Cluster
|
2
|
+
|
3
|
+
## Getting started
|
4
|
+
|
5
|
+
Install with:
|
6
|
+
|
7
|
+
```
|
8
|
+
$ gem install redis-clustering
|
9
|
+
```
|
10
|
+
|
11
|
+
You can connect to Redis by instantiating the `Redis::Cluster` class:
|
12
|
+
|
13
|
+
```ruby
|
14
|
+
require "redis-clustering"
|
15
|
+
|
16
|
+
redis = Redis::Cluter.new(nodes: (7000..7005).map { |port| "redis://127.0.0.1:#{port}" })
|
17
|
+
```
|
18
|
+
|
19
|
+
NB: Both `redis_cluster` and `redis-cluster` are unrelated and abandoned gems.
|
20
|
+
|
21
|
+
```ruby
|
22
|
+
# Nodes can be passed to the client as an array of connection URLs.
|
23
|
+
nodes = (7000..7005).map { |port| "redis://127.0.0.1:#{port}" }
|
24
|
+
redis = Redis::Cluster.new(nodes: nodes)
|
25
|
+
|
26
|
+
# You can also specify the options as a Hash. The options are the same as for a single server connection.
|
27
|
+
(7000..7005).map { |port| { host: '127.0.0.1', port: port } }
|
28
|
+
```
|
29
|
+
|
30
|
+
You can also specify only a subset of the nodes, and the client will discover the missing ones using the [CLUSTER NODES](https://redis.io/commands/cluster-nodes) command.
|
31
|
+
|
32
|
+
```ruby
|
33
|
+
Redis::Cluster.new(nodes: %w[redis://127.0.0.1:7000])
|
34
|
+
```
|
35
|
+
|
36
|
+
If you want [the connection to be able to read from any replica](https://redis.io/commands/readonly), you must pass the `replica: true`. Note that this connection won't be usable to write keys.
|
37
|
+
|
38
|
+
```ruby
|
39
|
+
Redis::Cluster.new(nodes: nodes, replica: true)
|
40
|
+
```
|
41
|
+
|
42
|
+
The calling code is responsible for [avoiding cross slot commands](https://redis.io/topics/cluster-spec#keys-distribution-model).
|
43
|
+
|
44
|
+
```ruby
|
45
|
+
redis = Redis::Cluster.new(nodes: %w[redis://127.0.0.1:7000])
|
46
|
+
|
47
|
+
redis.mget('key1', 'key2')
|
48
|
+
#=> Redis::CommandError (CROSSSLOT Keys in request don't hash to the same slot)
|
49
|
+
|
50
|
+
redis.mget('{key}1', '{key}2')
|
51
|
+
#=> [nil, nil]
|
52
|
+
```
|
53
|
+
|
54
|
+
* The client automatically reconnects after a failover occurred, but the caller is responsible for handling errors while it is happening.
|
55
|
+
* The client support permanent node failures, and will reroute requests to promoted slaves.
|
56
|
+
* The client supports `MOVED` and `ASK` redirections transparently.
|
57
|
+
|
58
|
+
## Cluster mode with SSL/TLS
|
59
|
+
Since Redis can return FQDN of nodes in reply to client since `7.*` with CLUSTER commands, we can use cluster feature with SSL/TLS connection like this:
|
60
|
+
|
61
|
+
```ruby
|
62
|
+
Redis.new(cluster: %w[rediss://foo.example.com:6379])
|
63
|
+
```
|
64
|
+
|
65
|
+
On the other hand, in Redis versions prior to `6.*`, you can specify options like the following if cluster mode is enabled and client has to connect to nodes via single endpoint with SSL/TLS.
|
66
|
+
|
67
|
+
```ruby
|
68
|
+
Redis.new(cluster: %w[rediss://foo-endpoint.example.com:6379], fixed_hostname: 'foo-endpoint.example.com')
|
69
|
+
```
|
70
|
+
|
71
|
+
In case of the above architecture, if you don't pass the `fixed_hostname` option to the client and servers return IP addresses of nodes, the client may fail to verify certificates.
|
@@ -0,0 +1,94 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require 'redis-cluster-client'
|
4
|
+
|
5
|
+
class Redis
|
6
|
+
class Cluster
|
7
|
+
class Client < RedisClient::Cluster
|
8
|
+
ERROR_MAPPING = ::Redis::Client::ERROR_MAPPING.merge(
|
9
|
+
RedisClient::Cluster::InitialSetupError => Redis::Cluster::InitialSetupError,
|
10
|
+
RedisClient::Cluster::OrchestrationCommandNotSupported => Redis::Cluster::OrchestrationCommandNotSupported,
|
11
|
+
RedisClient::Cluster::AmbiguousNodeError => Redis::Cluster::AmbiguousNodeError,
|
12
|
+
RedisClient::Cluster::ErrorCollection => Redis::Cluster::CommandErrorCollection
|
13
|
+
).freeze
|
14
|
+
|
15
|
+
class << self
|
16
|
+
def config(**kwargs)
|
17
|
+
super(protocol: 2, **kwargs)
|
18
|
+
end
|
19
|
+
|
20
|
+
def sentinel(**kwargs)
|
21
|
+
super(protocol: 2, **kwargs)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
def initialize(*)
|
26
|
+
handle_errors { super }
|
27
|
+
@inherit_socket = false
|
28
|
+
@pid = Process.pid
|
29
|
+
end
|
30
|
+
ruby2_keywords :initialize if respond_to?(:ruby2_keywords, true)
|
31
|
+
|
32
|
+
def id
|
33
|
+
@router.node.node_keys.join(' ')
|
34
|
+
end
|
35
|
+
|
36
|
+
def server_url
|
37
|
+
@router.node.node_keys
|
38
|
+
end
|
39
|
+
|
40
|
+
def connected?
|
41
|
+
true
|
42
|
+
end
|
43
|
+
|
44
|
+
def disable_reconnection
|
45
|
+
yield # TODO: do we need this, is it doable?
|
46
|
+
end
|
47
|
+
|
48
|
+
def timeout
|
49
|
+
config.read_timeout
|
50
|
+
end
|
51
|
+
|
52
|
+
def db
|
53
|
+
0
|
54
|
+
end
|
55
|
+
|
56
|
+
undef_method :call
|
57
|
+
undef_method :call_once
|
58
|
+
undef_method :call_once_v
|
59
|
+
undef_method :blocking_call
|
60
|
+
|
61
|
+
def call_v(command, &block)
|
62
|
+
handle_errors { super(command, &block) }
|
63
|
+
end
|
64
|
+
|
65
|
+
def blocking_call_v(timeout, command, &block)
|
66
|
+
timeout += self.timeout if timeout && timeout > 0
|
67
|
+
handle_errors { super(timeout, command, &block) }
|
68
|
+
end
|
69
|
+
|
70
|
+
def pipelined(&block)
|
71
|
+
handle_errors { super(&block) }
|
72
|
+
end
|
73
|
+
|
74
|
+
def multi(&block)
|
75
|
+
handle_errors { super(&block) }
|
76
|
+
end
|
77
|
+
|
78
|
+
private
|
79
|
+
|
80
|
+
def handle_errors
|
81
|
+
yield
|
82
|
+
rescue RedisClient::Cluster::ErrorCollection => error
|
83
|
+
error.errors.each do |_node, node_error|
|
84
|
+
if node_error.is_a?(RedisClient::AuthenticationError)
|
85
|
+
raise ERROR_MAPPING.fetch(node_error.class), node_error.message, node_error.backtrace
|
86
|
+
end
|
87
|
+
end
|
88
|
+
raise ERROR_MAPPING.fetch(error.class), error.message, error.backtrace
|
89
|
+
rescue ::RedisClient::Error => error
|
90
|
+
raise ERROR_MAPPING.fetch(error.class), error.message, error.backtrace
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
@@ -0,0 +1,101 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "redis"
|
4
|
+
|
5
|
+
class Redis
|
6
|
+
class Cluster < ::Redis
|
7
|
+
# Raised when client connected to redis as cluster mode
|
8
|
+
# and failed to fetch cluster state information by commands.
|
9
|
+
class InitialSetupError < BaseError
|
10
|
+
end
|
11
|
+
|
12
|
+
# Raised when client connected to redis as cluster mode
|
13
|
+
# and some cluster subcommands were called.
|
14
|
+
class OrchestrationCommandNotSupported < BaseError
|
15
|
+
def initialize(command, subcommand = '')
|
16
|
+
str = [command, subcommand].map(&:to_s).reject(&:empty?).join(' ').upcase
|
17
|
+
msg = "#{str} command should be used with care "\
|
18
|
+
'only by applications orchestrating Redis Cluster, like redis-trib, '\
|
19
|
+
'and the command if used out of the right context can leave the cluster '\
|
20
|
+
'in a wrong state or cause data loss.'
|
21
|
+
super(msg)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# Raised when error occurs on any node of cluster.
|
26
|
+
class CommandErrorCollection < BaseError
|
27
|
+
attr_reader :errors
|
28
|
+
|
29
|
+
# @param errors [Hash{String => Redis::CommandError}]
|
30
|
+
# @param error_message [String]
|
31
|
+
def initialize(errors, error_message = 'Command errors were replied on any node')
|
32
|
+
@errors = errors
|
33
|
+
super(error_message)
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
# Raised when cluster client can't select node.
|
38
|
+
class AmbiguousNodeError < BaseError
|
39
|
+
end
|
40
|
+
|
41
|
+
def connection
|
42
|
+
raise NotImplementedError, "Redis::Cluster doesn't implement #connection"
|
43
|
+
end
|
44
|
+
|
45
|
+
# Create a new client instance
|
46
|
+
#
|
47
|
+
# @param [Hash] options
|
48
|
+
# @option options [Float] :timeout (5.0) timeout in seconds
|
49
|
+
# @option options [Float] :connect_timeout (same as timeout) timeout for initial connect in seconds
|
50
|
+
# @option options [Symbol] :driver Driver to use, currently supported: `:ruby`, `:hiredis`
|
51
|
+
# @option options [Integer, Array<Integer, Float>] :reconnect_attempts Number of attempts trying to connect,
|
52
|
+
# or a list of sleep duration between attempts.
|
53
|
+
# @option options [Boolean] :inherit_socket (false) Whether to use socket in forked process or not
|
54
|
+
# @option options [Array<String, Hash{Symbol => String, Integer}>] :nodes List of cluster nodes to contact
|
55
|
+
# @option options [Boolean] :replica Whether to use readonly replica nodes in Redis Cluster or not
|
56
|
+
# @option options [String] :fixed_hostname Specify a FQDN if cluster mode enabled and
|
57
|
+
# client has to connect nodes via single endpoint with SSL/TLS
|
58
|
+
# @option options [Class] :connector Class of custom connector
|
59
|
+
#
|
60
|
+
# @return [Redis::Cluster] a new client instance
|
61
|
+
def initialize(*) # rubocop:disable Lint/UselessMethodDefinition
|
62
|
+
super
|
63
|
+
end
|
64
|
+
ruby2_keywords :initialize if respond_to?(:ruby2_keywords, true)
|
65
|
+
|
66
|
+
# Sends `CLUSTER *` command to random node and returns its reply.
|
67
|
+
#
|
68
|
+
# @see https://redis.io/commands#cluster Reference of cluster command
|
69
|
+
#
|
70
|
+
# @param subcommand [String, Symbol] the subcommand of cluster command
|
71
|
+
# e.g. `:slots`, `:nodes`, `:slaves`, `:info`
|
72
|
+
#
|
73
|
+
# @return [Object] depends on the subcommand
|
74
|
+
def cluster(subcommand, *args)
|
75
|
+
subcommand = subcommand.to_s.downcase
|
76
|
+
block = case subcommand
|
77
|
+
when 'slots'
|
78
|
+
HashifyClusterSlots
|
79
|
+
when 'nodes'
|
80
|
+
HashifyClusterNodes
|
81
|
+
when 'slaves'
|
82
|
+
HashifyClusterSlaves
|
83
|
+
when 'info'
|
84
|
+
HashifyInfo
|
85
|
+
else
|
86
|
+
Noop
|
87
|
+
end
|
88
|
+
|
89
|
+
send_command([:cluster, subcommand] + args, &block)
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def initialize_client(options)
|
95
|
+
cluster_config = RedisClient.cluster(**options, protocol: 2, client_implementation: ::Redis::Cluster::Client)
|
96
|
+
cluster_config.new_client
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
|
101
|
+
require "redis/cluster/client"
|
metadata
ADDED
@@ -0,0 +1,79 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: redis-clustering
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 5.0.0.beta3
|
5
|
+
platform: ruby
|
6
|
+
authors:
|
7
|
+
- Ezra Zygmuntowicz
|
8
|
+
- Taylor Weibley
|
9
|
+
- Matthew Clark
|
10
|
+
- Brian McKinney
|
11
|
+
- Salvatore Sanfilippo
|
12
|
+
- Luca Guidi
|
13
|
+
- Michel Martens
|
14
|
+
- Damian Janowski
|
15
|
+
- Pieter Noordhuis
|
16
|
+
autorequire:
|
17
|
+
bindir: bin
|
18
|
+
cert_chain: []
|
19
|
+
date: 2022-08-18 00:00:00.000000000 Z
|
20
|
+
dependencies:
|
21
|
+
- !ruby/object:Gem::Dependency
|
22
|
+
name: redis-cluster-client
|
23
|
+
requirement: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - "~>"
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: '0.2'
|
28
|
+
type: :runtime
|
29
|
+
prerelease: false
|
30
|
+
version_requirements: !ruby/object:Gem::Requirement
|
31
|
+
requirements:
|
32
|
+
- - "~>"
|
33
|
+
- !ruby/object:Gem::Version
|
34
|
+
version: '0.2'
|
35
|
+
description: |2
|
36
|
+
A Ruby client that tries to match Redis' Cluster API one-to-one, while still
|
37
|
+
providing an idiomatic interface.
|
38
|
+
email:
|
39
|
+
- redis-db@googlegroups.com
|
40
|
+
executables: []
|
41
|
+
extensions: []
|
42
|
+
extra_rdoc_files: []
|
43
|
+
files:
|
44
|
+
- CHANGELOG.md
|
45
|
+
- LICENSE
|
46
|
+
- README.md
|
47
|
+
- lib/redis-clustering.rb
|
48
|
+
- lib/redis/cluster.rb
|
49
|
+
- lib/redis/cluster/client.rb
|
50
|
+
- lib/redis/cluster/version.rb
|
51
|
+
homepage: https://github.com/redis/redis-rb/blob/master/cluster
|
52
|
+
licenses:
|
53
|
+
- MIT
|
54
|
+
metadata:
|
55
|
+
bug_tracker_uri: https://github.com/redis/redis-rb/issues
|
56
|
+
changelog_uri: https://github.com/redis/redis-rb/blob/master/cluster/CHANGELOG.md
|
57
|
+
documentation_uri: https://www.rubydoc.info/gems/redis/5.0.0.beta3
|
58
|
+
homepage_uri: https://github.com/redis/redis-rb/blob/master/cluster
|
59
|
+
source_code_uri: https://github.com/redis/redis-rb/tree/v5.0.0.beta3/cluster
|
60
|
+
post_install_message:
|
61
|
+
rdoc_options: []
|
62
|
+
require_paths:
|
63
|
+
- lib
|
64
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
65
|
+
requirements:
|
66
|
+
- - ">="
|
67
|
+
- !ruby/object:Gem::Version
|
68
|
+
version: 2.7.0
|
69
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
70
|
+
requirements:
|
71
|
+
- - ">"
|
72
|
+
- !ruby/object:Gem::Version
|
73
|
+
version: 1.3.1
|
74
|
+
requirements: []
|
75
|
+
rubygems_version: 3.1.2
|
76
|
+
signing_key:
|
77
|
+
specification_version: 4
|
78
|
+
summary: A Ruby client library for Redis Cluster
|
79
|
+
test_files: []
|