redisent 0.0.1 → 0.1.0.alpha
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.
- checksums.yaml +7 -0
- data/.gems +2 -0
- data/{README → README.md} +8 -0
- data/lib/redisent.rb +137 -12
- data/makefile +4 -0
- data/redisent.gemspec +14 -0
- data/test/all.rb +95 -0
- data/test/helper.rb +1 -0
- metadata +30 -22
- data/test/redisent_test.rb +0 -38
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: a94d9eca24c1582cf8ed550514a0a4d4e9b5d3fa
|
4
|
+
data.tar.gz: 8901e7d03da59f40b9e377ac87fd456819fd3120
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 3621af7ae4f480b177a0f3c4c221f754fef2cb574939ed308f2655a1ad725475f7bcba0e69b46ce284a3d4424808d35fa754eae5047cb55c1d3a982f69900d19
|
7
|
+
data.tar.gz: 8a2478cbbd51e01196eae3c22d78d73fc90ff3e1774977f6b5ddd5a6bf63388551496e8dacb4a5c02ff0cb23a08342cef422d6894018e2cc1014ed4726881d22
|
data/.gems
ADDED
data/{README → README.md}
RENAMED
@@ -35,6 +35,14 @@ redis = Redisent.new(sentinels, master, options)
|
|
35
35
|
If the sentinels can't be reached, or if there is no master available,
|
36
36
|
you will get the exception `Redis::CannotConnectError`.
|
37
37
|
|
38
|
+
## Failover
|
39
|
+
|
40
|
+
In case of a failover, it is important that the clients don't engage
|
41
|
+
with the failed master even if it's restored. For that reason, clients
|
42
|
+
must connect to the Redis sentinels in order to get the address of the
|
43
|
+
promoted master, and the way to accomplish that is by using
|
44
|
+
Redisent.new each time a reconnection is needed.
|
45
|
+
|
38
46
|
## Installation
|
39
47
|
|
40
48
|
You can install it using rubygems:
|
data/lib/redisent.rb
CHANGED
@@ -1,23 +1,148 @@
|
|
1
|
-
require "
|
1
|
+
require "redic"
|
2
2
|
|
3
|
-
|
4
|
-
|
5
|
-
|
3
|
+
class Redisent
|
4
|
+
class UnreachableHosts < ArgumentError; end
|
5
|
+
class UnknownMaster < ArgumentError; end
|
6
|
+
|
7
|
+
ECONN = [
|
8
|
+
Errno::ECONNREFUSED,
|
9
|
+
Errno::EINVAL,
|
10
|
+
]
|
11
|
+
|
12
|
+
attr_reader :hosts
|
13
|
+
attr_reader :healthy
|
14
|
+
attr_reader :invalid
|
15
|
+
attr_reader :unknown
|
16
|
+
attr_reader :prime
|
17
|
+
attr_reader :scout
|
18
|
+
|
19
|
+
def initialize(hosts:, name:, client: Redic, auth: nil)
|
20
|
+
@name = name
|
21
|
+
@auth = auth
|
22
|
+
|
23
|
+
# Client library
|
24
|
+
@client = client
|
25
|
+
|
26
|
+
# Hosts according to availability
|
27
|
+
@healthy = []
|
28
|
+
@invalid = []
|
29
|
+
@unknown = []
|
30
|
+
|
31
|
+
# Last known healthy hosts
|
32
|
+
@hosts = hosts
|
33
|
+
|
34
|
+
# Primary client
|
35
|
+
@prime = @client.new
|
36
|
+
|
37
|
+
# Scout client
|
38
|
+
@scout = @client.new
|
39
|
+
|
40
|
+
explore!
|
41
|
+
end
|
42
|
+
|
43
|
+
def url
|
44
|
+
@prime.url
|
45
|
+
end
|
46
|
+
|
47
|
+
private def explore!
|
48
|
+
@unknown = []
|
49
|
+
@invalid = []
|
50
|
+
@healthy = []
|
51
|
+
|
52
|
+
@hosts.each do |host|
|
6
53
|
begin
|
7
|
-
|
8
|
-
|
9
|
-
|
54
|
+
@scout.configure(sentinel_url(host))
|
55
|
+
|
56
|
+
sentinels = @scout.call("SENTINEL", "sentinels", @name)
|
57
|
+
|
58
|
+
if RuntimeError === sentinels
|
59
|
+
unknown.push(host)
|
60
|
+
else
|
61
|
+
healthy.push(host)
|
62
|
+
|
63
|
+
sentinels.each do |sentinel|
|
64
|
+
info = Hash[*sentinel]
|
65
|
+
|
66
|
+
healthy.push(sprintf("%s:%s", info["ip"], info["port"]))
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
@scout.quit
|
71
|
+
|
72
|
+
rescue *ECONN
|
73
|
+
invalid.push(host)
|
10
74
|
end
|
11
75
|
end
|
12
76
|
|
13
|
-
|
77
|
+
if healthy.any?
|
78
|
+
@hosts.replace(healthy)
|
79
|
+
@prime.configure(master)
|
80
|
+
return true
|
81
|
+
end
|
82
|
+
|
83
|
+
if invalid.any?
|
84
|
+
raise UnreachableHosts, invalid
|
85
|
+
end
|
86
|
+
|
87
|
+
if unknown.any?
|
88
|
+
raise UnknownMaster, @name
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def call(*args)
|
93
|
+
forward do
|
94
|
+
@prime.call(*args)
|
95
|
+
end
|
96
|
+
end
|
97
|
+
|
98
|
+
def call!(*args)
|
99
|
+
forward do
|
100
|
+
@prime.call!(*args)
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def queue(*args)
|
105
|
+
@prime.queue(*args)
|
14
106
|
end
|
15
107
|
|
16
|
-
def
|
17
|
-
|
108
|
+
def commit
|
109
|
+
buffer = @prime.buffer
|
18
110
|
|
19
|
-
|
111
|
+
forward do
|
112
|
+
@prime.buffer.replace(buffer)
|
113
|
+
@prime.commit
|
114
|
+
end
|
115
|
+
end
|
20
116
|
|
21
|
-
|
117
|
+
def forward
|
118
|
+
yield
|
119
|
+
rescue
|
120
|
+
explore!
|
121
|
+
retry
|
122
|
+
end
|
123
|
+
|
124
|
+
private def sentinel_url(host)
|
125
|
+
sprintf("redis://%s", host)
|
126
|
+
end
|
127
|
+
|
128
|
+
private def redis_url(host)
|
129
|
+
if @auth then
|
130
|
+
sprintf("redis://:%s@%s", @auth, host)
|
131
|
+
else
|
132
|
+
sprintf("redis://%s", host)
|
133
|
+
end
|
134
|
+
end
|
135
|
+
|
136
|
+
private def master
|
137
|
+
hosts.each do |host|
|
138
|
+
begin
|
139
|
+
@scout.configure(sentinel_url(host))
|
140
|
+
ip, port = @scout.call("SENTINEL", "get-master-addr-by-name", @name)
|
141
|
+
|
142
|
+
break redis_url(sprintf("%s:%s", ip, port))
|
143
|
+
rescue *ECONN
|
144
|
+
$stderr.puts($!.inspect)
|
145
|
+
end
|
146
|
+
end
|
22
147
|
end
|
23
148
|
end
|
data/makefile
ADDED
data/redisent.gemspec
ADDED
@@ -0,0 +1,14 @@
|
|
1
|
+
Gem::Specification.new do |s|
|
2
|
+
s.name = "redisent"
|
3
|
+
s.version = "0.1.0.alpha"
|
4
|
+
s.summary = "Sentinel aware Redis client."
|
5
|
+
s.description = "Redisent is a wrapper for the Redis client that fetches configuration details from sentinels."
|
6
|
+
s.authors = ["Michel Martens"]
|
7
|
+
s.email = ["michel@soveran.com"]
|
8
|
+
s.homepage = "https://github.com/soveran/redisent"
|
9
|
+
s.files = `git ls-files`.split("\n")
|
10
|
+
s.license = "MIT"
|
11
|
+
|
12
|
+
s.add_dependency "redic", "~> 1.5"
|
13
|
+
s.add_development_dependency "cutest", "~> 0"
|
14
|
+
end
|
data/test/all.rb
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
require_relative "helper"
|
2
|
+
|
3
|
+
require "stringio"
|
4
|
+
|
5
|
+
module Silencer
|
6
|
+
@output = nil
|
7
|
+
|
8
|
+
def self.start
|
9
|
+
$olderr = $stderr
|
10
|
+
$stderr = StringIO.new
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.stop
|
14
|
+
@output = $stderr.string
|
15
|
+
$stderr = $olderr
|
16
|
+
end
|
17
|
+
|
18
|
+
def self.output
|
19
|
+
@output
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
SENTINEL_HOSTS = [
|
24
|
+
"127.0.0.1:27000",
|
25
|
+
"127.0.0.1:27001",
|
26
|
+
"127.0.0.1:27002",
|
27
|
+
"127.0.0.1:27003",
|
28
|
+
"127.0.0.1:27004",
|
29
|
+
]
|
30
|
+
|
31
|
+
SENTINEL_BAD_HOSTS = SENTINEL_HOSTS[0,1]
|
32
|
+
SENTINEL_GOOD_HOSTS = SENTINEL_HOSTS[1,4]
|
33
|
+
|
34
|
+
prepare do
|
35
|
+
c = Redic.new
|
36
|
+
|
37
|
+
SENTINEL_GOOD_HOSTS.each do |host|
|
38
|
+
c.configure(sprintf("redis://%s", host))
|
39
|
+
|
40
|
+
c.call("SENTINEL", "monitor", "master-6379", "127.0.0.1", "6379", "3")
|
41
|
+
c.call("QUIT")
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
setup do
|
46
|
+
Redic.new.tap do |c|
|
47
|
+
c.call("FLUSHDB")
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
test "invalid hosts" do
|
52
|
+
assert_raise(Redisent::UnreachableHosts) do
|
53
|
+
Redisent.new(hosts: SENTINEL_BAD_HOSTS, name: "master-6379")
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
test "invalid master" do
|
58
|
+
assert_raise(Redisent::UnknownMaster) do
|
59
|
+
Redisent.new(hosts: SENTINEL_GOOD_HOSTS, name: "master-6380")
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
setup do
|
64
|
+
Redisent.new(hosts: SENTINEL_GOOD_HOSTS, name: "master-6379")
|
65
|
+
end
|
66
|
+
|
67
|
+
test "call" do |c|
|
68
|
+
assert_equal "PONG", c.call("PING")
|
69
|
+
end
|
70
|
+
|
71
|
+
test "call!" do |c|
|
72
|
+
assert_equal "PONG", c.call("PING")
|
73
|
+
end
|
74
|
+
|
75
|
+
test "queue/commit" do |c|
|
76
|
+
assert_equal [["PING"]], c.queue("PING")
|
77
|
+
assert_equal [["PING"], ["PING"]], c.queue("PING")
|
78
|
+
assert_equal ["PONG", "PONG"], c.commit
|
79
|
+
end
|
80
|
+
|
81
|
+
test "retry on connection failures" do |c|
|
82
|
+
assert_equal "PONG", c.call("PING")
|
83
|
+
|
84
|
+
# Simulate a server disconnection.
|
85
|
+
c.prime.configure(sprintf("redis://%s", SENTINEL_BAD_HOSTS.first))
|
86
|
+
|
87
|
+
assert_equal "PONG", c.call("PING")
|
88
|
+
|
89
|
+
# Simulate a server disconnection.
|
90
|
+
c.prime.configure(sprintf("redis://%s", SENTINEL_BAD_HOSTS.first))
|
91
|
+
|
92
|
+
assert_equal [["PING"]], c.queue("PING")
|
93
|
+
assert_equal [["PING"], ["PING"]], c.queue("PING")
|
94
|
+
assert_equal ["PONG", "PONG"], c.commit
|
95
|
+
end
|
data/test/helper.rb
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
require_relative "../lib/redisent"
|
metadata
CHANGED
@@ -1,38 +1,43 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: redisent
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
5
|
-
prerelease:
|
4
|
+
version: 0.1.0.alpha
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Michel Martens
|
9
8
|
autorequire:
|
10
9
|
bindir: bin
|
11
10
|
cert_chain: []
|
12
|
-
date:
|
11
|
+
date: 2017-01-23 00:00:00.000000000 Z
|
13
12
|
dependencies:
|
14
13
|
- !ruby/object:Gem::Dependency
|
15
|
-
name:
|
16
|
-
requirement:
|
17
|
-
none: false
|
14
|
+
name: redic
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
18
16
|
requirements:
|
19
|
-
- -
|
17
|
+
- - "~>"
|
20
18
|
- !ruby/object:Gem::Version
|
21
|
-
version: '
|
19
|
+
version: '1.5'
|
22
20
|
type: :runtime
|
23
21
|
prerelease: false
|
24
|
-
version_requirements:
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
23
|
+
requirements:
|
24
|
+
- - "~>"
|
25
|
+
- !ruby/object:Gem::Version
|
26
|
+
version: '1.5'
|
25
27
|
- !ruby/object:Gem::Dependency
|
26
28
|
name: cutest
|
27
|
-
requirement:
|
28
|
-
none: false
|
29
|
+
requirement: !ruby/object:Gem::Requirement
|
29
30
|
requirements:
|
30
|
-
- -
|
31
|
+
- - "~>"
|
31
32
|
- !ruby/object:Gem::Version
|
32
33
|
version: '0'
|
33
34
|
type: :development
|
34
35
|
prerelease: false
|
35
|
-
version_requirements:
|
36
|
+
version_requirements: !ruby/object:Gem::Requirement
|
37
|
+
requirements:
|
38
|
+
- - "~>"
|
39
|
+
- !ruby/object:Gem::Version
|
40
|
+
version: '0'
|
36
41
|
description: Redisent is a wrapper for the Redis client that fetches configuration
|
37
42
|
details from sentinels.
|
38
43
|
email:
|
@@ -41,33 +46,36 @@ executables: []
|
|
41
46
|
extensions: []
|
42
47
|
extra_rdoc_files: []
|
43
48
|
files:
|
49
|
+
- ".gems"
|
44
50
|
- LICENSE
|
45
|
-
- README
|
51
|
+
- README.md
|
46
52
|
- lib/redisent.rb
|
47
|
-
-
|
53
|
+
- makefile
|
54
|
+
- redisent.gemspec
|
55
|
+
- test/all.rb
|
56
|
+
- test/helper.rb
|
48
57
|
homepage: https://github.com/soveran/redisent
|
49
58
|
licenses:
|
50
59
|
- MIT
|
60
|
+
metadata: {}
|
51
61
|
post_install_message:
|
52
62
|
rdoc_options: []
|
53
63
|
require_paths:
|
54
64
|
- lib
|
55
65
|
required_ruby_version: !ruby/object:Gem::Requirement
|
56
|
-
none: false
|
57
66
|
requirements:
|
58
|
-
- -
|
67
|
+
- - ">="
|
59
68
|
- !ruby/object:Gem::Version
|
60
69
|
version: '0'
|
61
70
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
62
|
-
none: false
|
63
71
|
requirements:
|
64
|
-
- -
|
72
|
+
- - ">"
|
65
73
|
- !ruby/object:Gem::Version
|
66
|
-
version:
|
74
|
+
version: 1.3.1
|
67
75
|
requirements: []
|
68
76
|
rubyforge_project:
|
69
|
-
rubygems_version:
|
77
|
+
rubygems_version: 2.4.5.1
|
70
78
|
signing_key:
|
71
|
-
specification_version:
|
79
|
+
specification_version: 4
|
72
80
|
summary: Sentinel aware Redis client.
|
73
81
|
test_files: []
|
data/test/redisent_test.rb
DELETED
@@ -1,38 +0,0 @@
|
|
1
|
-
require File.expand_path("../lib/redisent", File.dirname(__FILE__))
|
2
|
-
|
3
|
-
test "basics" do |redis|
|
4
|
-
redis = Redisent.new(
|
5
|
-
["redis://localhost:27378/",
|
6
|
-
"redis://localhost:27379/",
|
7
|
-
"redis://localhost:27380/",
|
8
|
-
"redis://localhost:27381/"],
|
9
|
-
"server-1", :timeout => 5)
|
10
|
-
|
11
|
-
redis.set("foo", 1)
|
12
|
-
|
13
|
-
assert_equal "1", redis.get("foo")
|
14
|
-
assert_equal "6379", redis.info["tcp_port"]
|
15
|
-
assert_equal 5.0, redis.client.timeout
|
16
|
-
end
|
17
|
-
|
18
|
-
test "no available sentinel" do
|
19
|
-
assert_raise Redis::CannotConnectError do
|
20
|
-
redis = Redisent.new(
|
21
|
-
["redis://localhost:27478/",
|
22
|
-
"redis://localhost:27479/",
|
23
|
-
"redis://localhost:27480/",
|
24
|
-
"redis://localhost:27481/"],
|
25
|
-
"server-1")
|
26
|
-
end
|
27
|
-
end
|
28
|
-
|
29
|
-
test "no available master" do
|
30
|
-
assert_raise Redis::CannotConnectError do
|
31
|
-
redis = Redisent.new(
|
32
|
-
["redis://localhost:27478/",
|
33
|
-
"redis://localhost:27479/",
|
34
|
-
"redis://localhost:27480/",
|
35
|
-
"redis://localhost:27481/"],
|
36
|
-
"server-2")
|
37
|
-
end
|
38
|
-
end
|