raidis 0.1.0 → 0.2.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/README.md +4 -1
- data/lib/raidis/configuration.rb +5 -1
- data/lib/raidis/redis_wrapper.rb +8 -0
- data/lib/raidis/throttle.rb +22 -0
- data/spec/lib/raidis/throttle_spec.rb +66 -0
- data/spec/lib/redis_wrapper_spec.rb +19 -5
- data/spec/spec_helper.rb +1 -0
- metadata +3 -1
data/README.md
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
[](https://travis-ci.org/bukowskis/raidis)
|
2
|
+
|
1
3
|
# Raid-is
|
2
4
|
|
3
5
|
Raidis is yet another failover solution for Redis.
|
@@ -27,7 +29,7 @@ Raidis.redis.class # => Redis::Namespace
|
|
27
29
|
|
28
30
|
# How does it work?
|
29
31
|
|
30
|
-
Whenever you call `Raidis.redis`, the connectivity to the remote redis server is monitored. If connectivity problems occur, or you're trying to make write-calls to a Redis slave, Raidis will immediately reload the `/etc/redis_master` file and try to perform the call to Redis again (hoping to have connected to a working redis server this time). If that second attempt failed (or you set `config.retries` to `0`) a `Raidis::ConnectionError` is raised.
|
32
|
+
Whenever you call `Raidis.redis`, the connectivity to the remote redis server is monitored. If connectivity problems occur, or you're trying to make write-calls to a Redis slave, Raidis will immediately reload the `/etc/redis_master` file and try to perform the call to Redis again (hoping to have connected to a working redis server this time). If that second attempt failed (or you set `config.retries` to `0`) a `Raidis::ConnectionError` is raised. To prevent too many retries in too short time, there is a minimum interval that Radis will wait after every retry attempt (see config `retry_interval`, default is 3 seconds).
|
31
33
|
|
32
34
|
You should not have too many retries, because you don't want the end user's browser to hang and wait too long. That's where the `#available?` feature comes in. As soon as one of those connection errors occurs, the global variable `Raidis.available?` turns from `true` to `false` and any further damage can be mitigated, by simply not making any further calls to redis. You should inform your end-users about the outage in a friendly manner. E.g. like so:
|
33
35
|
|
@@ -60,6 +62,7 @@ Raidis.configure do |config|
|
|
60
62
|
config.redis_timeout = 3 # seconds # default is whatever Redis.new has as default
|
61
63
|
|
62
64
|
config.retries = 3 # times # default is 1
|
65
|
+
config.retry_interval = 3 # seconds # default is 3
|
63
66
|
config.unavailability_timeout = 60 # seconds # default is 15 seconds
|
64
67
|
config.info_file_path = '/opt/redis_server' # default is '/etc/redis_master'
|
65
68
|
|
data/lib/raidis/configuration.rb
CHANGED
@@ -13,7 +13,7 @@ module Raidis
|
|
13
13
|
end
|
14
14
|
|
15
15
|
attr_accessor :redis_namespace, :redis_timeout, :redis_db
|
16
|
-
attr_writer :logger, :unavailability_timeout, :master, :retries
|
16
|
+
attr_writer :logger, :unavailability_timeout, :master, :retries, :retry_interval
|
17
17
|
|
18
18
|
def logger
|
19
19
|
@logger ||= begin
|
@@ -41,6 +41,10 @@ module Raidis
|
|
41
41
|
@retries ||= 1
|
42
42
|
end
|
43
43
|
|
44
|
+
def retry_interval
|
45
|
+
@retry_interval ||= 3
|
46
|
+
end
|
47
|
+
|
44
48
|
def master
|
45
49
|
unless @master
|
46
50
|
unless info_file_path.exist?
|
data/lib/raidis/redis_wrapper.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
require 'raidis/throttle'
|
2
|
+
|
1
3
|
module Raidis
|
2
4
|
class RedisWrapper
|
3
5
|
|
@@ -24,7 +26,9 @@ module Raidis
|
|
24
26
|
result = block.call
|
25
27
|
rescue Raidis::ConnectionError => exception
|
26
28
|
# Try again a couple of times.
|
29
|
+
throttle.sleep_if_needed
|
27
30
|
if (tries -= 1) >= 0
|
31
|
+
throttle.action!
|
28
32
|
reconnect!
|
29
33
|
retry
|
30
34
|
end
|
@@ -81,6 +85,10 @@ module Raidis
|
|
81
85
|
@redis = nil
|
82
86
|
end
|
83
87
|
|
88
|
+
def throttle
|
89
|
+
@throttle ||= Throttle.new config.retry_interval
|
90
|
+
end
|
91
|
+
|
84
92
|
# Internal: Establishes a brand-new, raw connection to Redis.
|
85
93
|
#
|
86
94
|
# Returns a Redis::Namespace instance or nil if we don't know where the Redis server is.
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module Raidis
|
2
|
+
class Throttle
|
3
|
+
|
4
|
+
attr_reader :interval, :last_action
|
5
|
+
|
6
|
+
def initialize(seconds = 3)
|
7
|
+
@interval = seconds.to_i
|
8
|
+
end
|
9
|
+
|
10
|
+
def sleep_if_needed
|
11
|
+
return unless last_action
|
12
|
+
duration = last_action + interval - Time.now
|
13
|
+
return unless duration.to_i > 0
|
14
|
+
sleep duration
|
15
|
+
end
|
16
|
+
|
17
|
+
def action!
|
18
|
+
@last_action = Time.now
|
19
|
+
end
|
20
|
+
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,66 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
|
3
|
+
describe Raidis::Throttle do
|
4
|
+
|
5
|
+
let(:interval) { 3 }
|
6
|
+
let(:throttle) { Raidis::Throttle.new }
|
7
|
+
|
8
|
+
before do
|
9
|
+
Raidis::Throttle.stub!(:sleep)
|
10
|
+
end
|
11
|
+
|
12
|
+
context 'no action took place yet' do
|
13
|
+
describe '#sleep_if_needed' do
|
14
|
+
it 'does not sleep' do
|
15
|
+
throttle.should_not_receive(:sleep)
|
16
|
+
throttle.sleep_if_needed
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
context 'action took place 1 second ago' do
|
22
|
+
before do
|
23
|
+
Timecop.freeze
|
24
|
+
throttle.action!
|
25
|
+
Timecop.freeze Time.now + 1
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#sleep_if_needed' do
|
29
|
+
it 'sleeps for 2 seconds' do
|
30
|
+
throttle.should_receive(:sleep).with(2)
|
31
|
+
throttle.sleep_if_needed
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'action took place 2 seconds ago' do
|
37
|
+
before do
|
38
|
+
Timecop.freeze
|
39
|
+
throttle.action!
|
40
|
+
Timecop.freeze Time.now + 2
|
41
|
+
end
|
42
|
+
|
43
|
+
describe '#sleep_if_needed' do
|
44
|
+
it 'sleeps for 1 seconds' do
|
45
|
+
throttle.should_receive(:sleep).with(1)
|
46
|
+
throttle.sleep_if_needed
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
context 'action took place 4 seconds ago' do
|
52
|
+
before do
|
53
|
+
Timecop.freeze
|
54
|
+
throttle.action!
|
55
|
+
Timecop.freeze Time.now + 4
|
56
|
+
end
|
57
|
+
|
58
|
+
describe '#sleep_if_needed' do
|
59
|
+
it 'does not sleep' do
|
60
|
+
throttle.should_not_receive(:sleep)
|
61
|
+
throttle.sleep_if_needed
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
65
|
+
|
66
|
+
end
|
@@ -21,7 +21,7 @@ end
|
|
21
21
|
|
22
22
|
describe Raidis::RedisWrapper do
|
23
23
|
|
24
|
-
let(:config) { mock(:config, retries: 3) }
|
24
|
+
let(:config) { mock(:config, retries: 3, retry_interval: 0) }
|
25
25
|
let(:backend) { mock(:backend) }
|
26
26
|
let(:shaky_backend) { ShakyRedis.new }
|
27
27
|
let(:wrapper) { Raidis::RedisWrapper.new }
|
@@ -71,13 +71,27 @@ describe Raidis::RedisWrapper do
|
|
71
71
|
wrapper.some_redis_command.should == :shaky_result
|
72
72
|
end
|
73
73
|
|
74
|
-
context '
|
74
|
+
context 'with a retry interval' do
|
75
75
|
before do
|
76
|
-
config.stub!(:
|
76
|
+
config.stub!(:retry_interval).and_return 10
|
77
77
|
end
|
78
78
|
|
79
|
-
it '
|
80
|
-
|
79
|
+
it 'waits some time before each retry' do
|
80
|
+
wrapper.throttle.should_receive(:sleep).exactly(2).times.with(10)
|
81
|
+
Timecop.freeze
|
82
|
+
wrapper.some_redis_command.should == :shaky_result
|
83
|
+
end
|
84
|
+
|
85
|
+
context 'when running out of retries' do
|
86
|
+
before do
|
87
|
+
config.stub!(:retries).and_return 1
|
88
|
+
end
|
89
|
+
|
90
|
+
it 'finally raises a unified connection error' do
|
91
|
+
wrapper.throttle.should_receive(:sleep).with(10)
|
92
|
+
Timecop.freeze
|
93
|
+
expect { wrapper.some_redis_command }.to raise_error(Raidis::ConnectionError)
|
94
|
+
end
|
81
95
|
end
|
82
96
|
end
|
83
97
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -34,6 +34,7 @@ RSpec.configure do |config|
|
|
34
34
|
Raidis.configure do |config|
|
35
35
|
config.redis_db = 15
|
36
36
|
config.redis_timeout = 0.5
|
37
|
+
config.retry_interval = 0
|
37
38
|
end
|
38
39
|
Raidis.config.stub!(:info_file_path).and_return mock(:info_file_path, exist?: true, readable?: true, read: '127.0.0.1')
|
39
40
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: raidis
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.2.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -100,7 +100,9 @@ files:
|
|
100
100
|
- lib/raidis/availability.rb
|
101
101
|
- lib/raidis/configuration.rb
|
102
102
|
- lib/raidis/redis_wrapper.rb
|
103
|
+
- lib/raidis/throttle.rb
|
103
104
|
- lib/raidis.rb
|
105
|
+
- spec/lib/raidis/throttle_spec.rb
|
104
106
|
- spec/lib/raidis_spec.rb
|
105
107
|
- spec/lib/redis_wrapper_spec.rb
|
106
108
|
- spec/spec_helper.rb
|