Skip to content

Commit de132ff

Browse files
committed
Split cluster support in a separate gem
1 parent 93e5dc8 commit de132ff

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

45 files changed

+575
-402
lines changed

.github/workflows/test.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -197,7 +197,7 @@ jobs:
197197
LOW_TIMEOUT: "0.14"
198198
DRIVER: ruby
199199
REDIS_BRANCH: "7.0"
200-
REDIS_CLUSTER: "true"
200+
BUNDLE_GEMFILE: redis_cluster/Gemfile
201201
steps:
202202
- name: Check out code
203203
uses: actions/checkout@v3

.rubocop.yml

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,3 +148,7 @@ Style/SymbolProc:
148148

149149
Bundler/OrderedGems:
150150
Enabled: false
151+
152+
Gemspec/RequiredRubyVersion:
153+
Exclude:
154+
- redis_cluster/redis_cluster.gemspec

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22

33
# Unreleased 5.0.0
44

5+
- Cluster support has been moved to a `redis_cluster` companion gem.
56
- `select` no longer record the current database. If the client has to reconnect after `select` was used, it will reconnect to the original database.
67
- Removed `logger` option.
78
- Removed `reconnect_delay_max` and `reconnect_delay`, you can pass precise sleep durations to `reconnect_attempts` instead.

Gemfile

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,3 @@ gem 'mocha'
1111

1212
gem 'redis-client', github: 'redis-rb/redis-client'
1313
gem 'hiredis-client'
14-
gem 'redis-cluster-client', github: 'redis-rb/redis-cluster-client' if ENV['REDIS_CLUSTER']

README.md

Lines changed: 1 addition & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -116,59 +116,7 @@ redis = Redis.new(name: 'mymaster', sentinels: SENTINELS, role: :master)
116116

117117
## Cluster support
118118

119-
`redis-rb` supports [clustering](https://redis.io/topics/cluster-spec).
120-
121-
```ruby
122-
# Nodes can be passed to the client as an array of connection URLs.
123-
nodes = (7000..7005).map { |port| "redis://127.0.0.1:#{port}" }
124-
redis = Redis.new(cluster: nodes)
125-
126-
# You can also specify the options as a Hash. The options are the same as for a single server connection.
127-
(7000..7005).map { |port| { host: '127.0.0.1', port: port } }
128-
```
129-
130-
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.
131-
132-
```ruby
133-
Redis.new(cluster: %w[redis://127.0.0.1:7000])
134-
```
135-
136-
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.
137-
138-
```ruby
139-
Redis.new(cluster: nodes, replica: true)
140-
```
141-
142-
The calling code is responsible for [avoiding cross slot commands](https://redis.io/topics/cluster-spec#keys-distribution-model).
143-
144-
```ruby
145-
redis = Redis.new(cluster: %w[redis://127.0.0.1:7000])
146-
147-
redis.mget('key1', 'key2')
148-
#=> Redis::CommandError (CROSSSLOT Keys in request don't hash to the same slot)
149-
150-
redis.mget('{key}1', '{key}2')
151-
#=> [nil, nil]
152-
```
153-
154-
* The client automatically reconnects after a failover occurred, but the caller is responsible for handling errors while it is happening.
155-
* The client support permanent node failures, and will reroute requests to promoted slaves.
156-
* The client supports `MOVED` and `ASK` redirections transparently.
157-
158-
## Cluster mode with SSL/TLS
159-
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:
160-
161-
```ruby
162-
Redis.new(cluster: %w[rediss://foo.example.com:6379])
163-
```
164-
165-
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.
166-
167-
```ruby
168-
Redis.new(cluster: %w[rediss://foo-endpoint.example.com:6379], fixed_hostname: 'foo-endpoint.example.com')
169-
```
170-
171-
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.
119+
[Clustering](https://redis.io/topics/cluster-spec). is supported via the [`redis_cluster` gem](redis_cluster/).
172120

173121
## Storing objects
174122

Rakefile

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
# frozen_string_literal: true
22

33
require 'bundler/gem_tasks'
4+
Bundler::GemHelper.install_tasks(dir: "redis_cluster", name: "redis_cluster")
5+
46
require 'rake/testtask'
57

68
namespace :test do
7-
groups = %i(redis distributed sentinel cluster)
9+
groups = %i(redis distributed sentinel)
810
groups.each do |group|
911
Rake::TestTask.new(group) do |t|
1012
t.libs << "test"
@@ -18,6 +20,13 @@ namespace :test do
1820
unless lost_tests.empty?
1921
abort "The following test files are in no group:\n#{lost_tests.join("\n")}"
2022
end
23+
24+
Rake::TestTask.new(:cluster) do |t|
25+
t.libs << "redis_cluster/test" << "test"
26+
t.libs << "redis_cluster/lib" << "lib"
27+
t.test_files = FileList["redis_cluster/test/**/*_test.rb"]
28+
t.options = '-v' if ENV['CI'] || ENV['VERBOSE']
29+
end
2130
end
2231

2332
task test: ["test:redis", "test:distributed", "test:sentinel", "test:cluster"]

bin/cluster_creator

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ puts ARGV.join(" ")
55
require 'bundler/setup'
66

77
$LOAD_PATH.unshift(File.expand_path('../lib', __dir__))
8-
require_relative '../test/support/cluster/orchestrator'
8+
require_relative '../redis_cluster/test/support/orchestrator'
99

1010
urls = ARGV.map { |host_port| "redis://#{host_port}" }
1111
orchestrator = ClusterOrchestrator.new(urls, timeout: 3.0)

lib/redis.rb

Lines changed: 28 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ class Redis
88
BASE_PATH = __dir__
99
Deprecated = Class.new(StandardError)
1010

11-
autoload :ClusterClient, "redis/cluster_client"
12-
1311
class << self
1412
attr_accessor :silence_deprecations, :raise_deprecations
1513

@@ -50,12 +48,6 @@ def deprecate!(message)
5048
# @option options [Boolean] :inherit_socket (false) Whether to use socket in forked process or not
5149
# @option options [String] :name The name of the server group to connect to.
5250
# @option options [Array] :sentinels List of sentinels to contact
53-
# @option options [Symbol] :role (:master) Role to fetch via Sentinel, either `:master` or `:slave`
54-
# @option options [Array<String, Hash{Symbol => String, Integer}>] :cluster 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
5951
#
6052
# @return [Redis] a new client instance
6153
def initialize(options = {})
@@ -68,34 +60,7 @@ def initialize(options = {})
6860
inherit_socket = @options.delete(:inherit_socket)
6961
@subscription_client = nil
7062

71-
@client = if @cluster_mode = options.key?(:cluster)
72-
@options[:nodes] ||= @options.delete(:cluster)
73-
cluster_config = RedisClient.cluster(**@options, protocol: 2, client_implementation: ClusterClient)
74-
begin
75-
cluster_config.new_client
76-
rescue ::RedisClient::Error => error
77-
raise ClusterClient::ERROR_MAPPING.fetch(error.class), error.message, error.backtrace
78-
end
79-
elsif @options.key?(:sentinels)
80-
if url = @options.delete(:url)
81-
uri = URI.parse(url)
82-
if !@options.key?(:name) && uri.host
83-
@options[:name] = uri.host
84-
end
85-
86-
if !@options.key?(:password) && uri.password && !uri.password.empty?
87-
@options[:password] = uri.password
88-
end
89-
90-
if !@options.key?(:username) && uri.user && !uri.user.empty?
91-
@options[:username] = uri.user
92-
end
93-
end
94-
95-
Client.sentinel(**@options).new_client
96-
else
97-
Client.config(**@options).new_client
98-
end
63+
@client = initialize_client(@options)
9964
@client.inherit_socket! if inherit_socket
10065
end
10166

@@ -145,10 +110,6 @@ def dup
145110
end
146111

147112
def connection
148-
if @cluster_mode
149-
raise NotImplementedError, "Redis::Cluster doesn't implement #connection"
150-
end
151-
152113
{
153114
host: @client.host,
154115
port: @client.port,
@@ -160,6 +121,33 @@ def connection
160121

161122
private
162123

124+
def initialize_client(options)
125+
if options.key?(:cluster)
126+
raise "Redis Cluster support was moved to the `redis_cluster` gem."
127+
end
128+
129+
if options.key?(:sentinels)
130+
if url = options.delete(:url)
131+
uri = URI.parse(url)
132+
if !options.key?(:name) && uri.host
133+
options[:name] = uri.host
134+
end
135+
136+
if !options.key?(:password) && uri.password && !uri.password.empty?
137+
options[:password] = uri.password
138+
end
139+
140+
if !options.key?(:username) && uri.user && !uri.user.empty?
141+
options[:username] = uri.user
142+
end
143+
end
144+
145+
Client.sentinel(**options).new_client
146+
else
147+
Client.config(**options).new_client
148+
end
149+
end
150+
163151
def synchronize
164152
@monitor.synchronize { yield(@client) }
165153
end

lib/redis/cluster_client.rb

Lines changed: 0 additions & 92 deletions
This file was deleted.

lib/redis/commands/cluster.rb

Lines changed: 1 addition & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -12,24 +12,7 @@ module Cluster
1212
#
1313
# @return [Object] depends on the subcommand
1414
def cluster(subcommand, *args)
15-
subcommand = subcommand.to_s.downcase
16-
block = case subcommand
17-
when 'slots'
18-
HashifyClusterSlots
19-
when 'nodes'
20-
HashifyClusterNodes
21-
when 'slaves'
22-
HashifyClusterSlaves
23-
when 'info'
24-
HashifyInfo
25-
else
26-
Noop
27-
end
28-
29-
# @see https://github.com/antirez/redis/blob/unstable/src/redis-trib.rb#L127 raw reply expected
30-
block = Noop unless @cluster_mode
31-
32-
send_command([:cluster, subcommand] + args, &block)
15+
send_command([:cluster, subcommand] + args)
3316
end
3417

3518
# Sends `ASKING` command to random node and returns its reply.

0 commit comments

Comments
 (0)