red_cluster 0.0.1 → 0.0.2
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/lib/red_cluster.rb +31 -23
- data/spec/red_cluster_spec.rb +2 -22
- data/spec/replica_sets_spec.rb +74 -86
- metadata +3 -3
data/lib/red_cluster.rb
CHANGED
@@ -10,20 +10,6 @@ class RedCluster
|
|
10
10
|
@replica_sets = replica_sets.map { |replica_set| ReplicaSet.new(self, replica_set) }
|
11
11
|
end
|
12
12
|
|
13
|
-
def load_aof_file(file_path)
|
14
|
-
aof_file = File.read file_path
|
15
|
-
commands = aof_file.split /^\*/
|
16
|
-
commands.each do |cmd|
|
17
|
-
split_cmd = cmd.split("\r\n")[1..-1]
|
18
|
-
next unless split_cmd
|
19
|
-
split_cmd.reject! { |cmd| cmd =~ /^\$/ }
|
20
|
-
redis_cmd, redis_key, redis_args = split_cmd[0], split_cmd[1], split_cmd[2..-1]
|
21
|
-
crc32_of_key = Zlib.crc32(redis_key).abs
|
22
|
-
replica_set = @replica_sets[crc32_of_key % @replica_sets.size]
|
23
|
-
replica_set.master.send redis_cmd, redis_key, *redis_args
|
24
|
-
end
|
25
|
-
end
|
26
|
-
|
27
13
|
SINGLE_KEY_KEY_OPS = %W{del exists expire expireat move persists ttl type}.map(&:to_sym)
|
28
14
|
STRING_OPS = %W{append decr decrby get getbit getrange getset incr incrby mget mset msetnx set setbit setex setnx setrange strlen}.map(&:to_sym)
|
29
15
|
HASH_OPS = %W{hdel hexists hget hgetall hincrby hkeys hlen hmget hmset hset hsetnx hvals}.map(&:to_sym)
|
@@ -33,15 +19,19 @@ class RedCluster
|
|
33
19
|
SINGLE_KEY_OPS = SINGLE_KEY_KEY_OPS + STRING_OPS + HASH_OPS + SINGLE_KEY_LIST_OPS + SINGLE_KEY_SET_OPS + SINGLE_KEY_SORTED_SET_OPS
|
34
20
|
|
35
21
|
# Server Ops
|
36
|
-
def select(db); @replica_sets.each {|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
22
|
+
def select(db); @replica_sets.each {|replica_set| replica_set.select(db) }; "OK"; end
|
23
|
+
|
24
|
+
[:flushdb, :shutdown, :flushall, :quit, :multi].each do |method|
|
25
|
+
define_method method do
|
26
|
+
perform_across_all_replica_sets method
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
42
30
|
def ping; @replica_sets.each(&:ping); "PONG"; end
|
43
|
-
def keys(pattern); @replica_sets.map { |server| server.keys pattern }.flatten; end
|
44
31
|
def bgsave; @replica_sets.each(&:bgsave); "Background saving started"; end
|
32
|
+
def echo(msg); @replica_sets.each {|replica_set| replica_set.echo(msg) }; msg; end
|
33
|
+
|
34
|
+
def keys(pattern); @replica_sets.map { |replica_set| replica_set.keys pattern }.flatten; end
|
45
35
|
def lastsave; @replica_sets.map(&:lastsave).min; end
|
46
36
|
|
47
37
|
def config(cmd, *args)
|
@@ -54,8 +44,6 @@ class RedCluster
|
|
54
44
|
end
|
55
45
|
|
56
46
|
# Transaction Ops
|
57
|
-
def multi; @replica_sets.each(&:multi); end
|
58
|
-
|
59
47
|
def exec
|
60
48
|
@multi_count = nil
|
61
49
|
exec_results = @replica_sets.map(&:exec)
|
@@ -152,7 +140,27 @@ class RedCluster
|
|
152
140
|
end
|
153
141
|
end
|
154
142
|
|
143
|
+
def load_aof_file(file_path)
|
144
|
+
aof_file = File.read file_path
|
145
|
+
commands = aof_file.split /^\*/
|
146
|
+
commands.each do |cmd|
|
147
|
+
split_cmd = cmd.split("\r\n")[1..-1]
|
148
|
+
next unless split_cmd
|
149
|
+
split_cmd.reject! { |cmd| cmd =~ /^\$/ }
|
150
|
+
redis_cmd, redis_key, redis_args = split_cmd[0], split_cmd[1], split_cmd[2..-1]
|
151
|
+
crc32_of_key = Zlib.crc32(redis_key).abs
|
152
|
+
replica_set = @replica_sets[crc32_of_key % @replica_sets.size]
|
153
|
+
replica_set.master.send redis_cmd, redis_key, *redis_args
|
154
|
+
end
|
155
|
+
end
|
156
|
+
|
155
157
|
private
|
158
|
+
|
159
|
+
def perform_across_all_replica_sets(op, return_msg = 'OK')
|
160
|
+
@replica_sets.each { |replica_set| replica_set.send op }
|
161
|
+
return_msg
|
162
|
+
end
|
163
|
+
|
156
164
|
def replica_set_for_key(key)
|
157
165
|
@replica_sets[Zlib.crc32(key).abs % @replica_sets.size]
|
158
166
|
end
|
data/spec/red_cluster_spec.rb
CHANGED
@@ -25,26 +25,6 @@ describe RedCluster do
|
|
25
25
|
let(:rc) { @rc }
|
26
26
|
after { rc.flushall }
|
27
27
|
|
28
|
-
it "gets initialized with a bunch of replica sets" do
|
29
|
-
first_replica_set = {
|
30
|
-
:master => {:host => "localhost", :port => 6379},
|
31
|
-
:slaves => [{:host => "localhost", :port => 7379},
|
32
|
-
{:host => "localhost", :port => 8379}]
|
33
|
-
}
|
34
|
-
second_replica_set = {
|
35
|
-
:master => {:host => "localhost", :port => 9379},
|
36
|
-
:slaves => [{:host => "localhost", :port => 10379},
|
37
|
-
{:host => "localhost", :port => 11379}]
|
38
|
-
}
|
39
|
-
third_replica_set = {
|
40
|
-
:master => {:host => "localhost", :port => 12379},
|
41
|
-
:slaves => [{:host => "localhost", :port => 13379},
|
42
|
-
{:host => "localhost", :port => 14379}]
|
43
|
-
}
|
44
|
-
replica_sets = [first_replica_set, second_replica_set, third_replica_set]
|
45
|
-
RedCluster.new replica_sets
|
46
|
-
end
|
47
|
-
|
48
28
|
context "#randomkey", :fast => true do
|
49
29
|
it "returns a random key across the cluster", :fast => true do
|
50
30
|
rc.set "foo", "bar"
|
@@ -250,8 +230,8 @@ describe RedCluster do
|
|
250
230
|
|
251
231
|
context "#echo", :fast => true do
|
252
232
|
it "echo's all replica_sets" do
|
253
|
-
rc.
|
254
|
-
rc.echo("hello")
|
233
|
+
rc.echo('hello').should == 'hello'
|
234
|
+
# rc.replica_sets.each { |rs| rs.should_receive(:echo).with("hello") }
|
255
235
|
end
|
256
236
|
end
|
257
237
|
|
data/spec/replica_sets_spec.rb
CHANGED
@@ -2,117 +2,105 @@ require 'spec_helper'
|
|
2
2
|
require 'replica_set'
|
3
3
|
|
4
4
|
describe RedCluster::ReplicaSet do
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
5
|
+
before(:each) do
|
6
|
+
master = {:host => "localhost", :port => 6379}
|
7
|
+
slaves = [{:host => "localhost", :port => 7379},
|
8
|
+
{:host => "localhost", :port => 8379}]
|
9
|
+
@rs = RedCluster::ReplicaSet.new nil, :master => master, :slaves => slaves
|
10
|
+
end
|
11
|
+
let(:rs) { @rs }
|
12
|
+
let(:master) { @rs.master }
|
13
|
+
let(:slaves) { @rs.slaves }
|
14
|
+
after { master.flushall }
|
15
|
+
|
16
|
+
context "#replication" do
|
17
|
+
it "the slaves are slaveof's master" do
|
18
|
+
slaves.each do |slave|
|
19
|
+
slave.info["role"].should == "slave"
|
20
|
+
slave.info["master_host"].should == master.client.host
|
21
|
+
slave.info["master_port"].should == master.client.port.to_s
|
22
|
+
end
|
11
23
|
end
|
12
24
|
end
|
13
25
|
|
14
|
-
context "
|
26
|
+
context "master dying" do
|
15
27
|
before(:each) do
|
16
|
-
master
|
17
|
-
slaves = [{:host => "localhost", :port => 7379},
|
18
|
-
{:host => "localhost", :port => 8379}]
|
19
|
-
@rs = RedCluster::ReplicaSet.new nil, :master => master, :slaves => slaves
|
28
|
+
master.stubs(:set).raises Errno::ECONNREFUSED
|
20
29
|
end
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
after { master.flushall }
|
25
|
-
|
26
|
-
context "#replication" do
|
27
|
-
it "the slaves are slaveof's master" do
|
28
|
-
slaves.each do |slave|
|
29
|
-
slave.info["role"].should == "slave"
|
30
|
-
slave.info["master_host"].should == master.client.host
|
31
|
-
slave.info["master_port"].should == master.client.port.to_s
|
32
|
-
end
|
30
|
+
context "when it's a read op" do
|
31
|
+
it "things work as though nothing happened" do
|
32
|
+
expect { rs.get("foo") }.to_not raise_error
|
33
33
|
end
|
34
34
|
end
|
35
35
|
|
36
|
-
context "
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
end
|
36
|
+
context "when there are more than one slave" do
|
37
|
+
it "one of them gets promoted to the new master" do
|
38
|
+
old_slaves = slaves.dup
|
39
|
+
old_master = master
|
40
|
+
rs.set("foo", "bar")
|
41
|
+
old_master.should_not == rs.master
|
42
|
+
old_slaves.should include(rs.master)
|
44
43
|
end
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
old_master.should_not == rs.master
|
52
|
-
old_slaves.should include(rs.master)
|
53
|
-
end
|
54
|
-
it "the other's become slaves of the new master" do
|
55
|
-
rs.set("foo", "bar")
|
56
|
-
new_master = rs.master
|
57
|
-
rs.slaves.each do |slave|
|
58
|
-
slave.info["master_host"].should == new_master.client.host
|
59
|
-
slave.info["master_port"].should == new_master.client.port.to_s
|
60
|
-
end
|
44
|
+
it "the other's become slaves of the new master" do
|
45
|
+
rs.set("foo", "bar")
|
46
|
+
new_master = rs.master
|
47
|
+
rs.slaves.each do |slave|
|
48
|
+
slave.info["master_host"].should == new_master.client.host
|
49
|
+
slave.info["master_port"].should == new_master.client.port.to_s
|
61
50
|
end
|
62
51
|
end
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
52
|
+
end
|
53
|
+
context "when there is just one slave" do
|
54
|
+
it "becomes the new master" do
|
55
|
+
slaves.shift while slaves.count > 1
|
56
|
+
slaves.count.should == 1
|
57
|
+
old_slave = slaves[0]
|
58
|
+
rs.set('foo', 'bar')
|
59
|
+
old_slave.should == rs.master
|
71
60
|
end
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
61
|
+
end
|
62
|
+
context "when there are no slaves" do
|
63
|
+
it "a RedCluster::NoMaster exception get's thrown" do
|
64
|
+
slaves.shift while slaves.count > 0
|
65
|
+
expect { rs.set('foo', 'bar') }.to raise_error(RedCluster::NoMaster, "No master in replica set")
|
77
66
|
end
|
78
67
|
end
|
68
|
+
end
|
79
69
|
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
70
|
+
context "#read operations" do
|
71
|
+
it "get forwarded to the slaves on a round-robin basis" do
|
72
|
+
master.expects(:get).never
|
73
|
+
slaves[0].expects(:get).with("some_key").returns "some_val"
|
74
|
+
slaves[1].expects(:get).with("some_key").returns "some_new_val"
|
85
75
|
|
86
|
-
|
87
|
-
|
88
|
-
end
|
76
|
+
rs.get("some_key").should == "some_val"
|
77
|
+
rs.get("some_key").should == "some_new_val"
|
89
78
|
end
|
79
|
+
end
|
90
80
|
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
end
|
81
|
+
context "#write operations" do
|
82
|
+
it "get forwarded to the master" do
|
83
|
+
master.expects(:set)
|
84
|
+
rs.set("foo", "bar")
|
96
85
|
end
|
86
|
+
end
|
97
87
|
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
end
|
88
|
+
context "#blocking operations" do
|
89
|
+
it "raise an error" do
|
90
|
+
expect { rs.blpop("some_list", 0) }.to raise_error
|
102
91
|
end
|
92
|
+
end
|
103
93
|
|
104
|
-
|
105
|
-
|
106
|
-
|
107
|
-
end
|
94
|
+
context "#slaveof operations" do
|
95
|
+
it "raise an error" do
|
96
|
+
expect { rs.slaveof("localhost", 6379) }.to raise_error
|
108
97
|
end
|
98
|
+
end
|
109
99
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
end
|
100
|
+
context "#pub sub operations" do
|
101
|
+
it "raise an error" do
|
102
|
+
expect { rs.blpop("publish", 0) }.to raise_error
|
114
103
|
end
|
115
|
-
|
116
104
|
end
|
117
105
|
end
|
118
106
|
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: red_cluster
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.2
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -13,7 +13,7 @@ date: 2011-10-25 00:00:00.000000000Z
|
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: redis
|
16
|
-
requirement: &
|
16
|
+
requirement: &70306677369620 !ruby/object:Gem::Requirement
|
17
17
|
none: false
|
18
18
|
requirements:
|
19
19
|
- - ! '>='
|
@@ -21,7 +21,7 @@ dependencies:
|
|
21
21
|
version: '0'
|
22
22
|
type: :runtime
|
23
23
|
prerelease: false
|
24
|
-
version_requirements: *
|
24
|
+
version_requirements: *70306677369620
|
25
25
|
description: ! " Red Cluster brings together a set of redis servers and allows
|
26
26
|
you to read and write to them\n as though you were writing to just one. A few
|
27
27
|
of the reasons you might want to consider\n clustering could be:\n\n * Robustness
|