dcell 0.10.0 → 0.12.0.pre
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/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
|