redis_failover 0.2.0 → 0.3.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/.travis.yml +4 -0
- data/Changes.md +6 -0
- data/README.md +2 -0
- data/lib/redis_failover/client.rb +52 -22
- data/lib/redis_failover/util.rb +4 -0
- data/lib/redis_failover/version.rb +1 -1
- data/spec/client_spec.rb +22 -1
- data/spec/util_spec.rb +10 -0
- metadata +17 -16
data/.travis.yml
ADDED
data/Changes.md
CHANGED
data/README.md
CHANGED
@@ -1,5 +1,7 @@
|
|
1
1
|
# Automatic Redis Failover Client/Server
|
2
2
|
|
3
|
+
[](http://travis-ci.org/ryanlecompte/redis_failover)
|
4
|
+
|
3
5
|
Redis Failover attempts to provides a full automatic master/slave failover solution for Ruby. Redis does not provide
|
4
6
|
an automatic failover capability when configured for master/slave replication. When the master node dies,
|
5
7
|
a new master must be manually brought online and assigned as the slave's new master. This manual
|
@@ -86,11 +86,11 @@ module RedisFailover
|
|
86
86
|
@retry = options[:retry_failure] || true
|
87
87
|
@max_retries = @retry ? options.fetch(:max_retries, 3) : 0
|
88
88
|
@server_url = "http://#{options[:host]}:#{options[:port]}/redis_servers"
|
89
|
-
@redis_servers = nil
|
90
89
|
@master = nil
|
91
90
|
@slaves = []
|
92
91
|
@lock = Mutex.new
|
93
92
|
build_clients
|
93
|
+
start_background_monitor
|
94
94
|
end
|
95
95
|
|
96
96
|
def method_missing(method, *args, &block)
|
@@ -106,7 +106,7 @@ module RedisFailover
|
|
106
106
|
end
|
107
107
|
|
108
108
|
def inspect
|
109
|
-
"#<RedisFailover::Client - master: #{
|
109
|
+
"#<RedisFailover::Client - master: #{master_name}, slaves: #{slave_names})>"
|
110
110
|
end
|
111
111
|
alias_method :to_s, :inspect
|
112
112
|
|
@@ -141,9 +141,10 @@ module RedisFailover
|
|
141
141
|
end
|
142
142
|
|
143
143
|
def master
|
144
|
-
|
145
|
-
|
146
|
-
|
144
|
+
master = @master
|
145
|
+
if master
|
146
|
+
verify_role!(master, :master)
|
147
|
+
return master
|
147
148
|
end
|
148
149
|
raise NoMasterError
|
149
150
|
end
|
@@ -162,16 +163,19 @@ module RedisFailover
|
|
162
163
|
tries = 0
|
163
164
|
|
164
165
|
begin
|
165
|
-
logger.info('
|
166
|
-
|
167
|
-
|
168
|
-
|
166
|
+
logger.info('Checking for new redis nodes.')
|
167
|
+
nodes = fetch_nodes
|
168
|
+
return unless nodes_changed?(nodes)
|
169
|
+
|
170
|
+
logger.info('Node change detected, rebuilding clients.')
|
171
|
+
master = new_clients_for(nodes[:master]).first if nodes[:master]
|
172
|
+
slaves = new_clients_for(*nodes[:slaves])
|
169
173
|
|
170
174
|
# once clients are successfully created, swap the references
|
171
175
|
@master = master
|
172
176
|
@slaves = slaves
|
173
177
|
rescue => ex
|
174
|
-
logger.error("Failed to fetch
|
178
|
+
logger.error("Failed to fetch nodes from #{@server_url} - #{ex.message}")
|
175
179
|
logger.error(ex.backtrace.join("\n"))
|
176
180
|
|
177
181
|
if tries < @max_retries
|
@@ -184,11 +188,11 @@ module RedisFailover
|
|
184
188
|
end
|
185
189
|
end
|
186
190
|
|
187
|
-
def
|
191
|
+
def fetch_nodes
|
188
192
|
open(@server_url) do |io|
|
189
|
-
|
190
|
-
logger.info("Fetched
|
191
|
-
|
193
|
+
nodes = symbolize_keys(MultiJson.decode(io))
|
194
|
+
logger.info("Fetched nodes: #{nodes}")
|
195
|
+
nodes
|
192
196
|
end
|
193
197
|
end
|
194
198
|
|
@@ -203,26 +207,52 @@ module RedisFailover
|
|
203
207
|
end
|
204
208
|
end
|
205
209
|
|
206
|
-
def
|
207
|
-
|
208
|
-
name_for(@master)
|
210
|
+
def master_name
|
211
|
+
address_for(@master) || 'none'
|
209
212
|
end
|
210
213
|
|
211
|
-
def
|
212
|
-
return
|
213
|
-
@slaves
|
214
|
+
def slave_names
|
215
|
+
return 'none' if @slaves.empty?
|
216
|
+
addresses_for(@slaves).join(', ')
|
214
217
|
end
|
215
218
|
|
216
219
|
def verify_role!(node, role)
|
217
220
|
current_role = node.info['role']
|
218
221
|
if current_role.to_sym != role
|
219
|
-
raise InvalidNodeRoleError.new(
|
222
|
+
raise InvalidNodeRoleError.new(address_for(node), role, current_role)
|
220
223
|
end
|
221
224
|
role
|
222
225
|
end
|
223
226
|
|
224
|
-
def
|
227
|
+
def addresses_for(nodes)
|
228
|
+
nodes.map { |node| address_for(node) }
|
229
|
+
end
|
230
|
+
|
231
|
+
def address_for(node)
|
232
|
+
return unless node
|
225
233
|
"#{node.client.host}:#{node.client.port}"
|
226
234
|
end
|
235
|
+
|
236
|
+
def nodes_changed?(new_nodes)
|
237
|
+
return true if address_for(@master) != new_nodes[:master]
|
238
|
+
return true if different?(addresses_for(@slaves), new_nodes[:slaves])
|
239
|
+
false
|
240
|
+
end
|
241
|
+
|
242
|
+
# Spawns a background thread to periodically fetch the latest
|
243
|
+
# set of nodes from the redis failover server.
|
244
|
+
def start_background_monitor
|
245
|
+
Thread.new do
|
246
|
+
loop do
|
247
|
+
sleep(10)
|
248
|
+
begin
|
249
|
+
build_clients
|
250
|
+
rescue => ex
|
251
|
+
logger.error("Failed to poll for new nodes from #{@server_url} - #{ex.message}")
|
252
|
+
logger.error(ex.backtrace.join("\n"))
|
253
|
+
end
|
254
|
+
end
|
255
|
+
end
|
256
|
+
end
|
227
257
|
end
|
228
258
|
end
|
data/lib/redis_failover/util.rb
CHANGED
data/spec/client_spec.rb
CHANGED
@@ -11,7 +11,7 @@ module RedisFailover
|
|
11
11
|
@slaves
|
12
12
|
end
|
13
13
|
|
14
|
-
def
|
14
|
+
def fetch_nodes
|
15
15
|
{
|
16
16
|
:master => 'localhost:6379',
|
17
17
|
:slaves => ['localhost:1111'],
|
@@ -31,6 +31,18 @@ module RedisFailover
|
|
31
31
|
it 'properly parses slaves' do
|
32
32
|
client.current_slaves.first.to_s.should == 'localhost:1111'
|
33
33
|
end
|
34
|
+
|
35
|
+
it 'does not rebuild clients if hosts have not changed' do
|
36
|
+
class << client
|
37
|
+
attr_reader :built_new_client
|
38
|
+
def new_clients_for(*)
|
39
|
+
@built_new_client = true
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
5.times { client.send(:build_clients) }
|
44
|
+
client.built_new_client.should_not be_true
|
45
|
+
end
|
34
46
|
end
|
35
47
|
|
36
48
|
describe '#dispatch' do
|
@@ -52,6 +64,15 @@ module RedisFailover
|
|
52
64
|
@reconnected = true
|
53
65
|
super
|
54
66
|
end
|
67
|
+
|
68
|
+
def fetch_nodes
|
69
|
+
@calls ||= 0
|
70
|
+
{
|
71
|
+
:master => "localhost:222#{@calls += 1}",
|
72
|
+
:slaves => ['localhost:1111'],
|
73
|
+
:unreachable => []
|
74
|
+
}
|
75
|
+
end
|
55
76
|
end
|
56
77
|
|
57
78
|
client.current_master.make_unreachable!
|
data/spec/util_spec.rb
CHANGED
@@ -7,5 +7,15 @@ module RedisFailover
|
|
7
7
|
Util.symbolize_keys('a' => 1, 'b' => 2).should == {:a => 1, :b => 2}
|
8
8
|
end
|
9
9
|
end
|
10
|
+
|
11
|
+
describe '.different?' do
|
12
|
+
it 'handles different arrays' do
|
13
|
+
Util.different?([1,2,3], [1,5,3]).should be_true
|
14
|
+
end
|
15
|
+
|
16
|
+
it 'handles non-different arrays' do
|
17
|
+
Util.different?([1,2,3], [3,2,1]).should be_false
|
18
|
+
end
|
19
|
+
end
|
10
20
|
end
|
11
21
|
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.3.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-
|
12
|
+
date: 2012-04-14 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: redis
|
16
|
-
requirement: &
|
16
|
+
requirement: &70202974273640 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,10 +21,10 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70202974273640
|
25
25
|
- !ruby/object:Gem::Dependency
|
26
26
|
name: redis-namespace
|
27
|
-
requirement: &
|
27
|
+
requirement: &70202974273200 !ruby/object:Gem::Requirement
|
28
28
|
none: false
|
29
29
|
requirements:
|
30
30
|
- - ! '>='
|
@@ -32,10 +32,10 @@ dependencies:
|
|
32
32
|
version: '0'
|
33
33
|
type: :runtime
|
34
34
|
prerelease: false
|
35
|
-
version_requirements: *
|
35
|
+
version_requirements: *70202974273200
|
36
36
|
- !ruby/object:Gem::Dependency
|
37
37
|
name: multi_json
|
38
|
-
requirement: &
|
38
|
+
requirement: &70202974272780 !ruby/object:Gem::Requirement
|
39
39
|
none: false
|
40
40
|
requirements:
|
41
41
|
- - ! '>='
|
@@ -43,10 +43,10 @@ dependencies:
|
|
43
43
|
version: '0'
|
44
44
|
type: :runtime
|
45
45
|
prerelease: false
|
46
|
-
version_requirements: *
|
46
|
+
version_requirements: *70202974272780
|
47
47
|
- !ruby/object:Gem::Dependency
|
48
48
|
name: sinatra
|
49
|
-
requirement: &
|
49
|
+
requirement: &70202974272360 !ruby/object:Gem::Requirement
|
50
50
|
none: false
|
51
51
|
requirements:
|
52
52
|
- - ! '>='
|
@@ -54,10 +54,10 @@ dependencies:
|
|
54
54
|
version: '0'
|
55
55
|
type: :runtime
|
56
56
|
prerelease: false
|
57
|
-
version_requirements: *
|
57
|
+
version_requirements: *70202974272360
|
58
58
|
- !ruby/object:Gem::Dependency
|
59
59
|
name: rake
|
60
|
-
requirement: &
|
60
|
+
requirement: &70202974271940 !ruby/object:Gem::Requirement
|
61
61
|
none: false
|
62
62
|
requirements:
|
63
63
|
- - ! '>='
|
@@ -65,10 +65,10 @@ dependencies:
|
|
65
65
|
version: '0'
|
66
66
|
type: :development
|
67
67
|
prerelease: false
|
68
|
-
version_requirements: *
|
68
|
+
version_requirements: *70202974271940
|
69
69
|
- !ruby/object:Gem::Dependency
|
70
70
|
name: rspec
|
71
|
-
requirement: &
|
71
|
+
requirement: &70202974271520 !ruby/object:Gem::Requirement
|
72
72
|
none: false
|
73
73
|
requirements:
|
74
74
|
- - ! '>='
|
@@ -76,7 +76,7 @@ dependencies:
|
|
76
76
|
version: '0'
|
77
77
|
type: :development
|
78
78
|
prerelease: false
|
79
|
-
version_requirements: *
|
79
|
+
version_requirements: *70202974271520
|
80
80
|
description: Redis Failover provides a full automatic master/slave failover solution
|
81
81
|
for Ruby
|
82
82
|
email:
|
@@ -87,6 +87,7 @@ extensions: []
|
|
87
87
|
extra_rdoc_files: []
|
88
88
|
files:
|
89
89
|
- .gitignore
|
90
|
+
- .travis.yml
|
90
91
|
- Changes.md
|
91
92
|
- Gemfile
|
92
93
|
- LICENSE
|
@@ -128,7 +129,7 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
128
129
|
version: '0'
|
129
130
|
segments:
|
130
131
|
- 0
|
131
|
-
hash: -
|
132
|
+
hash: -1781006209108187327
|
132
133
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
133
134
|
none: false
|
134
135
|
requirements:
|
@@ -137,7 +138,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
137
138
|
version: '0'
|
138
139
|
segments:
|
139
140
|
- 0
|
140
|
-
hash: -
|
141
|
+
hash: -1781006209108187327
|
141
142
|
requirements: []
|
142
143
|
rubyforge_project:
|
143
144
|
rubygems_version: 1.8.16
|