redis_failover 0.8.3 → 0.8.4
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.
- data/.gitignore +2 -0
- data/Changes.md +6 -0
- data/README.md +4 -0
- data/examples/multiple_environments_config.yml +15 -0
- data/lib/redis_failover/cli.rb +16 -4
- data/lib/redis_failover/client.rb +19 -16
- data/lib/redis_failover/errors.rb +1 -1
- data/lib/redis_failover/{manual.rb → manual_failover.rb} +17 -13
- data/lib/redis_failover/node_manager.rb +9 -9
- data/lib/redis_failover/util.rb +1 -1
- data/lib/redis_failover/version.rb +1 -1
- data/lib/redis_failover.rb +2 -1
- data/redis_failover.gemspec +1 -1
- data/spec/cli_spec.rb +11 -0
- data/spec/support/config/multiple_environments.yml +15 -0
- data/spec/support/config/single_environment.yml +7 -0
- metadata +14 -9
data/.gitignore
CHANGED
data/Changes.md
CHANGED
@@ -1,3 +1,9 @@
|
|
1
|
+
0.8.4
|
2
|
+
-----------
|
3
|
+
- Lock-down gemspec to redis 2.2.x in light of upcoming redis 3.x release. Once sufficient testing
|
4
|
+
has been done with the 3.x release, I will relax the constraint in the gemspec.
|
5
|
+
- Add environment-scoped configuration file support (oleriesenberg)
|
6
|
+
|
1
7
|
0.8.3
|
2
8
|
-----------
|
3
9
|
- Added a way to gracefully shutdown/reconnect a RedisFailover::Client. (#13)
|
data/README.md
CHANGED
@@ -67,6 +67,7 @@ following options:
|
|
67
67
|
--znode-path PATH Znode path override for storing redis server list
|
68
68
|
--max-failures COUNT Max failures before manager marks node unavailable
|
69
69
|
-C, --config PATH Path to YAML configuration file
|
70
|
+
-E, --environment ENV Config environment to use
|
70
71
|
-h, --help Display all options
|
71
72
|
|
72
73
|
To start the daemon for a simple master/slave configuration, use the following:
|
@@ -93,6 +94,9 @@ You would then simpy start the Node Manager via the following:
|
|
93
94
|
|
94
95
|
redis_node_manager -C config.yml
|
95
96
|
|
97
|
+
You can also scope the configuration to a particular environment (e.g., staging/development). See the examples
|
98
|
+
directory for configuration file samples.
|
99
|
+
|
96
100
|
The Node Manager will automatically discover the master/slaves upon startup. Note that it is
|
97
101
|
a good idea to run more than one instance of the Node Manager daemon in your environment. At
|
98
102
|
any moment, a single Node Manager process will be designated to monitor the redis servers. If
|
data/lib/redis_failover/cli.rb
CHANGED
@@ -36,10 +36,14 @@ module RedisFailover
|
|
36
36
|
options[:max_failures] = Integer(max)
|
37
37
|
end
|
38
38
|
|
39
|
-
opts.on
|
39
|
+
opts.on('-C', '--config PATH', 'Path to YAML config file') do |file|
|
40
40
|
options[:config_file] = file
|
41
41
|
end
|
42
42
|
|
43
|
+
opts.on('-E', '--environment ENV', 'Config environment to use') do |config_env|
|
44
|
+
options[:config_environment] = config_env
|
45
|
+
end
|
46
|
+
|
43
47
|
opts.on('-h', '--help', 'Display all options') do
|
44
48
|
puts opts
|
45
49
|
exit
|
@@ -48,7 +52,7 @@ module RedisFailover
|
|
48
52
|
|
49
53
|
parser.parse(source)
|
50
54
|
if config_file = options[:config_file]
|
51
|
-
options = from_file(config_file)
|
55
|
+
options = from_file(config_file, options[:config_environment])
|
52
56
|
end
|
53
57
|
|
54
58
|
if required_options_missing?(options)
|
@@ -69,12 +73,20 @@ module RedisFailover
|
|
69
73
|
# Parses options from a YAML file.
|
70
74
|
#
|
71
75
|
# @param [String] file the filename
|
76
|
+
# @params [String] env the environment
|
72
77
|
# @return [Hash] the parsed options
|
73
|
-
def self.from_file(file)
|
78
|
+
def self.from_file(file, env = nil)
|
74
79
|
unless File.exists?(file)
|
75
80
|
raise ArgumentError, "File #{file} can't be found"
|
76
81
|
end
|
77
82
|
options = YAML.load_file(file)
|
83
|
+
|
84
|
+
if env
|
85
|
+
options = options.fetch(env.to_sym) do
|
86
|
+
raise ArgumentError, "Environment #{env} can't be found in config"
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
78
90
|
options[:nodes] = options[:nodes].join(',')
|
79
91
|
options[:zkservers] = options[:zkservers].join(',')
|
80
92
|
|
@@ -100,4 +112,4 @@ module RedisFailover
|
|
100
112
|
options
|
101
113
|
end
|
102
114
|
end
|
103
|
-
end
|
115
|
+
end
|
@@ -1,15 +1,18 @@
|
|
1
1
|
module RedisFailover
|
2
|
-
# Redis failover-aware client. RedisFailover::Client is a wrapper over a set
|
3
|
-
# clients, which means all normal redis operations can be
|
4
|
-
# The class only requires a set of
|
5
|
-
#
|
6
|
-
#
|
7
|
-
#
|
8
|
-
#
|
9
|
-
# and
|
2
|
+
# Redis failover-aware client. RedisFailover::Client is a wrapper over a set
|
3
|
+
# of underlying redis clients, which means all normal redis operations can be
|
4
|
+
# performed on an instance of this class. The class only requires a set of
|
5
|
+
# ZooKeeper server addresses to function properly. The client will automatically
|
6
|
+
# retry failed operations, and handle failover to a new master. The client
|
7
|
+
# registers and listens for watcher events from the Node Manager. When these
|
8
|
+
# events are received, the client fetches the latest set of redis nodes from
|
9
|
+
# ZooKeeper and rebuilds its internal Redis clients appropriately.
|
10
|
+
# RedisFailover::Client also directs write operations to the master, and all
|
11
|
+
# read operations to the slaves.
|
10
12
|
#
|
11
13
|
# @example Usage
|
12
|
-
#
|
14
|
+
# zk_servers = 'localhost:2181,localhost:2182,localhost:2183'
|
15
|
+
# client = RedisFailover::Client.new(:zkservers => zk_servers)
|
13
16
|
# client.set('foo', 1) # will be directed to master
|
14
17
|
# client.get('foo') # will be directed to a slave
|
15
18
|
#
|
@@ -86,13 +89,13 @@ module RedisFailover
|
|
86
89
|
# Creates a new failover redis client.
|
87
90
|
#
|
88
91
|
# @param [Hash] options the options used to initialize the client instance
|
89
|
-
# @option options [String] :zkservers comma-separated ZooKeeper host:port
|
90
|
-
# @option options [String] :znode_path znode path override for redis
|
92
|
+
# @option options [String] :zkservers comma-separated ZooKeeper host:port
|
93
|
+
# @option options [String] :znode_path znode path override for redis nodes
|
91
94
|
# @option options [String] :password password for redis nodes
|
92
95
|
# @option options [String] :db database to use for redis nodes
|
93
96
|
# @option options [String] :namespace namespace for redis nodes
|
94
97
|
# @option options [Logger] :logger logger override
|
95
|
-
# @option options [Boolean] :retry_failure indicates if failures
|
98
|
+
# @option options [Boolean] :retry_failure indicates if failures are retried
|
96
99
|
# @option options [Integer] :max_retries max retries for a failure
|
97
100
|
# @return [RedisFailover::Client]
|
98
101
|
def initialize(options = {})
|
@@ -124,7 +127,7 @@ module RedisFailover
|
|
124
127
|
# Determines whether or not an unknown method can be handled.
|
125
128
|
#
|
126
129
|
# @param [Symbol] method the method to check
|
127
|
-
# @param [Boolean] include_private determines if private methods
|
130
|
+
# @param [Boolean] include_private determines if private methods are checked
|
128
131
|
# @return [Boolean] indicates if the method can be handled
|
129
132
|
def respond_to_missing?(method, include_private)
|
130
133
|
redis_operation?(method) || super
|
@@ -144,7 +147,7 @@ module RedisFailover
|
|
144
147
|
# @option options [String] :host the host of the failover candidate
|
145
148
|
# @option options [String] :port the port of the failover candidate
|
146
149
|
def manual_failover(options = {})
|
147
|
-
|
150
|
+
ManualFailover.new(zk, options).perform
|
148
151
|
self
|
149
152
|
end
|
150
153
|
|
@@ -295,7 +298,7 @@ module RedisFailover
|
|
295
298
|
|
296
299
|
nodes
|
297
300
|
rescue Zookeeper::Exceptions::InheritedConnectionError => ex
|
298
|
-
logger.debug { "Caught #{ex.class} '#{ex.message}'
|
301
|
+
logger.debug { "Caught #{ex.class} '#{ex.message}' - reopening ZK client" }
|
299
302
|
@zk.reopen
|
300
303
|
retry
|
301
304
|
end
|
@@ -395,7 +398,7 @@ module RedisFailover
|
|
395
398
|
end
|
396
399
|
end
|
397
400
|
|
398
|
-
# Disconnects current redis clients
|
401
|
+
# Disconnects current redis clients.
|
399
402
|
def purge_clients
|
400
403
|
@lock.synchronize do
|
401
404
|
logger.info("Purging current redis clients")
|
@@ -1,34 +1,38 @@
|
|
1
1
|
module RedisFailover
|
2
2
|
# Provides manual failover support to a new master.
|
3
|
-
|
4
|
-
extend self
|
5
|
-
|
3
|
+
class ManualFailover
|
6
4
|
# Path for manual failover communication.
|
7
5
|
ZNODE_PATH = '/redis_failover_manual'.freeze
|
8
6
|
|
9
7
|
# Denotes that any slave can be used as a candidate for promotion.
|
10
8
|
ANY_SLAVE = "ANY_SLAVE".freeze
|
11
9
|
|
12
|
-
#
|
13
|
-
# be used as a failover candidate.
|
10
|
+
# Creates a new instance.
|
14
11
|
#
|
15
12
|
# @param [ZK] zk the ZooKeeper client
|
16
13
|
# @param [Hash] options the options used for manual failover
|
17
14
|
# @option options [String] :host the host of the failover candidate
|
18
15
|
# @option options [String] :port the port of the failover candidate
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
16
|
+
# @note
|
17
|
+
# If options is empty, a random slave will be used
|
18
|
+
# as a failover candidate.
|
19
|
+
def initialize(zk, options = {})
|
20
|
+
@zk = zk
|
21
|
+
@options = options
|
22
|
+
end
|
23
|
+
|
24
|
+
# Performs a manual failover.
|
25
|
+
def perform
|
26
|
+
create_path
|
27
|
+
node = @options.empty? ? ANY_SLAVE : "#{@options[:host]}:#{@options[:port]}"
|
28
|
+
@zk.set(ZNODE_PATH, node)
|
23
29
|
end
|
24
30
|
|
25
31
|
private
|
26
32
|
|
27
33
|
# Creates the znode path used for coordinating manual failovers.
|
28
|
-
|
29
|
-
|
30
|
-
def create_path(zk)
|
31
|
-
zk.create(ZNODE_PATH)
|
34
|
+
def create_path
|
35
|
+
@zk.create(ZNODE_PATH)
|
32
36
|
rescue ZK::Exceptions::NodeExists
|
33
37
|
# best effort
|
34
38
|
end
|
@@ -23,22 +23,22 @@ module RedisFailover
|
|
23
23
|
# Creates a new instance.
|
24
24
|
#
|
25
25
|
# @param [Hash] options the options used to initialize the manager
|
26
|
-
# @option options [String] :zkservers comma-separated
|
27
|
-
# @option options [String] :znode_path znode path override for redis
|
26
|
+
# @option options [String] :zkservers comma-separated ZK host:port pairs
|
27
|
+
# @option options [String] :znode_path znode path override for redis nodes
|
28
28
|
# @option options [String] :password password for redis nodes
|
29
29
|
# @option options [Array<String>] :nodes the nodes to manage
|
30
|
-
# @option options [String] :max_failures the max failures for a
|
30
|
+
# @option options [String] :max_failures the max failures for a node
|
31
31
|
def initialize(options)
|
32
32
|
logger.info("Redis Node Manager v#{VERSION} starting (#{RUBY_DESCRIPTION})")
|
33
33
|
@options = options
|
34
34
|
@znode = @options[:znode_path] || Util::DEFAULT_ZNODE_PATH
|
35
|
-
@manual_znode =
|
35
|
+
@manual_znode = ManualFailover::ZNODE_PATH
|
36
36
|
@mutex = Mutex.new
|
37
37
|
end
|
38
38
|
|
39
39
|
# Starts the node manager.
|
40
40
|
#
|
41
|
-
# @note This
|
41
|
+
# @note This method does not return until the manager terminates.
|
42
42
|
def start
|
43
43
|
@queue = Queue.new
|
44
44
|
@leader = false
|
@@ -60,8 +60,8 @@ module RedisFailover
|
|
60
60
|
retry
|
61
61
|
end
|
62
62
|
|
63
|
-
# Notifies the manager of a state change. Used primarily by
|
64
|
-
# to inform the manager of watched node states.
|
63
|
+
# Notifies the manager of a state change. Used primarily by
|
64
|
+
# {RedisFailover::NodeWatcher} to inform the manager of watched node states.
|
65
65
|
#
|
66
66
|
# @param [Node] node the node
|
67
67
|
# @param [Symbol] state the state
|
@@ -202,7 +202,7 @@ module RedisFailover
|
|
202
202
|
# make a specific node or slave the new master
|
203
203
|
candidate = node || @slaves.pop
|
204
204
|
unless candidate
|
205
|
-
logger.error('Failed to promote a new master
|
205
|
+
logger.error('Failed to promote a new master, no candidate available.')
|
206
206
|
return
|
207
207
|
end
|
208
208
|
|
@@ -338,7 +338,7 @@ module RedisFailover
|
|
338
338
|
new_master = @zk.get(@manual_znode, :watch => true).first
|
339
339
|
logger.info("Received manual failover request for: #{new_master}")
|
340
340
|
|
341
|
-
node = if new_master ==
|
341
|
+
node = if new_master == ManualFailover::ANY_SLAVE
|
342
342
|
@slaves.sample
|
343
343
|
else
|
344
344
|
host, port = new_master.split(':', 2)
|
data/lib/redis_failover/util.rb
CHANGED
@@ -5,7 +5,7 @@ module RedisFailover
|
|
5
5
|
module Util
|
6
6
|
extend self
|
7
7
|
|
8
|
-
# Default node in
|
8
|
+
# Default node in ZK that contains the current list of available redis nodes.
|
9
9
|
DEFAULT_ZNODE_PATH = '/redis_failover_nodes'.freeze
|
10
10
|
|
11
11
|
# Connectivity errors that the redis client raises.
|
data/lib/redis_failover.rb
CHANGED
@@ -15,7 +15,8 @@ require 'redis_failover/node'
|
|
15
15
|
require 'redis_failover/errors'
|
16
16
|
require 'redis_failover/client'
|
17
17
|
require 'redis_failover/runner'
|
18
|
-
require 'redis_failover/manual'
|
19
18
|
require 'redis_failover/version'
|
20
19
|
require 'redis_failover/node_manager'
|
21
20
|
require 'redis_failover/node_watcher'
|
21
|
+
require 'redis_failover/manual_failover'
|
22
|
+
|
data/redis_failover.gemspec
CHANGED
@@ -15,7 +15,7 @@ Gem::Specification.new do |gem|
|
|
15
15
|
gem.require_paths = ["lib"]
|
16
16
|
gem.version = RedisFailover::VERSION
|
17
17
|
|
18
|
-
gem.add_dependency('redis')
|
18
|
+
gem.add_dependency('redis', '~> 2.2')
|
19
19
|
gem.add_dependency('redis-namespace')
|
20
20
|
gem.add_dependency('multi_json', '~> 1')
|
21
21
|
gem.add_dependency('zk', '~> 1.4')
|
data/spec/cli_spec.rb
CHANGED
@@ -35,6 +35,17 @@ module RedisFailover
|
|
35
35
|
'1'])
|
36
36
|
opts[:max_failures].should == 1
|
37
37
|
end
|
38
|
+
|
39
|
+
it 'properly parses the config file' do
|
40
|
+
opts = CLI.parse(['-C', "#{File.dirname(__FILE__)}/support/config/single_environment.yml"])
|
41
|
+
opts[:zkservers].should == 'zk01:2181,zk02:2181,zk03:2181'
|
42
|
+
|
43
|
+
opts = CLI.parse(['-C', "#{File.dirname(__FILE__)}/support/config/multiple_environments.yml", '-E', 'development'])
|
44
|
+
opts[:zkservers].should == 'localhost:2181'
|
45
|
+
|
46
|
+
opts = CLI.parse(['-C', "#{File.dirname(__FILE__)}/support/config/multiple_environments.yml", '-E', 'staging'])
|
47
|
+
opts[:zkservers].should == 'zk01:2181,zk02:2181,zk03:2181'
|
48
|
+
end
|
38
49
|
end
|
39
50
|
end
|
40
51
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redis_failover
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.8.
|
4
|
+
version: 0.8.4
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,24 +9,24 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-06-06 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: redis
|
16
16
|
requirement: !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
|
-
- -
|
19
|
+
- - ~>
|
20
20
|
- !ruby/object:Gem::Version
|
21
|
-
version: '
|
21
|
+
version: '2.2'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
24
|
version_requirements: !ruby/object:Gem::Requirement
|
25
25
|
none: false
|
26
26
|
requirements:
|
27
|
-
- -
|
27
|
+
- - ~>
|
28
28
|
- !ruby/object:Gem::Version
|
29
|
-
version: '
|
29
|
+
version: '2.2'
|
30
30
|
- !ruby/object:Gem::Dependency
|
31
31
|
name: redis-namespace
|
32
32
|
requirement: !ruby/object:Gem::Requirement
|
@@ -142,11 +142,12 @@ files:
|
|
142
142
|
- Rakefile
|
143
143
|
- bin/redis_node_manager
|
144
144
|
- examples/config.yml
|
145
|
+
- examples/multiple_environments_config.yml
|
145
146
|
- lib/redis_failover.rb
|
146
147
|
- lib/redis_failover/cli.rb
|
147
148
|
- lib/redis_failover/client.rb
|
148
149
|
- lib/redis_failover/errors.rb
|
149
|
-
- lib/redis_failover/
|
150
|
+
- lib/redis_failover/manual_failover.rb
|
150
151
|
- lib/redis_failover/node.rb
|
151
152
|
- lib/redis_failover/node_manager.rb
|
152
153
|
- lib/redis_failover/node_watcher.rb
|
@@ -160,6 +161,8 @@ files:
|
|
160
161
|
- spec/node_spec.rb
|
161
162
|
- spec/node_watcher_spec.rb
|
162
163
|
- spec/spec_helper.rb
|
164
|
+
- spec/support/config/multiple_environments.yml
|
165
|
+
- spec/support/config/single_environment.yml
|
163
166
|
- spec/support/node_manager_stub.rb
|
164
167
|
- spec/support/redis_stub.rb
|
165
168
|
- spec/util_spec.rb
|
@@ -177,7 +180,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
177
180
|
version: '0'
|
178
181
|
segments:
|
179
182
|
- 0
|
180
|
-
hash:
|
183
|
+
hash: 2968723262927828498
|
181
184
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
182
185
|
none: false
|
183
186
|
requirements:
|
@@ -186,7 +189,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
186
189
|
version: '0'
|
187
190
|
segments:
|
188
191
|
- 0
|
189
|
-
hash:
|
192
|
+
hash: 2968723262927828498
|
190
193
|
requirements: []
|
191
194
|
rubyforge_project:
|
192
195
|
rubygems_version: 1.8.23
|
@@ -201,6 +204,8 @@ test_files:
|
|
201
204
|
- spec/node_spec.rb
|
202
205
|
- spec/node_watcher_spec.rb
|
203
206
|
- spec/spec_helper.rb
|
207
|
+
- spec/support/config/multiple_environments.yml
|
208
|
+
- spec/support/config/single_environment.yml
|
204
209
|
- spec/support/node_manager_stub.rb
|
205
210
|
- spec/support/redis_stub.rb
|
206
211
|
- spec/util_spec.rb
|