redis_failover 0.8.9 → 0.9.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|