dcell 0.10.0 → 0.12.0.pre
Sign up to get free protection for your applications and to get access to all the features.
- data/CHANGES.md +4 -0
- data/Gemfile +1 -1
- data/README.md +85 -4
- data/benchmarks/messaging.rb +7 -1
- data/benchmarks/receiver.rb +3 -3
- data/dcell.gemspec +3 -3
- data/explorer/index.html.erb +1 -1
- data/lib/dcell.rb +5 -18
- data/lib/dcell/actor_proxy.rb +26 -1
- data/lib/dcell/celluloid_ext.rb +19 -8
- data/lib/dcell/directory.rb +3 -15
- data/lib/dcell/explorer.rb +0 -1
- data/lib/dcell/mailbox_proxy.rb +8 -12
- data/lib/dcell/messages.rb +7 -20
- data/lib/dcell/node.rb +20 -24
- data/lib/dcell/node_manager.rb +2 -34
- data/lib/dcell/registries/cassandra_adapter.rb +32 -0
- data/lib/dcell/registries/moneta_adapter.rb +7 -0
- data/lib/dcell/registries/redis_adapter.rb +31 -0
- data/lib/dcell/registries/zk_adapter.rb +39 -1
- data/lib/dcell/router.rb +0 -22
- data/lib/dcell/server.rb +1 -1
- data/lib/dcell/version.rb +1 -1
- data/spec/dcell/directory_spec.rb +1 -1
- data/spec/dcell/global_spec.rb +0 -4
- data/spec/spec_helper.rb +3 -6
- data/spec/support/registry_examples.rb +18 -0
- data/spec/test_node.rb +3 -7
- metadata +66 -30
- data/lib/dcell/registries/gossip/core.rb +0 -235
- data/lib/dcell/registries/gossip_adapter.rb +0 -26
- data/spec/dcell/registries/gossip_adapter_spec.rb +0 -6
data/lib/dcell/node.rb
CHANGED
@@ -3,33 +3,39 @@ module DCell
|
|
3
3
|
class Node
|
4
4
|
include Celluloid
|
5
5
|
include Celluloid::FSM
|
6
|
-
attr_reader :id, :addr
|
6
|
+
attr_reader :id, :addr
|
7
7
|
|
8
8
|
# FSM
|
9
9
|
default_state :disconnected
|
10
10
|
state :shutdown
|
11
11
|
state :disconnected, :to => [:connected, :shutdown]
|
12
12
|
state :connected do
|
13
|
+
send_heartbeat
|
13
14
|
Celluloid::Logger.info "Connected to #{id}"
|
14
15
|
end
|
15
16
|
state :partitioned do
|
16
17
|
Celluloid::Logger.warn "Communication with #{id} interrupted"
|
17
18
|
end
|
19
|
+
|
20
|
+
@nodes = {}
|
21
|
+
@lock = Mutex.new
|
22
|
+
|
23
|
+
@heartbeat_rate = 5 # How often to send heartbeats in seconds
|
24
|
+
@heartbeat_timeout = 10 # How soon until a lost heartbeat triggers a node partition
|
18
25
|
|
19
26
|
# Singleton methods
|
20
27
|
class << self
|
21
28
|
include Enumerable
|
22
29
|
extend Forwardable
|
23
30
|
|
24
|
-
def_delegators "Celluloid::Actor[:node_manager]", :all, :each, :find, :[]
|
25
|
-
def_delegators "Celluloid::Actor[:node_manager]", :
|
31
|
+
def_delegators "Celluloid::Actor[:node_manager]", :all, :each, :find, :[]
|
32
|
+
def_delegators "Celluloid::Actor[:node_manager]", :heartbeat_rate, :heartbeat_timeout
|
26
33
|
end
|
27
34
|
|
28
35
|
def initialize(id, addr)
|
29
36
|
@id, @addr = id, addr
|
30
|
-
@timestamp = 0
|
31
37
|
@socket = nil
|
32
|
-
@
|
38
|
+
@heartbeat = nil
|
33
39
|
|
34
40
|
# Total hax to accommodate the new Celluloid::FSM API
|
35
41
|
attach self
|
@@ -37,7 +43,6 @@ module DCell
|
|
37
43
|
|
38
44
|
def finalize
|
39
45
|
transition :shutdown
|
40
|
-
@gossip.cancel if @gossip
|
41
46
|
@socket.close if @socket
|
42
47
|
end
|
43
48
|
|
@@ -97,26 +102,17 @@ module DCell
|
|
97
102
|
socket << message
|
98
103
|
end
|
99
104
|
alias_method :<<, :send_message
|
100
|
-
|
101
|
-
|
102
|
-
|
103
|
-
|
104
|
-
|
105
|
-
def fresh?
|
106
|
-
@fresh
|
105
|
+
|
106
|
+
# Send a heartbeat message after the given interval
|
107
|
+
def send_heartbeat
|
108
|
+
send_message DCell::Message::Heartbeat.new
|
109
|
+
@heartbeat = after(self.class.heartbeat_rate) { send_heartbeat }
|
107
110
|
end
|
108
111
|
|
109
|
-
# Handle an incoming
|
110
|
-
def
|
111
|
-
|
112
|
-
|
113
|
-
@timestamp = t
|
114
|
-
transition :connected
|
115
|
-
transition :partitioned, :delay => self.class.heartbeat_timeout
|
116
|
-
unless state == :connected
|
117
|
-
Celluloid::Logger.info "Revived node #{id}"
|
118
|
-
end
|
119
|
-
end
|
112
|
+
# Handle an incoming heartbeat for this node
|
113
|
+
def handle_heartbeat
|
114
|
+
transition :connected
|
115
|
+
transition :partitioned, :delay => self.class.heartbeat_timeout
|
120
116
|
end
|
121
117
|
|
122
118
|
# Friendlier inspection
|
data/lib/dcell/node_manager.rb
CHANGED
@@ -4,15 +4,13 @@ module DCell
|
|
4
4
|
include Celluloid::ZMQ
|
5
5
|
include Enumerable
|
6
6
|
|
7
|
-
attr_reader :
|
7
|
+
attr_reader :heartbeat_rate, :heartbeat_timeout
|
8
8
|
|
9
9
|
def initialize
|
10
10
|
@nodes = {}
|
11
11
|
|
12
|
-
@
|
12
|
+
@heartbeat_rate = 5 # How often to send heartbeats in seconds
|
13
13
|
@heartbeat_timeout = 10 # How soon until a lost heartbeat triggers a node partition
|
14
|
-
each { |node| node.socket if node } # Connect all so we can gossip
|
15
|
-
@gossip = after(gossip_rate) { gossip_timeout }
|
16
14
|
end
|
17
15
|
|
18
16
|
# Return all available nodes in the cluster
|
@@ -47,35 +45,5 @@ module DCell
|
|
47
45
|
@nodes[id]
|
48
46
|
end
|
49
47
|
alias_method :[], :find
|
50
|
-
|
51
|
-
# Send gossip to a random node (except ourself) after the given interval
|
52
|
-
def gossip_timeout
|
53
|
-
nodes = select { |node| node.state == :connected }
|
54
|
-
peer = nodes.select { |node| node.id != DCell.id }.sample(1)[0]
|
55
|
-
if peer
|
56
|
-
nodes = nodes.inject([]) { |a,n| a << [n.id, n.addr, n.timestamp]; a }
|
57
|
-
data = nil
|
58
|
-
if DCell.registry.is_a? Registry::GossipAdapter
|
59
|
-
data = peer.fresh? ? DCell.registry.values : DCell.registry.changed
|
60
|
-
end
|
61
|
-
DCell.me.tick
|
62
|
-
peer.send_message DCell::Message::Gossip.new nodes, data
|
63
|
-
end
|
64
|
-
@gossip = after(gossip_rate) { gossip_timeout }
|
65
|
-
end
|
66
|
-
|
67
|
-
def handle_gossip(peers, data)
|
68
|
-
peers.each do |id, addr, timestamp|
|
69
|
-
if (node = find(id))
|
70
|
-
node.handle_timestamp! timestamp
|
71
|
-
else
|
72
|
-
Directory[id] = addr
|
73
|
-
Celluloid::Logger.info "Found node #{id}"
|
74
|
-
end
|
75
|
-
end
|
76
|
-
if DCell.registry.is_a? Registry::GossipAdapter
|
77
|
-
data.map { |data| DCell.registry.observe data } if data
|
78
|
-
end
|
79
|
-
end
|
80
48
|
end
|
81
49
|
end
|
@@ -44,13 +44,45 @@ module DCell
|
|
44
44
|
|
45
45
|
cass = Cassandra.new(keyspace, options[:servers])
|
46
46
|
|
47
|
+
@node_registry = NodeRegistry.new(cass, columnfamily)
|
47
48
|
@global_registry = GlobalRegistry.new(cass, columnfamily)
|
48
49
|
end
|
49
50
|
|
51
|
+
def clear_nodes
|
52
|
+
@node_registry.clear
|
53
|
+
end
|
54
|
+
|
50
55
|
def clear_globals
|
51
56
|
@global_registry.clear
|
52
57
|
end
|
53
58
|
|
59
|
+
class NodeRegistry
|
60
|
+
def initialize(cass, cf)
|
61
|
+
@cass = cass
|
62
|
+
@cf = cf
|
63
|
+
end
|
64
|
+
|
65
|
+
def get(node_id)
|
66
|
+
@cass.get @cf, "nodes", node_id
|
67
|
+
end
|
68
|
+
|
69
|
+
def set(node_id, addr)
|
70
|
+
@cass.insert @cf, "nodes", { node_id => addr }
|
71
|
+
end
|
72
|
+
|
73
|
+
def nodes
|
74
|
+
@cass.get(@cf, "nodes").keys
|
75
|
+
end
|
76
|
+
|
77
|
+
def clear
|
78
|
+
@cass.del @cf, "nodes"
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
def get_node(node_id); @node_registry.get(node_id) end
|
83
|
+
def set_node(node_id, addr); @node_registry.set(node_id, addr) end
|
84
|
+
def nodes; @node_registry.nodes end
|
85
|
+
|
54
86
|
class GlobalRegistry
|
55
87
|
def initialize(cass, cf)
|
56
88
|
@cass = cass
|
@@ -16,6 +16,7 @@ module DCell
|
|
16
16
|
# @moneta = Moneta::TieredCache.new options
|
17
17
|
@moneta = Moneta::Memory.new options
|
18
18
|
|
19
|
+
@node_registry = Registry.new(@moneta, :nodes)
|
19
20
|
@global_registry = Registry.new(@moneta, :globals)
|
20
21
|
end
|
21
22
|
|
@@ -38,6 +39,7 @@ module DCell
|
|
38
39
|
end
|
39
40
|
|
40
41
|
# DCell registry behaviors
|
42
|
+
alias_method :nodes, :all
|
41
43
|
alias_method :global_keys, :all
|
42
44
|
|
43
45
|
def clear
|
@@ -45,6 +47,11 @@ module DCell
|
|
45
47
|
end
|
46
48
|
end
|
47
49
|
|
50
|
+
def get_node(node_id); @node_registry.get(node_id) end
|
51
|
+
def set_node(node_id, addr); @node_registry.set(node_id, addr) end
|
52
|
+
def nodes; @node_registry.nodes end
|
53
|
+
def clear_nodes; @node_registry.clear end
|
54
|
+
|
48
55
|
def get_global(key); @global_registry.get(key) end
|
49
56
|
def set_global(key, value); @global_registry.set(key, value) end
|
50
57
|
def global_keys; @global_registry.global_keys end
|
@@ -14,13 +14,44 @@ module DCell
|
|
14
14
|
redis = Redis.new options
|
15
15
|
@redis = Redis::Namespace.new @namespace, :redis => redis
|
16
16
|
|
17
|
+
@node_registry = NodeRegistry.new(@redis)
|
17
18
|
@global_registry = GlobalRegistry.new(@redis)
|
18
19
|
end
|
19
20
|
|
21
|
+
def clear_nodes
|
22
|
+
@node_registry.clear
|
23
|
+
end
|
24
|
+
|
20
25
|
def clear_globals
|
21
26
|
@global_registry.clear
|
22
27
|
end
|
23
28
|
|
29
|
+
class NodeRegistry
|
30
|
+
def initialize(redis)
|
31
|
+
@redis = redis
|
32
|
+
end
|
33
|
+
|
34
|
+
def get(node_id)
|
35
|
+
@redis.hget 'nodes', node_id
|
36
|
+
end
|
37
|
+
|
38
|
+
def set(node_id, addr)
|
39
|
+
@redis.hset 'nodes', node_id, addr
|
40
|
+
end
|
41
|
+
|
42
|
+
def nodes
|
43
|
+
@redis.hkeys 'nodes'
|
44
|
+
end
|
45
|
+
|
46
|
+
def clear
|
47
|
+
@redis.del 'nodes'
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def get_node(node_id); @node_registry.get(node_id) end
|
52
|
+
def set_node(node_id, addr); @node_registry.set(node_id, addr) end
|
53
|
+
def nodes; @node_registry.nodes end
|
54
|
+
|
24
55
|
class GlobalRegistry
|
25
56
|
def initialize(redis)
|
26
57
|
@redis = redis
|
@@ -36,13 +36,51 @@ module DCell
|
|
36
36
|
end
|
37
37
|
|
38
38
|
@zk = ZK.new(*servers)
|
39
|
+
@node_registry = NodeRegistry.new(@zk, @base_path)
|
39
40
|
@global_registry = GlobalRegistry.new(@zk, @base_path)
|
40
41
|
end
|
41
42
|
|
43
|
+
def clear_nodes
|
44
|
+
@node_registry.clear
|
45
|
+
end
|
46
|
+
|
42
47
|
def clear_globals
|
43
48
|
@global_registry.clear
|
44
49
|
end
|
45
50
|
|
51
|
+
class NodeRegistry
|
52
|
+
def initialize(zk, base_path)
|
53
|
+
@zk, @base_path = zk, "#{base_path}/nodes"
|
54
|
+
@zk.mkdir_p @base_path
|
55
|
+
end
|
56
|
+
|
57
|
+
def get(node_id)
|
58
|
+
result, _ = @zk.get("#{@base_path}/#{node_id}")
|
59
|
+
result
|
60
|
+
rescue ZK::Exceptions::NoNode
|
61
|
+
end
|
62
|
+
|
63
|
+
def set(node_id, addr)
|
64
|
+
path = "#{@base_path}/#{node_id}"
|
65
|
+
@zk.set path, addr
|
66
|
+
rescue ZK::Exceptions::NoNode
|
67
|
+
@zk.create path, addr
|
68
|
+
end
|
69
|
+
|
70
|
+
def nodes
|
71
|
+
@zk.children @base_path
|
72
|
+
end
|
73
|
+
|
74
|
+
def clear
|
75
|
+
@zk.rm_rf @base_path
|
76
|
+
@zk.mkdir_p @base_path
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
def get_node(node_id); @node_registry.get(node_id) end
|
81
|
+
def set_node(node_id, addr); @node_registry.set(node_id, addr) end
|
82
|
+
def nodes; @node_registry.nodes end
|
83
|
+
|
46
84
|
class GlobalRegistry
|
47
85
|
def initialize(zk, base_path)
|
48
86
|
@zk, @base_path = zk, "#{base_path}/globals"
|
@@ -81,4 +119,4 @@ module DCell
|
|
81
119
|
def global_keys; @global_registry.global_keys end
|
82
120
|
end
|
83
121
|
end
|
84
|
-
end
|
122
|
+
end
|
data/lib/dcell/router.rb
CHANGED
@@ -43,28 +43,6 @@ module DCell
|
|
43
43
|
end
|
44
44
|
end
|
45
45
|
|
46
|
-
# Route a message to a given mailbox ID
|
47
|
-
def route(recipient, message)
|
48
|
-
recipient = find(recipient) if recipient.is_a? String
|
49
|
-
|
50
|
-
if recipient
|
51
|
-
recipient << message
|
52
|
-
else
|
53
|
-
Celluloid::Logger.debug("received message for invalid actor: #{mailbox_address.inspect}")
|
54
|
-
end
|
55
|
-
end
|
56
|
-
|
57
|
-
# Route a system event to a given mailbox ID
|
58
|
-
def route_system_event(mailbox_address, event)
|
59
|
-
recipient = find mailbox_address
|
60
|
-
|
61
|
-
if recipient
|
62
|
-
recipient.system_event event
|
63
|
-
else
|
64
|
-
Celluloid::Logger.debug("received message for invalid actor: #{mailbox_address.inspect}")
|
65
|
-
end
|
66
|
-
end
|
67
|
-
|
68
46
|
# Prune all entries that point to dead objects
|
69
47
|
def gc
|
70
48
|
@mutex.synchronize do
|
data/lib/dcell/server.rb
CHANGED
@@ -6,7 +6,7 @@ module DCell
|
|
6
6
|
# Bind to the given 0MQ address (in URL form ala tcp://host:port)
|
7
7
|
def initialize
|
8
8
|
# The gossip protocol is dependent on the node manager
|
9
|
-
link Actor[:node_manager]
|
9
|
+
link Celluloid::Actor[:node_manager]
|
10
10
|
|
11
11
|
@addr = DCell.addr
|
12
12
|
@socket = PullSocket.new
|
data/lib/dcell/version.rb
CHANGED
data/spec/dcell/global_spec.rb
CHANGED
@@ -11,10 +11,6 @@ describe DCell::Global do
|
|
11
11
|
|
12
12
|
# Double check the global value is available on all nodes
|
13
13
|
node = DCell::Node['test_node']
|
14
|
-
20.downto(0) do |i|
|
15
|
-
break if node[:test_actor].the_answer
|
16
|
-
sleep 1
|
17
|
-
end
|
18
14
|
node[:test_actor].the_answer.should == 42
|
19
15
|
end
|
20
16
|
|
data/spec/spec_helper.rb
CHANGED
@@ -7,8 +7,8 @@ Dir['./spec/support/*.rb'].map { |f| require f }
|
|
7
7
|
|
8
8
|
RSpec.configure do |config|
|
9
9
|
config.before(:suite) do
|
10
|
-
DCell.setup
|
11
|
-
|
10
|
+
DCell.setup
|
11
|
+
DCell.run!
|
12
12
|
|
13
13
|
TestNode.start
|
14
14
|
TestNode.wait_until_ready
|
@@ -17,7 +17,4 @@ RSpec.configure do |config|
|
|
17
17
|
config.after(:suite) do
|
18
18
|
TestNode.stop
|
19
19
|
end
|
20
|
-
end
|
21
|
-
|
22
|
-
# FIXME: this is hax to bypass the other at_exit handlers
|
23
|
-
at_exit { exit! $!.status }
|
20
|
+
end
|
@@ -1,4 +1,22 @@
|
|
1
1
|
shared_context "a DCell registry" do
|
2
|
+
context "node registry" do
|
3
|
+
before :each do
|
4
|
+
subject.clear_nodes
|
5
|
+
end
|
6
|
+
|
7
|
+
it "stores node addresses" do
|
8
|
+
address = "tcp://localhost:7777"
|
9
|
+
|
10
|
+
subject.set_node("foobar", address)
|
11
|
+
subject.get_node("foobar").should == address
|
12
|
+
end
|
13
|
+
|
14
|
+
it "stores the IDs of all nodes" do
|
15
|
+
subject.set_node("foobar", "tcp://localhost:7777")
|
16
|
+
subject.nodes.should include "foobar"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
2
20
|
context "global registry" do
|
3
21
|
before :each do
|
4
22
|
subject.clear_globals
|
data/spec/test_node.rb
CHANGED
@@ -6,7 +6,7 @@ require 'bundler'
|
|
6
6
|
Bundler.setup
|
7
7
|
|
8
8
|
require 'dcell'
|
9
|
-
DCell.
|
9
|
+
DCell.start :id => 'test_node', :addr => 'tcp://127.0.0.1:21264'
|
10
10
|
|
11
11
|
class TestActor
|
12
12
|
include Celluloid
|
@@ -25,9 +25,5 @@ class TestActor
|
|
25
25
|
end
|
26
26
|
end
|
27
27
|
|
28
|
-
|
29
|
-
|
30
|
-
supervise TestActor, :as => :test_actor
|
31
|
-
end
|
32
|
-
|
33
|
-
TestGroup.run
|
28
|
+
TestActor.supervise_as :test_actor
|
29
|
+
sleep
|