redis_failover 0.6.0 → 0.7.0

Sign up to get free protection for your applications and to get access to all the features.
data/Changes.md CHANGED
@@ -1,3 +1,9 @@
1
+ 0.7.0
2
+ -----------
3
+ - When new master promotion occurs, make existing slaves point to new candidate before promoting new master.
4
+ - Add support for specifying command-line options in a config.yml file for Node Manager.
5
+ - Upgrade to 0.9 version of ZK client and cleanup ZK connection error handling.
6
+
1
7
  0.6.0
2
8
  -----------
3
9
  - Add support for running multiple Node Manager processes for added redundancy (#4)
data/README.md CHANGED
@@ -1,4 +1,4 @@
1
- # Automatic Redis Failover Client / Node Manager
1
+ # Automatic Redis Failover
2
2
 
3
3
  [![Build Status](https://secure.travis-ci.org/ryanlecompte/redis_failover.png?branch=master)](http://travis-ci.org/ryanlecompte/redis_failover)
4
4
 
@@ -61,15 +61,36 @@ following options:
61
61
  Specific options:
62
62
  -n, --nodes NODES Comma-separated redis host:port pairs
63
63
  -z, --zkservers SERVERS Comma-separated ZooKeeper host:port pairs
64
- -p, --password [PASSWORD] Redis password
65
- --znode-path [PATH] Znode path override for storing redis server list
66
- --max-failures [COUNT] Max failures before manager marks node unavailable
64
+ -p, --password PASSWORD Redis password
65
+ --znode-path PATH Znode path override for storing redis server list
66
+ --max-failures COUNT Max failures before manager marks node unavailable
67
+ -C, --config PATH Path to YAML configuration file
67
68
  -h, --help Display all options
68
69
 
69
70
  To start the daemon for a simple master/slave configuration, use the following:
70
71
 
71
72
  redis_node_manager -n localhost:6379,localhost:6380 -z localhost:2181,localhost:2182,localhost:2183
72
73
 
74
+ The configuration parameters can also be specified in a config.yml file. An example configuration
75
+ would look like the following:
76
+
77
+ ---
78
+ :max_failures: 2
79
+ :nodes:
80
+ - localhost:6379
81
+ - localhost:1111
82
+ - localhost:2222
83
+ - localhost:3333
84
+ :zkservers:
85
+ - localhost:2181
86
+ - localhost:2182
87
+ - localhost:2183
88
+ :password: foobar
89
+
90
+ You would then simpy start the Node Manager via the following:
91
+
92
+ redis_node_manager -C config.yml
93
+
73
94
  The Node Manager will automatically discover the master/slaves upon startup. Note that it is
74
95
  a good idea to run more than one instance of the Node Manager daemon in your environment. At
75
96
  any moment, a single Node Manager process will be designated to monitor the redis servers. If
@@ -0,0 +1,14 @@
1
+ # Sample configuration file for Node Manager.
2
+ # redis_node_manager -C config.yml
3
+ ---
4
+ :max_failures: 2
5
+ :nodes:
6
+ - localhost:6379
7
+ - localhost:1111
8
+ - localhost:2222
9
+ - localhost:3333
10
+ :zkservers:
11
+ - localhost:2181
12
+ - localhost:2182
13
+ - localhost:2183
14
+ :password: foobar
@@ -10,31 +10,32 @@ module RedisFailover
10
10
 
11
11
  opts.on('-n', '--nodes NODES',
12
12
  'Comma-separated redis host:port pairs') do |nodes|
13
- # turns 'host1:port,host2:port' => [{:host => host, :port => port}, ...]
14
- options[:nodes] = nodes.split(',').map do |node|
15
- Hash[[:host, :port].zip(node.strip.split(':'))]
16
- end
13
+ options[:nodes] = nodes
17
14
  end
18
15
 
19
16
  opts.on('-z', '--zkservers SERVERS',
20
17
  'Comma-separated ZooKeeper host:port pairs') do |servers|
21
- options[:zkservers] = servers.strip
18
+ options[:zkservers] = servers
22
19
  end
23
20
 
24
- opts.on('-p', '--password [PASSWORD]', 'Redis password') do |password|
25
- options[:password] = password.strip
21
+ opts.on('-p', '--password PASSWORD', 'Redis password') do |password|
22
+ options[:password] = password
26
23
  end
27
24
 
28
- opts.on('--znode-path [PATH]',
25
+ opts.on('--znode-path PATH',
29
26
  'Znode path override for storing redis server list') do |path|
30
27
  options[:znode_path] = path
31
28
  end
32
29
 
33
- opts.on('--max-failures [COUNT]',
30
+ opts.on('--max-failures COUNT',
34
31
  'Max failures before manager marks node unavailable') do |max|
35
32
  options[:max_failures] = Integer(max)
36
33
  end
37
34
 
35
+ opts.on '-C', '--config PATH', "Path to YAML configuration file" do |file|
36
+ options[:config_file] = file
37
+ end
38
+
38
39
  opts.on('-h', '--help', 'Display all options') do
39
40
  puts opts
40
41
  exit
@@ -42,17 +43,16 @@ module RedisFailover
42
43
  end
43
44
 
44
45
  parser.parse(source)
46
+ if config_file = options[:config_file]
47
+ options = from_file(config_file)
48
+ end
49
+
45
50
  if required_options_missing?(options)
46
51
  puts parser
47
52
  exit
48
53
  end
49
54
 
50
- # assume password is same for all redis nodes
51
- if password = options[:password]
52
- options[:nodes].each { |opts| opts.update(:password => password) }
53
- end
54
-
55
- options
55
+ prepare(options)
56
56
  end
57
57
 
58
58
  def self.required_options_missing?(options)
@@ -60,5 +60,31 @@ module RedisFailover
60
60
  return true unless options.values_at(:nodes, :zkservers).all?
61
61
  false
62
62
  end
63
+
64
+ def self.from_file(file)
65
+ unless File.exists?(file)
66
+ raise ArgumentError, "File #{file} can't be found"
67
+ end
68
+ options = YAML.load_file(file)
69
+ options[:nodes] = options[:nodes].join(',')
70
+ options[:zkservers] = options[:zkservers].join(',')
71
+
72
+ options
73
+ end
74
+
75
+ def self.prepare(options)
76
+ options.each_value { |v| v.strip! if v.respond_to?(:strip!) }
77
+ # turns 'host1:port,host2:port' => [{:host => host, :port => port}, ...]
78
+ options[:nodes] = options[:nodes].split(',').map do |node|
79
+ Hash[[:host, :port].zip(node.split(':'))]
80
+ end
81
+
82
+ # assume password is same for all redis nodes
83
+ if password = options[:password]
84
+ options[:nodes].each { |opts| opts.update(:password => password) }
85
+ end
86
+
87
+ options
88
+ end
63
89
  end
64
90
  end
@@ -105,11 +105,12 @@ module RedisFailover
105
105
  @password = options[:password]
106
106
  @db = options[:db]
107
107
  @retry = options[:retry_failure] || true
108
- @max_retries = @retry ? options.fetch(:max_retries, 3) : 1
108
+ @max_retries = @retry ? options.fetch(:max_retries, 3) : 0
109
109
  @master = nil
110
110
  @slaves = []
111
+ @queue = Queue.new
111
112
  @lock = Monitor.new
112
- setup_zookeeper_client
113
+ start_zk
113
114
  build_clients
114
115
  end
115
116
 
@@ -133,40 +134,66 @@ module RedisFailover
133
134
 
134
135
  private
135
136
 
136
- def setup_zookeeper_client
137
- @zkclient = ZkClient.new(@zkservers) do |client|
138
- # when session expires, purge client list
139
- client.on_session_expiration do
140
- purge_clients
141
- end
137
+ def zk
138
+ @lock.synchronize { @zk }
139
+ end
142
140
 
143
- # when we are disconnected, purge client list
144
- client.event_handler.register_state_handler(:connecting) do
145
- purge_clients
141
+ def start_zk
142
+ @delivery_thread ||= Thread.new do
143
+ while event = @queue.pop
144
+ if event.is_a?(Proc)
145
+ event.call
146
+ else
147
+ handle_zk_event(event)
148
+ end
146
149
  end
150
+ end
151
+ reconnect_zk
152
+ end
147
153
 
148
- # when session is recovered, watch again
149
- client.on_session_recovered do
150
- client.stat(@znode, :watch => true)
154
+ def handle_session_established
155
+ @lock.synchronize do
156
+ @zk.watcher.register(@znode) do |event|
157
+ @queue << event
151
158
  end
152
-
153
- # register a watcher for future changes
154
- client.watcher.register(@znode) do |event|
155
- if event.node_created? || event.node_changed?
156
- update_znode_timestamp
157
- build_clients
158
- elsif event.node_deleted?
159
- update_znode_timestamp
160
- purge_clients
161
- client.stat(@znode, :watch => true)
162
- else
163
- logger.error("Unknown ZK node event: #{event.inspect}")
164
- end
159
+ @zk.on_expired_session do
160
+ @queue << proc { reconnect_zk }
161
+ end
162
+ @zk.event_handler.register_state_handler(:connecting) do
163
+ @queue << proc { handle_lost_connection }
164
+ end
165
+ @zk.on_connected do
166
+ @zk.stat(@znode, :watch => true)
165
167
  end
166
168
  end
169
+ end
170
+
171
+ def handle_zk_event(event)
172
+ update_znode_timestamp
173
+ if event.node_created? || event.node_changed?
174
+ build_clients
175
+ elsif event.node_deleted?
176
+ purge_clients
177
+ zk.stat(@znode, :watch => true)
178
+ else
179
+ logger.error("Unknown ZK node event: #{event.inspect}")
180
+ end
181
+ end
182
+
183
+ def reconnect_zk
184
+ handle_lost_connection
185
+ @lock.synchronize do
186
+ @zk.close! if @zk
187
+ @zk = ZK.new(@zkservers)
188
+ end
189
+ handle_session_established
167
190
  update_znode_timestamp
168
191
  end
169
192
 
193
+ def handle_lost_connection
194
+ purge_clients
195
+ end
196
+
170
197
  def redis_operation?(method)
171
198
  Redis.public_instance_methods(false).include?(method)
172
199
  end
@@ -197,7 +224,6 @@ module RedisFailover
197
224
  sleep(RETRY_WAIT_TIME)
198
225
  retry
199
226
  end
200
-
201
227
  raise
202
228
  end
203
229
  end
@@ -221,7 +247,7 @@ module RedisFailover
221
247
 
222
248
  def build_clients
223
249
  @lock.synchronize do
224
- tries = 0
250
+ retried = false
225
251
 
226
252
  begin
227
253
  nodes = fetch_nodes
@@ -233,24 +259,23 @@ module RedisFailover
233
259
  new_slaves = new_clients_for(*nodes[:slaves])
234
260
  @master = new_master
235
261
  @slaves = new_slaves
236
- rescue StandardError, *CONNECTIVITY_ERRORS => ex
237
- purge_clients
238
- logger.error("Failed to fetch nodes from #{@zkservers} - #{ex.inspect}")
262
+ rescue ZK::Exceptions::InterruptedSession => ex
263
+ logger.error("ZK error while attempting to build clients: #{ex.inspect}")
239
264
  logger.error(ex.backtrace.join("\n"))
240
265
 
241
- if tries < @max_retries
242
- tries += 1
243
- sleep(RETRY_WAIT_TIME)
266
+ # when ZK is disconnected, retry once
267
+ unless retried
268
+ reconnect_zk
269
+ retried = true
244
270
  retry
245
271
  end
246
-
247
272
  raise
248
273
  end
249
274
  end
250
275
  end
251
276
 
252
277
  def fetch_nodes
253
- data = @zkclient.get(@znode, :watch => true).first
278
+ data = zk.get(@znode, :watch => true).first
254
279
  nodes = symbolize_keys(decode(data))
255
280
  logger.debug("Fetched nodes: #{nodes}")
256
281
 
@@ -53,11 +53,11 @@ module RedisFailover
53
53
  end
54
54
  end
55
55
 
56
- def make_slave!(master)
56
+ def make_slave!(node)
57
57
  perform_operation do |redis|
58
- unless slave_of?(master)
59
- redis.slaveof(master.host, master.port)
60
- logger.info("#{self} is now a slave of master #{master}")
58
+ unless slave_of?(node)
59
+ redis.slaveof(node.host, node.port)
60
+ logger.info("#{self} is now a slave of #{node}")
61
61
  wakeup
62
62
  end
63
63
  end
@@ -18,29 +18,29 @@ module RedisFailover
18
18
  LOCK_PATH = 'master_node_manager'
19
19
 
20
20
  # Number of seconds to wait before retrying bootstrap process.
21
- TIMEOUT = 3
21
+ TIMEOUT = 5
22
22
 
23
23
  def initialize(options)
24
24
  logger.info("Redis Node Manager v#{VERSION} starting (#{RUBY_DESCRIPTION})")
25
25
  @options = options
26
26
  @znode = @options[:znode_path] || Util::DEFAULT_ZNODE_PATH
27
- @unavailable = []
28
- @queue = Queue.new
29
27
  end
30
28
 
31
29
  def start
32
- @zkclient = ZkClient.new(@options[:zkservers])
30
+ @queue = Queue.new
31
+ @zk = ZK.new(@options[:zkservers])
33
32
  logger.info('Waiting to become master Node Manager ...')
34
- @zkclient.with_lock(LOCK_PATH) do
33
+ @zk.with_lock(LOCK_PATH) do
35
34
  logger.info('Acquired master Node Manager lock')
36
35
  discover_nodes
37
36
  initialize_path
38
37
  spawn_watchers
39
38
  handle_state_reports
40
39
  end
41
- rescue *CONNECTIVITY_ERRORS => ex
42
- logger.error("Error while attempting to manage nodes: #{ex.inspect}")
40
+ rescue ZK::Exceptions::InterruptedSession => ex
41
+ logger.error("ZK error while attempting to manage nodes: #{ex.inspect}")
43
42
  logger.error(ex.backtrace.join("\n"))
43
+ shutdown
44
44
  sleep(TIMEOUT)
45
45
  retry
46
46
  end
@@ -51,7 +51,8 @@ module RedisFailover
51
51
 
52
52
  def shutdown
53
53
  @queue << nil
54
- @watchers.each(&:shutdown)
54
+ @watchers.each(&:shutdown) if @watchers
55
+ @zk.close! if @zk
55
56
  end
56
57
 
57
58
  private
@@ -69,7 +70,10 @@ module RedisFailover
69
70
 
70
71
  # flush current state
71
72
  write_state
72
- rescue StandardError, *CONNECTIVITY_ERRORS => ex
73
+ rescue ZK::Exceptions::InterruptedSession
74
+ # fail hard if this is a ZK connection-related error
75
+ raise
76
+ rescue => ex
73
77
  logger.error("Error handling #{state_report.inspect}: #{ex.inspect}")
74
78
  logger.error(ex.backtrace.join("\n"))
75
79
  end
@@ -134,15 +138,17 @@ module RedisFailover
134
138
  return
135
139
  end
136
140
 
141
+ redirect_slaves_to(candidate)
137
142
  candidate.make_master!
138
143
  @master = candidate
139
- redirect_slaves_to_master
144
+
140
145
  create_path
141
146
  write_state
142
147
  logger.info("Successfully promoted #{candidate} to master.")
143
148
  end
144
149
 
145
150
  def discover_nodes
151
+ @unavailable = []
146
152
  nodes = @options[:nodes].map { |opts| Node.new(opts) }.uniq
147
153
  raise NoMasterError unless @master = find_master(nodes)
148
154
  @slaves = nodes - [@master]
@@ -150,7 +156,7 @@ module RedisFailover
150
156
  " (#{@slaves.map(&:to_s).join(', ')})")
151
157
 
152
158
  # ensure that slaves are correctly pointing to this master
153
- redirect_slaves_to_master
159
+ redirect_slaves_to(@master)
154
160
  end
155
161
 
156
162
  def spawn_watchers
@@ -170,12 +176,12 @@ module RedisFailover
170
176
  end
171
177
  end
172
178
 
173
- def redirect_slaves_to_master
179
+ def redirect_slaves_to(node)
174
180
  @slaves.dup.each do |slave|
175
181
  begin
176
- slave.make_slave!(@master)
182
+ slave.make_slave!(node)
177
183
  rescue NodeUnavailableError
178
- logger.info("Failed to redirect unreachable slave #{slave} to master #{@master}")
184
+ logger.info("Failed to redirect unreachable slave #{slave} to #{node}")
179
185
  force_unavailable_slave(slave)
180
186
  end
181
187
  end
@@ -216,14 +222,14 @@ module RedisFailover
216
222
  end
217
223
 
218
224
  def delete_path
219
- @zkclient.delete(@znode)
225
+ @zk.delete(@znode)
220
226
  logger.info("Deleted ZooKeeper node #{@znode}")
221
227
  rescue ZK::Exceptions::NoNode => ex
222
228
  logger.info("Tried to delete missing znode: #{ex.inspect}")
223
229
  end
224
230
 
225
231
  def create_path
226
- @zkclient.create(@znode, encode(current_nodes), :ephemeral => true)
232
+ @zk.create(@znode, encode(current_nodes), :ephemeral => true)
227
233
  logger.info("Created ZooKeeper node #{@znode}")
228
234
  rescue ZK::Exceptions::NodeExists
229
235
  # best effort
@@ -236,7 +242,7 @@ module RedisFailover
236
242
 
237
243
  def write_state
238
244
  create_path
239
- @zkclient.set(@znode, encode(current_nodes))
245
+ @zk.set(@znode, encode(current_nodes))
240
246
  end
241
247
  end
242
248
  end
@@ -14,9 +14,9 @@ module RedisFailover
14
14
  # Full set of errors related to connectivity.
15
15
  CONNECTIVITY_ERRORS = [
16
16
  RedisFailover::Error,
17
- ZK::Exceptions::KeeperException,
18
- ZookeeperExceptions::ZookeeperException,
19
- REDIS_ERRORS].flatten.freeze
17
+ ZK::Exceptions::InterruptedSession,
18
+ REDIS_ERRORS
19
+ ].flatten.freeze
20
20
 
21
21
  def symbolize_keys(hash)
22
22
  Hash[hash.map { |k, v| [k.to_sym, v] }]
@@ -1,3 +1,3 @@
1
1
  module RedisFailover
2
- VERSION = "0.6.0"
2
+ VERSION = "0.7.0"
3
3
  end
@@ -1,5 +1,6 @@
1
1
  require 'zk'
2
2
  require 'set'
3
+ require 'yaml'
3
4
  require 'redis'
4
5
  require 'thread'
5
6
  require 'logger'
@@ -15,6 +16,5 @@ require 'redis_failover/errors'
15
16
  require 'redis_failover/client'
16
17
  require 'redis_failover/runner'
17
18
  require 'redis_failover/version'
18
- require 'redis_failover/zk_client'
19
19
  require 'redis_failover/node_manager'
20
20
  require 'redis_failover/node_watcher'
@@ -18,7 +18,7 @@ Gem::Specification.new do |gem|
18
18
  gem.add_dependency('redis')
19
19
  gem.add_dependency('redis-namespace')
20
20
  gem.add_dependency('multi_json', '>= 1.0', '< 1.3')
21
- gem.add_dependency('zk', '~> 0.8.8')
21
+ gem.add_dependency('zk', '~> 0.9')
22
22
 
23
23
  gem.add_development_dependency('rake')
24
24
  gem.add_development_dependency('rspec')
data/spec/spec_helper.rb CHANGED
@@ -12,8 +12,9 @@ end
12
12
 
13
13
  module RedisFailover
14
14
  Util.logger = NullObject.new
15
- def ZkClient.new(*args); NullObject.new; end
16
15
  end
17
16
 
17
+ def ZK.new(*args); NullObject.new; end
18
+
18
19
  RSpec.configure do |config|
19
20
  end
@@ -22,6 +22,7 @@ module RedisFailover
22
22
  [master, slave].each { |node| node.extend(RedisStubSupport) }
23
23
  master.make_master!
24
24
  slave.make_slave!(master)
25
+ @unavailable = []
25
26
  @master = master
26
27
  @slaves = [slave]
27
28
  @nodes_discovered = true
@@ -33,6 +34,7 @@ module RedisFailover
33
34
 
34
35
  def start_processing
35
36
  @thread = Thread.new { start }
37
+ sleep(1.5)
36
38
  end
37
39
 
38
40
  def stop_processing
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.6.0
4
+ version: 0.7.0
5
5
  prerelease:
6
6
  platform: ruby
7
7
  authors:
@@ -9,11 +9,11 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2012-04-21 00:00:00.000000000 Z
12
+ date: 2012-04-24 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: redis
16
- requirement: !ruby/object:Gem::Requirement
16
+ requirement: &70345784320360 !ruby/object:Gem::Requirement
17
17
  none: false
18
18
  requirements:
19
19
  - - ! '>='
@@ -21,15 +21,10 @@ dependencies:
21
21
  version: '0'
22
22
  type: :runtime
23
23
  prerelease: false
24
- version_requirements: !ruby/object:Gem::Requirement
25
- none: false
26
- requirements:
27
- - - ! '>='
28
- - !ruby/object:Gem::Version
29
- version: '0'
24
+ version_requirements: *70345784320360
30
25
  - !ruby/object:Gem::Dependency
31
26
  name: redis-namespace
32
- requirement: !ruby/object:Gem::Requirement
27
+ requirement: &70345784319940 !ruby/object:Gem::Requirement
33
28
  none: false
34
29
  requirements:
35
30
  - - ! '>='
@@ -37,15 +32,10 @@ dependencies:
37
32
  version: '0'
38
33
  type: :runtime
39
34
  prerelease: false
40
- version_requirements: !ruby/object:Gem::Requirement
41
- none: false
42
- requirements:
43
- - - ! '>='
44
- - !ruby/object:Gem::Version
45
- version: '0'
35
+ version_requirements: *70345784319940
46
36
  - !ruby/object:Gem::Dependency
47
37
  name: multi_json
48
- requirement: !ruby/object:Gem::Requirement
38
+ requirement: &70345784319420 !ruby/object:Gem::Requirement
49
39
  none: false
50
40
  requirements:
51
41
  - - ! '>='
@@ -56,34 +46,21 @@ dependencies:
56
46
  version: '1.3'
57
47
  type: :runtime
58
48
  prerelease: false
59
- version_requirements: !ruby/object:Gem::Requirement
60
- none: false
61
- requirements:
62
- - - ! '>='
63
- - !ruby/object:Gem::Version
64
- version: '1.0'
65
- - - <
66
- - !ruby/object:Gem::Version
67
- version: '1.3'
49
+ version_requirements: *70345784319420
68
50
  - !ruby/object:Gem::Dependency
69
51
  name: zk
70
- requirement: !ruby/object:Gem::Requirement
52
+ requirement: &70345784318660 !ruby/object:Gem::Requirement
71
53
  none: false
72
54
  requirements:
73
55
  - - ~>
74
56
  - !ruby/object:Gem::Version
75
- version: 0.8.8
57
+ version: '0.9'
76
58
  type: :runtime
77
59
  prerelease: false
78
- version_requirements: !ruby/object:Gem::Requirement
79
- none: false
80
- requirements:
81
- - - ~>
82
- - !ruby/object:Gem::Version
83
- version: 0.8.8
60
+ version_requirements: *70345784318660
84
61
  - !ruby/object:Gem::Dependency
85
62
  name: rake
86
- requirement: !ruby/object:Gem::Requirement
63
+ requirement: &70345784318280 !ruby/object:Gem::Requirement
87
64
  none: false
88
65
  requirements:
89
66
  - - ! '>='
@@ -91,15 +68,10 @@ dependencies:
91
68
  version: '0'
92
69
  type: :development
93
70
  prerelease: false
94
- version_requirements: !ruby/object:Gem::Requirement
95
- none: false
96
- requirements:
97
- - - ! '>='
98
- - !ruby/object:Gem::Version
99
- version: '0'
71
+ version_requirements: *70345784318280
100
72
  - !ruby/object:Gem::Dependency
101
73
  name: rspec
102
- requirement: !ruby/object:Gem::Requirement
74
+ requirement: &70345784317820 !ruby/object:Gem::Requirement
103
75
  none: false
104
76
  requirements:
105
77
  - - ! '>='
@@ -107,12 +79,7 @@ dependencies:
107
79
  version: '0'
108
80
  type: :development
109
81
  prerelease: false
110
- version_requirements: !ruby/object:Gem::Requirement
111
- none: false
112
- requirements:
113
- - - ! '>='
114
- - !ruby/object:Gem::Version
115
- version: '0'
82
+ version_requirements: *70345784317820
116
83
  description: Redis Failover is a ZooKeeper-based automatic master/slave failover solution
117
84
  for Ruby
118
85
  email:
@@ -130,6 +97,7 @@ files:
130
97
  - README.md
131
98
  - Rakefile
132
99
  - bin/redis_node_manager
100
+ - examples/config.yml
133
101
  - lib/redis_failover.rb
134
102
  - lib/redis_failover/cli.rb
135
103
  - lib/redis_failover/client.rb
@@ -140,7 +108,6 @@ files:
140
108
  - lib/redis_failover/runner.rb
141
109
  - lib/redis_failover/util.rb
142
110
  - lib/redis_failover/version.rb
143
- - lib/redis_failover/zk_client.rb
144
111
  - redis_failover.gemspec
145
112
  - spec/cli_spec.rb
146
113
  - spec/client_spec.rb
@@ -165,7 +132,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
165
132
  version: '0'
166
133
  segments:
167
134
  - 0
168
- hash: 2836022562258381397
135
+ hash: -4131531819785678189
169
136
  required_rubygems_version: !ruby/object:Gem::Requirement
170
137
  none: false
171
138
  requirements:
@@ -174,10 +141,10 @@ required_rubygems_version: !ruby/object:Gem::Requirement
174
141
  version: '0'
175
142
  segments:
176
143
  - 0
177
- hash: 2836022562258381397
144
+ hash: -4131531819785678189
178
145
  requirements: []
179
146
  rubyforge_project:
180
- rubygems_version: 1.8.23
147
+ rubygems_version: 1.8.16
181
148
  signing_key:
182
149
  specification_version: 3
183
150
  summary: Redis Failover is a ZooKeeper-based automatic master/slave failover solution
@@ -1,93 +0,0 @@
1
- module RedisFailover
2
- # ZkClient is a thin wrapper over the ZK client to gracefully handle reconnects
3
- # when a session expires.
4
- class ZkClient
5
- include Util
6
-
7
- # Time to sleep before retrying a failed operation.
8
- TIMEOUT = 2
9
-
10
- # Maximum reconnect attempts.
11
- MAX_RECONNECTS = 3
12
-
13
- # Errors that are candidates for rebuilding the underlying ZK client.
14
- RECONNECTABLE_ERRORS = [
15
- ZookeeperExceptions::ZookeeperException::SessionExpired,
16
- ZookeeperExceptions::ZookeeperException::SystemError,
17
- ZookeeperExceptions::ZookeeperException::ConnectionLoss,
18
- ZookeeperExceptions::ZookeeperException::OperationTimeOut,
19
- ZookeeperExceptions::ZookeeperException::AuthFailed,
20
- ZookeeperExceptions::ZookeeperException::SessionMoved,
21
- ZookeeperExceptions::ZookeeperException::ConnectionClosed,
22
- ZookeeperExceptions::ZookeeperException::NotConnected
23
- ].freeze
24
-
25
- # ZK methods that are wrapped with reconnect logic.
26
- WRAPPED_ZK_METHODS = [
27
- :get,
28
- :set,
29
- :watcher,
30
- :event_handler,
31
- :stat,
32
- :create,
33
- :delete,
34
- :with_lock].freeze
35
-
36
- def initialize(servers, &setup_block)
37
- @servers = servers
38
- @setup_block = setup_block
39
- @lock = Mutex.new
40
- build_client
41
- end
42
-
43
- def on_session_expiration(&block)
44
- @client.on_expired_session { block.call }
45
- @on_session_expiration = block
46
- end
47
-
48
- def on_session_recovered(&block)
49
- @client.on_connected { block.call }
50
- @on_session_recovered = block
51
- end
52
-
53
- WRAPPED_ZK_METHODS.each do |zk_method|
54
- class_eval(<<-RUBY, __FILE__, __LINE__ + 1)
55
- def #{zk_method}(*args, &block)
56
- perform_with_reconnect do
57
- @client.#{zk_method}(*args, &block)
58
- end
59
- end
60
- RUBY
61
- end
62
-
63
- private
64
-
65
- def perform_with_reconnect
66
- tries = 0
67
- begin
68
- yield
69
- rescue *RECONNECTABLE_ERRORS => ex
70
- logger.error("ZooKeeper connection error, rebuilding client: #{ex.inspect}")
71
- logger.error(ex.backtrace.join("\n"))
72
- if tries < MAX_RECONNECTS
73
- tries += 1
74
- @on_session_expiration.call if @on_session_expiration
75
- build_client
76
- @on_session_recovered.call if @on_session_recovered
77
- sleep(TIMEOUT)
78
- retry
79
- end
80
-
81
- raise
82
- end
83
- end
84
-
85
- def build_client
86
- @lock.synchronize do
87
- @client = ZK.new(@servers)
88
- @setup_block.call(self) if @setup_block
89
- logger.info("Communicating with ZooKeeper servers #{@servers}")
90
- end
91
- end
92
- end
93
- end