redis_failover 0.8.9 → 0.9.0
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/Changes.md +11 -0
- data/README.md +12 -0
- data/lib/redis_failover/client.rb +80 -56
- data/lib/redis_failover/errors.rb +4 -0
- data/lib/redis_failover/node_manager.rb +20 -16
- data/lib/redis_failover/runner.rb +3 -2
- data/lib/redis_failover/util.rb +47 -1
- data/lib/redis_failover/version.rb +1 -1
- data/redis_failover.gemspec +3 -3
- data/spec/client_spec.rb +37 -12
- metadata +16 -10
data/Changes.md
CHANGED
@@ -1,3 +1,14 @@
|
|
1
|
+
0.9.0
|
2
|
+
-----------
|
3
|
+
- Make Node Manager's lock path vary with its main znode. (Bira)
|
4
|
+
- Node Manager's znode for holding current list of redis nodes is no longer ephemeral. This is unnecessary since the current master should only be changed by redis_failover.
|
5
|
+
- Introduce :master_only option for RedisFailover::Client (disabled by default). This option configures the client to direct all read/write operations to the master.
|
6
|
+
- Introduce :safe_mode option (enabled by default). This option configures the client to purge its redis clients when a ZK session expires or when the client hasn't recently heard from the node manager.
|
7
|
+
- Introduce RedisFailover::Client#on_node_change callback notification for when the currently known list of master/slave redis nodes changes.
|
8
|
+
- Added #current_master and #current_slaves to RedisFailover::Client. This is useful for programmatically doing things based on the current master/slaves.
|
9
|
+
- redis_node_manager should start if no redis servers are available (#29)
|
10
|
+
- Better handling of ZK session expirations in Node Manager.
|
11
|
+
|
1
12
|
0.8.9
|
2
13
|
-----------
|
3
14
|
- Handle errors raised by redis 3.x client (tsilen)
|
data/README.md
CHANGED
@@ -129,6 +129,16 @@ The full set of options that can be passed to RedisFailover::Client are:
|
|
129
129
|
:logger - logger override (optional)
|
130
130
|
:retry_failure - indicate if failures should be retried (default true)
|
131
131
|
:max_retries - max retries for a failure (default 3)
|
132
|
+
:safe_mode - indicates if safe mode is used or not (default true)
|
133
|
+
:master_only - indicates if only redis master is used (default false)
|
134
|
+
|
135
|
+
The RedisFailover::Client also supports a custom callback that will be invoked whenever the list of redis clients changes. Example usage:
|
136
|
+
|
137
|
+
RedisFailover::Client.new(:zkservers => 'localhost:2181,localhost:2182,localhost:2183') do |client|
|
138
|
+
client.on_node_change do |master, slaves|
|
139
|
+
logger.info("Nodes changed! master: #{master}, slaves: #{slaves}")
|
140
|
+
end
|
141
|
+
end
|
132
142
|
|
133
143
|
## Manual Failover
|
134
144
|
|
@@ -160,11 +170,13 @@ redis_failover uses YARD for its API documentation. Refer to the generated [API
|
|
160
170
|
|
161
171
|
## Resources
|
162
172
|
|
173
|
+
- Check out Steve Whittaker's [redis-failover-test](https://github.com/swhitt/redis-failover-test) project which shows how to test redis_failover in a non-trivial configuration using Vagrant/Chef.
|
163
174
|
- To learn more about Redis master/slave replication, see the [Redis documentation](http://redis.io/topics/replication).
|
164
175
|
- To learn more about ZooKeeper, see the official [ZooKeeper](http://zookeeper.apache.org/) site.
|
165
176
|
- See the [Quick ZooKeeper Guide](https://github.com/ryanlecompte/redis_failover/wiki/Quick-ZooKeeper-Guide) for a quick guide to getting ZooKeeper up and running with redis_failover.
|
166
177
|
- To learn more about how ZooKeeper handles network partitions, see [ZooKeeper Failure Scenarios](http://wiki.apache.org/hadoop/ZooKeeper/FailureScenarios)
|
167
178
|
|
179
|
+
|
168
180
|
## License
|
169
181
|
|
170
182
|
Please see LICENSE for licensing details.
|
@@ -27,52 +27,6 @@ module RedisFailover
|
|
27
27
|
# Amount of time to sleep before retrying a failed operation.
|
28
28
|
RETRY_WAIT_TIME = 3
|
29
29
|
|
30
|
-
# Redis read operations that are automatically dispatched to slaves. Any
|
31
|
-
# operation not listed here will be dispatched to the master.
|
32
|
-
REDIS_READ_OPS = Set[
|
33
|
-
:echo,
|
34
|
-
:exists,
|
35
|
-
:get,
|
36
|
-
:getbit,
|
37
|
-
:getrange,
|
38
|
-
:hexists,
|
39
|
-
:hget,
|
40
|
-
:hgetall,
|
41
|
-
:hkeys,
|
42
|
-
:hlen,
|
43
|
-
:hmget,
|
44
|
-
:hvals,
|
45
|
-
:keys,
|
46
|
-
:lindex,
|
47
|
-
:llen,
|
48
|
-
:lrange,
|
49
|
-
:mapped_hmget,
|
50
|
-
:mapped_mget,
|
51
|
-
:mget,
|
52
|
-
:scard,
|
53
|
-
:sdiff,
|
54
|
-
:sinter,
|
55
|
-
:sismember,
|
56
|
-
:smembers,
|
57
|
-
:srandmember,
|
58
|
-
:strlen,
|
59
|
-
:sunion,
|
60
|
-
:type,
|
61
|
-
:zcard,
|
62
|
-
:zcount,
|
63
|
-
:zrange,
|
64
|
-
:zrangebyscore,
|
65
|
-
:zrank,
|
66
|
-
:zrevrange,
|
67
|
-
:zrevrangebyscore,
|
68
|
-
:zrevrank,
|
69
|
-
:zscore
|
70
|
-
].freeze
|
71
|
-
|
72
|
-
# Unsupported Redis operations. These don't make sense in a client
|
73
|
-
# that abstracts the master/slave servers.
|
74
|
-
UNSUPPORTED_OPS = Set[:select, :dbsize].freeze
|
75
|
-
|
76
30
|
# Performance optimization: to avoid unnecessary method_missing calls,
|
77
31
|
# we proactively define methods that dispatch to the underlying redis
|
78
32
|
# calls.
|
@@ -93,25 +47,37 @@ module RedisFailover
|
|
93
47
|
# @option options [Logger] :logger logger override
|
94
48
|
# @option options [Boolean] :retry_failure indicates if failures are retried
|
95
49
|
# @option options [Integer] :max_retries max retries for a failure
|
50
|
+
# @option options [Boolean] :safe_mode indicates if safe mode is used or not
|
51
|
+
# @option options [Boolean] :master_only indicates if only redis master is used
|
96
52
|
# @return [RedisFailover::Client]
|
97
53
|
def initialize(options = {})
|
98
54
|
Util.logger = options[:logger] if options[:logger]
|
99
|
-
@zkservers = options.fetch(:zkservers) { raise ArgumentError, ':zkservers required'}
|
100
|
-
@znode = options[:znode_path] || Util::DEFAULT_ZNODE_PATH
|
101
|
-
@namespace = options[:namespace]
|
102
|
-
@password = options[:password]
|
103
|
-
@db = options[:db]
|
104
|
-
@retry = options[:retry_failure] || true
|
105
|
-
@max_retries = @retry ? options.fetch(:max_retries, 3) : 0
|
106
55
|
@master = nil
|
107
56
|
@slaves = []
|
108
57
|
@node_addresses = {}
|
109
58
|
@lock = Monitor.new
|
110
59
|
@current_client_key = "current-client-#{self.object_id}"
|
60
|
+
yield self if block_given?
|
61
|
+
|
62
|
+
parse_options(options)
|
111
63
|
setup_zk
|
112
64
|
build_clients
|
113
65
|
end
|
114
66
|
|
67
|
+
# Specifies a callback to invoke when the current redis node list changes.
|
68
|
+
#
|
69
|
+
# @param [Proc] a callback with current master and slaves as arguments
|
70
|
+
#
|
71
|
+
# @example Usage
|
72
|
+
# RedisFailover::Client.new(:zkservers => zk_servers) do |client|
|
73
|
+
# client.on_node_change do |master, slaves|
|
74
|
+
# logger.info("Nodes changed! master: #{master}, slaves: #{slaves}")
|
75
|
+
# end
|
76
|
+
# end
|
77
|
+
def on_node_change(&callback)
|
78
|
+
@on_node_change = callback
|
79
|
+
end
|
80
|
+
|
115
81
|
# Dispatches redis operations to master/slaves.
|
116
82
|
def method_missing(method, *args, &block)
|
117
83
|
if redis_operation?(method)
|
@@ -168,13 +134,31 @@ module RedisFailover
|
|
168
134
|
build_clients
|
169
135
|
end
|
170
136
|
|
137
|
+
# Retrieves the current redis master.
|
138
|
+
#
|
139
|
+
# @return [String] the host/port of the current master
|
140
|
+
def current_master
|
141
|
+
master = @lock.synchronize { @master }
|
142
|
+
address_for(master)
|
143
|
+
end
|
144
|
+
|
145
|
+
# Retrieves the current redis slaves.
|
146
|
+
#
|
147
|
+
# @return [Array<String>] an array of known slave host/port addresses
|
148
|
+
def current_slaves
|
149
|
+
slaves = @lock.synchronize { @slaves }
|
150
|
+
addresses_for(slaves)
|
151
|
+
end
|
152
|
+
|
171
153
|
private
|
172
154
|
|
173
155
|
# Sets up the underlying ZooKeeper connection.
|
174
156
|
def setup_zk
|
175
157
|
@zk = ZK.new(@zkservers)
|
176
158
|
@zk.watcher.register(@znode) { |event| handle_zk_event(event) }
|
177
|
-
@
|
159
|
+
if @safe_mode
|
160
|
+
@zk.on_expired_session { purge_clients }
|
161
|
+
end
|
178
162
|
@zk.on_connected { @zk.stat(@znode, :watch => true) }
|
179
163
|
@zk.stat(@znode, :watch => true)
|
180
164
|
update_znode_timestamp
|
@@ -210,7 +194,7 @@ module RedisFailover
|
|
210
194
|
# @param [Proc] block an optional block to pass to the method
|
211
195
|
# @return [Object] the result of dispatching the command
|
212
196
|
def dispatch(method, *args, &block)
|
213
|
-
|
197
|
+
if @safe_mode && !recently_heard_from_node_manager?
|
214
198
|
build_clients
|
215
199
|
end
|
216
200
|
|
@@ -277,10 +261,26 @@ module RedisFailover
|
|
277
261
|
rescue
|
278
262
|
purge_clients
|
279
263
|
raise
|
264
|
+
ensure
|
265
|
+
if should_notify?
|
266
|
+
@on_node_change.call(current_master, current_slaves)
|
267
|
+
@last_notified_master = current_master
|
268
|
+
@last_notified_slaves = current_slaves
|
269
|
+
end
|
280
270
|
end
|
281
271
|
end
|
282
272
|
end
|
283
273
|
|
274
|
+
# Determines if the on_node_change callback should be invoked.
|
275
|
+
#
|
276
|
+
# @return [Boolean] true if callback should be invoked, false otherwise
|
277
|
+
def should_notify?
|
278
|
+
return false unless @on_node_change
|
279
|
+
return true if @last_notified_master != current_master
|
280
|
+
return true if different?(Array(@last_notified_slaves), current_slaves)
|
281
|
+
false
|
282
|
+
end
|
283
|
+
|
284
284
|
# Fetches the known redis nodes from ZooKeeper.
|
285
285
|
#
|
286
286
|
# @return [Hash] the known master/slave redis servers
|
@@ -424,7 +424,16 @@ module RedisFailover
|
|
424
424
|
# nested blocks (e.g., block passed to multi).
|
425
425
|
def client_for(method)
|
426
426
|
stack = Thread.current[@current_client_key] ||= []
|
427
|
-
client = stack.last
|
427
|
+
client = if stack.last
|
428
|
+
stack.last
|
429
|
+
elsif @master_only
|
430
|
+
master
|
431
|
+
elsif REDIS_READ_OPS.include?(method)
|
432
|
+
slave
|
433
|
+
else
|
434
|
+
master
|
435
|
+
end
|
436
|
+
|
428
437
|
stack << client
|
429
438
|
client
|
430
439
|
end
|
@@ -436,5 +445,20 @@ module RedisFailover
|
|
436
445
|
end
|
437
446
|
nil
|
438
447
|
end
|
448
|
+
|
449
|
+
# Parses the configuration operations.
|
450
|
+
#
|
451
|
+
# @param [Hash] options the configuration options
|
452
|
+
def parse_options(options)
|
453
|
+
@zkservers = options.fetch(:zkservers) { raise ArgumentError, ':zkservers required'}
|
454
|
+
@znode = options.fetch(:znode_path, Util::DEFAULT_ZNODE_PATH)
|
455
|
+
@namespace = options[:namespace]
|
456
|
+
@password = options[:password]
|
457
|
+
@db = options[:db]
|
458
|
+
@retry = options.fetch(:retry_failure, true)
|
459
|
+
@max_retries = @retry ? options.fetch(:max_retries, 3) : 0
|
460
|
+
@safe_mode = options.fetch(:safe_mode, true)
|
461
|
+
@master_only = options.fetch(:master_only, false)
|
462
|
+
end
|
439
463
|
end
|
440
464
|
end
|
@@ -9,14 +9,6 @@ module RedisFailover
|
|
9
9
|
class NodeManager
|
10
10
|
include Util
|
11
11
|
|
12
|
-
# Name for the znode that handles exclusive locking between multiple
|
13
|
-
# Node Manager processes. Whoever holds the lock will be considered
|
14
|
-
# the "master" Node Manager, and will be responsible for monitoring
|
15
|
-
# the redis nodes. When a Node Manager that holds the lock disappears
|
16
|
-
# or fails, another Node Manager process will grab the lock and
|
17
|
-
# become the master.
|
18
|
-
LOCK_PATH = 'master_node_manager'
|
19
|
-
|
20
12
|
# Number of seconds to wait before retrying bootstrap process.
|
21
13
|
TIMEOUT = 5
|
22
14
|
|
@@ -34,6 +26,14 @@ module RedisFailover
|
|
34
26
|
@znode = @options[:znode_path] || Util::DEFAULT_ZNODE_PATH
|
35
27
|
@manual_znode = ManualFailover::ZNODE_PATH
|
36
28
|
@mutex = Mutex.new
|
29
|
+
|
30
|
+
# Name for the znode that handles exclusive locking between multiple
|
31
|
+
# Node Manager processes. Whoever holds the lock will be considered
|
32
|
+
# the "master" Node Manager, and will be responsible for monitoring
|
33
|
+
# the redis nodes. When a Node Manager that holds the lock disappears
|
34
|
+
# or fails, another Node Manager process will grab the lock and
|
35
|
+
# become the
|
36
|
+
@lock_path = "#{@znode}_lock".freeze
|
37
37
|
end
|
38
38
|
|
39
39
|
# Starts the node manager.
|
@@ -44,7 +44,7 @@ module RedisFailover
|
|
44
44
|
@leader = false
|
45
45
|
setup_zk
|
46
46
|
logger.info('Waiting to become master Node Manager ...')
|
47
|
-
@zk.with_lock(
|
47
|
+
@zk.with_lock(@lock_path) do
|
48
48
|
@leader = true
|
49
49
|
logger.info('Acquired master Node Manager lock')
|
50
50
|
discover_nodes
|
@@ -52,7 +52,7 @@ module RedisFailover
|
|
52
52
|
spawn_watchers
|
53
53
|
handle_state_reports
|
54
54
|
end
|
55
|
-
rescue ZK::Exceptions::InterruptedSession => ex
|
55
|
+
rescue ZK::Exceptions::InterruptedSession, ZKDisconnectedError => ex
|
56
56
|
logger.error("ZK error while attempting to manage nodes: #{ex.inspect}")
|
57
57
|
logger.error(ex.backtrace.join("\n"))
|
58
58
|
shutdown
|
@@ -83,6 +83,7 @@ module RedisFailover
|
|
83
83
|
def setup_zk
|
84
84
|
@zk.close! if @zk
|
85
85
|
@zk = ZK.new("#{@options[:zkservers]}#{@options[:chroot] || ''}")
|
86
|
+
@zk.on_expired_session { notify_state(:zk_disconnected, nil) }
|
86
87
|
|
87
88
|
@zk.register(@manual_znode) do |event|
|
88
89
|
@mutex.synchronize do
|
@@ -106,12 +107,13 @@ module RedisFailover
|
|
106
107
|
when :available then handle_available(node)
|
107
108
|
when :syncing then handle_syncing(node)
|
108
109
|
when :manual_failover then handle_manual_failover(node)
|
110
|
+
when :zk_disconnected then raise ZKDisconnectedError
|
109
111
|
else raise InvalidNodeStateError.new(node, state)
|
110
112
|
end
|
111
113
|
|
112
114
|
# flush current state
|
113
115
|
write_state
|
114
|
-
rescue ZK::Exceptions::InterruptedSession
|
116
|
+
rescue ZK::Exceptions::InterruptedSession, ZKDisconnectedError
|
115
117
|
# fail hard if this is a ZK connection-related error
|
116
118
|
raise
|
117
119
|
rescue => ex
|
@@ -219,19 +221,19 @@ module RedisFailover
|
|
219
221
|
def discover_nodes
|
220
222
|
@unavailable = []
|
221
223
|
nodes = @options[:nodes].map { |opts| Node.new(opts) }.uniq
|
222
|
-
|
224
|
+
@master = find_master(nodes)
|
223
225
|
@slaves = nodes - [@master]
|
224
226
|
logger.info("Managing master (#{@master}) and slaves" +
|
225
227
|
" (#{@slaves.map(&:to_s).join(', ')})")
|
226
228
|
|
227
229
|
# ensure that slaves are correctly pointing to this master
|
228
|
-
redirect_slaves_to(@master)
|
230
|
+
redirect_slaves_to(@master) if @master
|
229
231
|
end
|
230
232
|
|
231
233
|
# Spawns the {RedisFailover::NodeWatcher} instances for each managed node.
|
232
234
|
def spawn_watchers
|
233
235
|
@watchers = [@master, @slaves, @unavailable].flatten.map do |node|
|
234
|
-
NodeWatcher.new(self, node,
|
236
|
+
NodeWatcher.new(self, node, @options[:max_failures] || 3)
|
235
237
|
end
|
236
238
|
@watchers.each(&:watch)
|
237
239
|
end
|
@@ -314,8 +316,10 @@ module RedisFailover
|
|
314
316
|
|
315
317
|
# Creates the znode path containing the redis nodes.
|
316
318
|
def create_path
|
317
|
-
@zk.
|
318
|
-
|
319
|
+
unless @zk.exists?(@znode)
|
320
|
+
@zk.create(@znode, encode(current_nodes))
|
321
|
+
logger.info("Created ZooKeeper node #{@znode}")
|
322
|
+
end
|
319
323
|
rescue ZK::Exceptions::NodeExists
|
320
324
|
# best effort
|
321
325
|
end
|
@@ -10,8 +10,8 @@ module RedisFailover
|
|
10
10
|
options = CLI.parse(options)
|
11
11
|
@node_manager = NodeManager.new(options)
|
12
12
|
trap_signals
|
13
|
-
node_manager_thread = Thread.new { @node_manager.start }
|
14
|
-
node_manager_thread.join
|
13
|
+
@node_manager_thread = Thread.new { @node_manager.start }
|
14
|
+
@node_manager_thread.join
|
15
15
|
end
|
16
16
|
|
17
17
|
# Traps shutdown signals.
|
@@ -20,6 +20,7 @@ module RedisFailover
|
|
20
20
|
trap(signal) do
|
21
21
|
Util.logger.info('Shutting down ...')
|
22
22
|
@node_manager.shutdown
|
23
|
+
@node_manager_thread.join
|
23
24
|
exit(0)
|
24
25
|
end
|
25
26
|
end
|
data/lib/redis_failover/util.rb
CHANGED
@@ -5,6 +5,52 @@ module RedisFailover
|
|
5
5
|
module Util
|
6
6
|
extend self
|
7
7
|
|
8
|
+
# Redis read operations that are automatically dispatched to slaves. Any
|
9
|
+
# operation not listed here will be dispatched to the master.
|
10
|
+
REDIS_READ_OPS = Set[
|
11
|
+
:echo,
|
12
|
+
:exists,
|
13
|
+
:get,
|
14
|
+
:getbit,
|
15
|
+
:getrange,
|
16
|
+
:hexists,
|
17
|
+
:hget,
|
18
|
+
:hgetall,
|
19
|
+
:hkeys,
|
20
|
+
:hlen,
|
21
|
+
:hmget,
|
22
|
+
:hvals,
|
23
|
+
:keys,
|
24
|
+
:lindex,
|
25
|
+
:llen,
|
26
|
+
:lrange,
|
27
|
+
:mapped_hmget,
|
28
|
+
:mapped_mget,
|
29
|
+
:mget,
|
30
|
+
:scard,
|
31
|
+
:sdiff,
|
32
|
+
:sinter,
|
33
|
+
:sismember,
|
34
|
+
:smembers,
|
35
|
+
:srandmember,
|
36
|
+
:strlen,
|
37
|
+
:sunion,
|
38
|
+
:type,
|
39
|
+
:zcard,
|
40
|
+
:zcount,
|
41
|
+
:zrange,
|
42
|
+
:zrangebyscore,
|
43
|
+
:zrank,
|
44
|
+
:zrevrange,
|
45
|
+
:zrevrangebyscore,
|
46
|
+
:zrevrank,
|
47
|
+
:zscore
|
48
|
+
].freeze
|
49
|
+
|
50
|
+
# Unsupported Redis operations. These don't make sense in a client
|
51
|
+
# that abstracts the master/slave servers.
|
52
|
+
UNSUPPORTED_OPS = Set[:select, :dbsize].freeze
|
53
|
+
|
8
54
|
# Default node in ZK that contains the current list of available redis nodes.
|
9
55
|
DEFAULT_ZNODE_PATH = '/redis_failover_nodes'.freeze
|
10
56
|
|
@@ -12,7 +58,7 @@ module RedisFailover
|
|
12
58
|
REDIS_ERRORS = Errno.constants.map { |c| Errno.const_get(c) }
|
13
59
|
|
14
60
|
# Connectivity errors that the redis (>3.x) client raises.
|
15
|
-
REDIS_ERRORS << Redis::BaseError if Redis.const_defined?(
|
61
|
+
REDIS_ERRORS << Redis::BaseError if Redis.const_defined?('BaseError')
|
16
62
|
REDIS_ERRORS.freeze
|
17
63
|
|
18
64
|
# Full set of errors related to connectivity.
|
data/redis_failover.gemspec
CHANGED
@@ -4,8 +4,8 @@ require File.expand_path('../lib/redis_failover/version', __FILE__)
|
|
4
4
|
Gem::Specification.new do |gem|
|
5
5
|
gem.authors = ["Ryan LeCompte"]
|
6
6
|
gem.email = ["lecompte@gmail.com"]
|
7
|
-
gem.description = %(
|
8
|
-
gem.summary = %(
|
7
|
+
gem.description = %(redis_failover is a ZooKeeper-based automatic master/slave failover solution for Ruby)
|
8
|
+
gem.summary = %(redis_failover is a ZooKeeper-based automatic master/slave failover solution for Ruby)
|
9
9
|
gem.homepage = "http://github.com/ryanlecompte/redis_failover"
|
10
10
|
|
11
11
|
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
@@ -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', '< 4'])
|
19
19
|
gem.add_dependency('redis-namespace')
|
20
20
|
gem.add_dependency('multi_json', '~> 1')
|
21
21
|
gem.add_dependency('zk', '~> 1.6')
|
data/spec/client_spec.rb
CHANGED
@@ -26,7 +26,7 @@ module RedisFailover
|
|
26
26
|
end
|
27
27
|
|
28
28
|
describe Client do
|
29
|
-
let(:client) { ClientStub.new(:zkservers => 'localhost:9281') }
|
29
|
+
let(:client) { ClientStub.new(:zkservers => 'localhost:9281', :safe_mode => true) }
|
30
30
|
|
31
31
|
describe '#build_clients' do
|
32
32
|
it 'properly parses master' do
|
@@ -48,14 +48,28 @@ module RedisFailover
|
|
48
48
|
called.should be_true
|
49
49
|
end
|
50
50
|
|
51
|
-
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
51
|
+
context 'with :master_only false' do
|
52
|
+
it 'routes read operations to a slave' do
|
53
|
+
called = false
|
54
|
+
client.current_slaves.first.change_role_to('slave')
|
55
|
+
client.current_slaves.first.define_singleton_method(:get) do |*args|
|
56
|
+
called = true
|
57
|
+
end
|
58
|
+
client.get('foo')
|
59
|
+
called.should be_true
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'with :master_only true' do
|
64
|
+
it 'routes read operations to master' do
|
65
|
+
client = ClientStub.new(:zkservers => 'localhost:9281', :master_only => true)
|
66
|
+
called = false
|
67
|
+
client.current_master.define_singleton_method(:get) do |*args|
|
68
|
+
called = true
|
69
|
+
end
|
70
|
+
client.get('foo')
|
71
|
+
called.should be_true
|
56
72
|
end
|
57
|
-
client.get('foo')
|
58
|
-
called.should be_true
|
59
73
|
end
|
60
74
|
|
61
75
|
it 'reconnects when node is unavailable' do
|
@@ -90,10 +104,21 @@ module RedisFailover
|
|
90
104
|
expect { client.select }.to raise_error(UnsupportedOperationError)
|
91
105
|
end
|
92
106
|
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
107
|
+
context 'with :safe_mode enabled' do
|
108
|
+
it 'rebuilds clients when no communication from Node Manager within certain time window' do
|
109
|
+
client.instance_variable_set(:@last_znode_timestamp, Time.at(0))
|
110
|
+
client.should_receive(:build_clients)
|
111
|
+
client.del('foo')
|
112
|
+
end
|
113
|
+
end
|
114
|
+
|
115
|
+
context 'with :safe_mode disabled' do
|
116
|
+
it 'does not rebuild clients when no communication from Node Manager within certain time window' do
|
117
|
+
client = ClientStub.new(:zkservers => 'localhost:9281', :safe_mode => false)
|
118
|
+
client.instance_variable_set(:@last_znode_timestamp, Time.at(0))
|
119
|
+
client.should_not_receive(:build_clients)
|
120
|
+
client.del('foo')
|
121
|
+
end
|
97
122
|
end
|
98
123
|
end
|
99
124
|
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.
|
4
|
+
version: 0.9.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,24 +9,30 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2012-
|
12
|
+
date: 2012-08-15 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
|
+
- !ruby/object:Gem::Version
|
21
|
+
version: '2.2'
|
22
|
+
- - <
|
20
23
|
- !ruby/object:Gem::Version
|
21
|
-
version: '
|
24
|
+
version: '4'
|
22
25
|
type: :runtime
|
23
26
|
prerelease: false
|
24
27
|
version_requirements: !ruby/object:Gem::Requirement
|
25
28
|
none: false
|
26
29
|
requirements:
|
27
|
-
- -
|
30
|
+
- - ! '>='
|
31
|
+
- !ruby/object:Gem::Version
|
32
|
+
version: '2.2'
|
33
|
+
- - <
|
28
34
|
- !ruby/object:Gem::Version
|
29
|
-
version: '
|
35
|
+
version: '4'
|
30
36
|
- !ruby/object:Gem::Dependency
|
31
37
|
name: redis-namespace
|
32
38
|
requirement: !ruby/object:Gem::Requirement
|
@@ -123,7 +129,7 @@ dependencies:
|
|
123
129
|
- - ! '>='
|
124
130
|
- !ruby/object:Gem::Version
|
125
131
|
version: '0'
|
126
|
-
description:
|
132
|
+
description: redis_failover is a ZooKeeper-based automatic master/slave failover solution
|
127
133
|
for Ruby
|
128
134
|
email:
|
129
135
|
- lecompte@gmail.com
|
@@ -183,7 +189,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
183
189
|
version: '0'
|
184
190
|
segments:
|
185
191
|
- 0
|
186
|
-
hash:
|
192
|
+
hash: 827412647750283458
|
187
193
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
188
194
|
none: false
|
189
195
|
requirements:
|
@@ -192,13 +198,13 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
192
198
|
version: '0'
|
193
199
|
segments:
|
194
200
|
- 0
|
195
|
-
hash:
|
201
|
+
hash: 827412647750283458
|
196
202
|
requirements: []
|
197
203
|
rubyforge_project:
|
198
204
|
rubygems_version: 1.8.23
|
199
205
|
signing_key:
|
200
206
|
specification_version: 3
|
201
|
-
summary:
|
207
|
+
summary: redis_failover is a ZooKeeper-based automatic master/slave failover solution
|
202
208
|
for Ruby
|
203
209
|
test_files:
|
204
210
|
- spec/cli_spec.rb
|