dcell 0.0.0 → 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/.rspec +4 -0
- data/CHANGES.md +8 -0
- data/Gemfile +2 -0
- data/README.md +209 -0
- data/Rakefile +3 -0
- data/benchmarks/messaging.rb +67 -0
- data/benchmarks/receiver.rb +37 -0
- data/dcell.gemspec +10 -3
- data/lib/celluloid/README +8 -0
- data/lib/celluloid/zmq.rb +28 -0
- data/lib/celluloid/zmq/mailbox.rb +13 -0
- data/lib/celluloid/zmq/reactor.rb +74 -0
- data/lib/dcell.rb +92 -2
- data/lib/dcell/actor_proxy.rb +4 -0
- data/lib/dcell/application.rb +6 -0
- data/lib/dcell/celluloid_ext.rb +57 -0
- data/lib/dcell/directory.rb +23 -0
- data/lib/dcell/global.rb +23 -0
- data/lib/dcell/mailbox_proxy.rb +61 -0
- data/lib/dcell/messages.rb +67 -0
- data/lib/dcell/node.rb +120 -0
- data/lib/dcell/registries/redis_adapter.rb +86 -0
- data/lib/dcell/registries/zk_adapter.rb +122 -0
- data/lib/dcell/responses.rb +16 -0
- data/lib/dcell/router.rb +71 -0
- data/lib/dcell/rspec.rb +1 -0
- data/lib/dcell/server.rb +80 -0
- data/lib/dcell/version.rb +1 -1
- data/spec/celluloid/zmq/mailbox_spec.rb +6 -0
- data/spec/dcell/actor_proxy_spec.rb +60 -0
- data/spec/dcell/celluloid_ext_spec.rb +21 -0
- data/spec/dcell/directory_spec.rb +8 -0
- data/spec/dcell/global_spec.rb +21 -0
- data/spec/dcell/node_spec.rb +23 -0
- data/spec/dcell/registries/redis_adapter_spec.rb +6 -0
- data/spec/dcell/registries/zk_adapter_spec.rb +11 -0
- data/spec/spec_helper.rb +16 -0
- data/spec/support/helpers.rb +40 -0
- data/spec/support/registry_examples.rb +35 -0
- data/spec/test_node.rb +33 -0
- data/tasks/rspec.task +7 -0
- data/tasks/zookeeper.task +58 -0
- metadata +111 -7
data/lib/dcell.rb
CHANGED
@@ -1,5 +1,95 @@
|
|
1
|
-
require
|
1
|
+
require 'celluloid'
|
2
|
+
require 'celluloid/zmq'
|
3
|
+
require 'dcell/version'
|
2
4
|
|
5
|
+
require 'dcell/actor_proxy'
|
6
|
+
require 'dcell/directory'
|
7
|
+
require 'dcell/mailbox_proxy'
|
8
|
+
require 'dcell/messages'
|
9
|
+
require 'dcell/node'
|
10
|
+
require 'dcell/global'
|
11
|
+
require 'dcell/responses'
|
12
|
+
require 'dcell/router'
|
13
|
+
require 'dcell/server'
|
14
|
+
|
15
|
+
require 'dcell/registries/redis_adapter'
|
16
|
+
require 'dcell/application'
|
17
|
+
require 'dcell/celluloid_ext'
|
18
|
+
|
19
|
+
# Distributed Celluloid
|
3
20
|
module DCell
|
4
|
-
#
|
21
|
+
DEFAULT_PORT = 7777 # Default DCell port
|
22
|
+
ZMQ_POOL_SIZE = 1 # DCell uses a fixed-size 0MQ thread pool
|
23
|
+
@zmq_context = ::ZMQ::Context.new(ZMQ_POOL_SIZE)
|
24
|
+
@config_lock = Mutex.new
|
25
|
+
|
26
|
+
class << self
|
27
|
+
attr_reader :me, :registry, :zmq_context
|
28
|
+
|
29
|
+
# Configure DCell with the following options:
|
30
|
+
#
|
31
|
+
# * id: to identify the local node, defaults to hostname
|
32
|
+
# * addr: 0MQ address of the local node (e.g. tcp://4.3.2.1:7777)
|
33
|
+
# *
|
34
|
+
def setup(options = {})
|
35
|
+
# Stringify keys :/
|
36
|
+
options = options.inject({}) { |h,(k,v)| h[k.to_s] = v; h }
|
37
|
+
|
38
|
+
@config_lock.synchronize do
|
39
|
+
@configuration = {
|
40
|
+
'id' => generate_node_id,
|
41
|
+
'addr' => "tcp://127.0.0.1:#{DEFAULT_PORT}",
|
42
|
+
'registry' => {'adapter' => 'redis', 'server' => 'localhost'}
|
43
|
+
}.merge(options)
|
44
|
+
|
45
|
+
@me = Node.new @configuration['id'], @configuration['addr']
|
46
|
+
|
47
|
+
registry_adapter = @configuration['registry'][:adapter] || @configuration['registry']['adapter']
|
48
|
+
raise ArgumentError, "no registry adapter given in config" unless registry_adapter
|
49
|
+
|
50
|
+
registry_class_name = registry_adapter.split("_").map(&:capitalize).join << "Adapter"
|
51
|
+
|
52
|
+
begin
|
53
|
+
registry_class = DCell::Registry.const_get registry_class_name
|
54
|
+
rescue NameError
|
55
|
+
raise ArgumentError, "invalid registry adapter: #{@configuration['registry']['adapter']}"
|
56
|
+
end
|
57
|
+
|
58
|
+
@registry = registry_class.new(@configuration['registry'])
|
59
|
+
|
60
|
+
addr = @configuration['public'] || @configuration['addr']
|
61
|
+
DCell::Directory.set @configuration['id'], addr
|
62
|
+
end
|
63
|
+
|
64
|
+
me
|
65
|
+
end
|
66
|
+
|
67
|
+
# Obtain the local node ID
|
68
|
+
def id; @configuration['id']; end
|
69
|
+
|
70
|
+
# Obtain the 0MQ address to the local mailbox
|
71
|
+
def addr; @configuration['addr']; end
|
72
|
+
alias_method :address, :addr
|
73
|
+
|
74
|
+
# Attempt to generate a unique node ID for this machine
|
75
|
+
def generate_node_id
|
76
|
+
`hostname`.strip # Super creative I know
|
77
|
+
end
|
78
|
+
|
79
|
+
# Run the DCell application
|
80
|
+
def run
|
81
|
+
DCell::Application.run
|
82
|
+
end
|
83
|
+
|
84
|
+
# Run the DCell application in the background
|
85
|
+
def run!
|
86
|
+
DCell::Application.run!
|
87
|
+
end
|
88
|
+
|
89
|
+
# Start combines setup and run! into a single step
|
90
|
+
def start(options = {})
|
91
|
+
setup options
|
92
|
+
run!
|
93
|
+
end
|
94
|
+
end
|
5
95
|
end
|
@@ -0,0 +1,57 @@
|
|
1
|
+
# Celluloid mailboxes are the universal message exchange points. You won't
|
2
|
+
# be able to marshal them though, unfortunately, because they contain
|
3
|
+
# mutexes.
|
4
|
+
#
|
5
|
+
# DCell provides a message routing layer between nodes that can direct
|
6
|
+
# messages back to local mailboxes. To accomplish this, DCell adds custom
|
7
|
+
# marshalling to mailboxes so that if they're unserialized on a remote
|
8
|
+
# node you instead get a proxy object that routes messages through the
|
9
|
+
# DCell overlay network back to the node where the actor actually exists
|
10
|
+
|
11
|
+
module Celluloid
|
12
|
+
class ActorProxy
|
13
|
+
# Marshal uses respond_to? to determine if this object supports _dump so
|
14
|
+
# unfortunately we have to monkeypatch in _dump support as the proxy
|
15
|
+
# itself normally jacks respond_to? and proxies to the actor
|
16
|
+
alias_method :__respond_to?, :respond_to?
|
17
|
+
def respond_to?(meth)
|
18
|
+
return false if meth == :marshal_dump
|
19
|
+
return true if meth == :_dump
|
20
|
+
__respond_to? meth
|
21
|
+
end
|
22
|
+
|
23
|
+
# Dump an actor proxy via its mailbox
|
24
|
+
def _dump(level)
|
25
|
+
@mailbox._dump(level)
|
26
|
+
end
|
27
|
+
|
28
|
+
# Create an actor proxy object which routes messages over DCell's overlay
|
29
|
+
# network and back to the original mailbox
|
30
|
+
def self._load(string)
|
31
|
+
mailbox = Celluloid::Mailbox._load(string)
|
32
|
+
|
33
|
+
case mailbox
|
34
|
+
when DCell::MailboxProxy
|
35
|
+
DCell::ActorProxy.new mailbox
|
36
|
+
when Celluloid::Mailbox
|
37
|
+
Celluloid::ActorProxy.new(mailbox)
|
38
|
+
else raise "funny, I did not expect to see a #{mailbox.class} here"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
43
|
+
class Mailbox
|
44
|
+
# This custom dumper registers actors with the DCell registry so they can
|
45
|
+
# be reached remotely.
|
46
|
+
def _dump(level)
|
47
|
+
mailbox_id = DCell::Router.register self
|
48
|
+
"#{mailbox_id}@#{DCell.id}"
|
49
|
+
end
|
50
|
+
|
51
|
+
# Create a mailbox proxy object which routes messages over DCell's overlay
|
52
|
+
# network and back to the original mailbox
|
53
|
+
def self._load(string)
|
54
|
+
DCell::MailboxProxy._load(string)
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
@@ -0,0 +1,23 @@
|
|
1
|
+
module DCell
|
2
|
+
# Directory of nodes connected to the DCell cluster
|
3
|
+
module Directory
|
4
|
+
extend self
|
5
|
+
|
6
|
+
# Get the URL for a particular Node ID
|
7
|
+
def get(node_id)
|
8
|
+
DCell.registry.get_node node_id
|
9
|
+
end
|
10
|
+
alias_method :[], :get
|
11
|
+
|
12
|
+
# Set the address of a particular Node ID
|
13
|
+
def set(node_id, addr)
|
14
|
+
DCell.registry.set_node node_id, addr
|
15
|
+
end
|
16
|
+
alias_method :[]=, :set
|
17
|
+
|
18
|
+
# List all of the node IDs in the directory
|
19
|
+
def all
|
20
|
+
DCell.registry.nodes
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
data/lib/dcell/global.rb
ADDED
@@ -0,0 +1,23 @@
|
|
1
|
+
module DCell
|
2
|
+
# Global object registry shared among all DCell nodes
|
3
|
+
module Global
|
4
|
+
extend self
|
5
|
+
|
6
|
+
# Get a global value
|
7
|
+
def get(key)
|
8
|
+
DCell.registry.get_global key.to_s
|
9
|
+
end
|
10
|
+
alias_method :[], :get
|
11
|
+
|
12
|
+
# Set a global value
|
13
|
+
def set(key, value)
|
14
|
+
DCell.registry.set_global key.to_s, value
|
15
|
+
end
|
16
|
+
alias_method :[]=, :set
|
17
|
+
|
18
|
+
# Get the keys for all the globals in the system
|
19
|
+
def keys
|
20
|
+
DCell.registry.global_keys.map(&:to_sym)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
@@ -0,0 +1,61 @@
|
|
1
|
+
module DCell
|
2
|
+
# A proxy object for a mailbox that delivers messages to the real mailbox on
|
3
|
+
# a remote node on a server far, far away...
|
4
|
+
class MailboxProxy
|
5
|
+
class InvalidNodeError < StandardError; end
|
6
|
+
|
7
|
+
def initialize(node_id, mailbox_id)
|
8
|
+
raise ArgumentError, "no mailbox_id given" unless mailbox_id
|
9
|
+
|
10
|
+
@node_id = node_id
|
11
|
+
@node = Node[node_id]
|
12
|
+
raise ArgumentError, "invalid node_id given" unless @node
|
13
|
+
|
14
|
+
@mailbox_id = mailbox_id
|
15
|
+
end
|
16
|
+
|
17
|
+
# name@host style address
|
18
|
+
def address
|
19
|
+
"#{@mailbox_id}@#{@node_id}"
|
20
|
+
end
|
21
|
+
|
22
|
+
def inspect
|
23
|
+
"#<DCell::MailboxProxy:0x#{object_id.to_s(16)} #{address}>"
|
24
|
+
end
|
25
|
+
|
26
|
+
# Send a message to the mailbox
|
27
|
+
def <<(message)
|
28
|
+
@node.send_message! Message::Relay.new(@mailbox_id, message)
|
29
|
+
end
|
30
|
+
|
31
|
+
# Send a system event
|
32
|
+
def system_event(event)
|
33
|
+
@node.send_message! Message::SystemEvent.new(@mailbox_id, event)
|
34
|
+
end
|
35
|
+
|
36
|
+
# Is the remote mailbox still alive?
|
37
|
+
def alive?
|
38
|
+
true # FIXME: hax!
|
39
|
+
end
|
40
|
+
|
41
|
+
# Custom marshaller for compatibility with Celluloid::Mailbox marshalling
|
42
|
+
def _dump(level)
|
43
|
+
"#{@mailbox_id}@#{@node_id}"
|
44
|
+
end
|
45
|
+
|
46
|
+
# Loader for custom marshal format
|
47
|
+
def self._load(string)
|
48
|
+
mailbox_id, node_id = string.split("@")
|
49
|
+
|
50
|
+
if DCell.id == node_id
|
51
|
+
# If we're on the local node, find the real mailbox
|
52
|
+
mailbox = DCell::Router.find mailbox_id
|
53
|
+
raise "tried to unmarshal dead Celluloid::Mailbox: #{mailbox_id}" unless mailbox
|
54
|
+
mailbox
|
55
|
+
else
|
56
|
+
# Create a proxy to the mailbox on the remote node
|
57
|
+
DCell::MailboxProxy.new(node_id, mailbox_id)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
end
|
61
|
+
end
|
@@ -0,0 +1,67 @@
|
|
1
|
+
module DCell
|
2
|
+
class Message
|
3
|
+
attr_reader :id
|
4
|
+
|
5
|
+
def initialize
|
6
|
+
# Memoize the original object ID so it will get marshalled
|
7
|
+
# Perhaps this should use a real UUID scheme
|
8
|
+
@id = object_id
|
9
|
+
end
|
10
|
+
|
11
|
+
# Query a node for the address of an actor
|
12
|
+
class Find < Message
|
13
|
+
attr_reader :caller, :name
|
14
|
+
|
15
|
+
def initialize(caller, name)
|
16
|
+
super()
|
17
|
+
@caller, @name = caller, name
|
18
|
+
end
|
19
|
+
|
20
|
+
def dispatch
|
21
|
+
@caller << SuccessResponse.new(@id, Celluloid::Actor[@name])
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
# List all registered actors
|
26
|
+
class List < Message
|
27
|
+
attr_reader :caller
|
28
|
+
|
29
|
+
def initialize(caller)
|
30
|
+
super()
|
31
|
+
@caller = caller
|
32
|
+
end
|
33
|
+
|
34
|
+
def dispatch
|
35
|
+
@caller << SuccessResponse.new(@id, Celluloid::Actor.registered)
|
36
|
+
end
|
37
|
+
end
|
38
|
+
|
39
|
+
# Relay a message to the given recipient
|
40
|
+
class Relay < Message
|
41
|
+
attr_reader :recipient, :message
|
42
|
+
|
43
|
+
def initialize(recipient, message)
|
44
|
+
super()
|
45
|
+
@recipient, @message = recipient, message
|
46
|
+
end
|
47
|
+
|
48
|
+
def dispatch
|
49
|
+
DCell::Router.route @recipient, @message
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
# Send a system event to the given recipient
|
54
|
+
class SystemEvent < Message
|
55
|
+
attr_reader :recipient, :event
|
56
|
+
|
57
|
+
def initialize(recipient, event)
|
58
|
+
super()
|
59
|
+
@recipient, @event = recipient, event
|
60
|
+
end
|
61
|
+
|
62
|
+
def dispatch
|
63
|
+
DCell::Router.route_system_event @recipient, @event
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
data/lib/dcell/node.rb
ADDED
@@ -0,0 +1,120 @@
|
|
1
|
+
module DCell
|
2
|
+
# A node in a DCell cluster
|
3
|
+
class Node
|
4
|
+
include Celluloid
|
5
|
+
attr_reader :id, :addr
|
6
|
+
|
7
|
+
@nodes = {}
|
8
|
+
@lock = Mutex.new
|
9
|
+
|
10
|
+
class << self
|
11
|
+
include Enumerable
|
12
|
+
|
13
|
+
# Return all available nodes in the cluster
|
14
|
+
def all
|
15
|
+
Directory.all.map do |node_id|
|
16
|
+
find node_id
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
# Iterate across all available nodes
|
21
|
+
def each
|
22
|
+
Directory.all.each do |node_id|
|
23
|
+
yield find node_id
|
24
|
+
end
|
25
|
+
end
|
26
|
+
|
27
|
+
# Find a node by its node ID
|
28
|
+
def find(id)
|
29
|
+
node = @lock.synchronize { @nodes[id] }
|
30
|
+
return node if node
|
31
|
+
|
32
|
+
addr = Directory[id]
|
33
|
+
|
34
|
+
if addr
|
35
|
+
if id == DCell.id
|
36
|
+
node = DCell.me
|
37
|
+
else
|
38
|
+
node = Node.new(id, addr)
|
39
|
+
end
|
40
|
+
|
41
|
+
@lock.synchronize do
|
42
|
+
@nodes[id] ||= node
|
43
|
+
@nodes[id]
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
alias_method :[], :find
|
48
|
+
end
|
49
|
+
|
50
|
+
def initialize(id, addr)
|
51
|
+
@id, @addr = id, addr
|
52
|
+
@socket = nil
|
53
|
+
end
|
54
|
+
|
55
|
+
def finalize
|
56
|
+
@socket.close if socket
|
57
|
+
end
|
58
|
+
|
59
|
+
# Obtain the node's 0MQ socket
|
60
|
+
def socket
|
61
|
+
return @socket if @socket
|
62
|
+
|
63
|
+
@socket = DCell.zmq_context.socket(::ZMQ::PUSH)
|
64
|
+
unless ::ZMQ::Util.resultcode_ok? @socket.connect @addr
|
65
|
+
@socket.close
|
66
|
+
@socket = nil
|
67
|
+
raise "error connecting to #{addr}: #{::ZMQ::Util.error_string}"
|
68
|
+
end
|
69
|
+
|
70
|
+
@socket
|
71
|
+
end
|
72
|
+
|
73
|
+
# Find an actor registered with a given name on this node
|
74
|
+
def find(name)
|
75
|
+
request = Message::Find.new(Thread.mailbox, name)
|
76
|
+
send_message request
|
77
|
+
|
78
|
+
response = receive do |msg|
|
79
|
+
msg.respond_to?(:request_id) && msg.request_id == request.id
|
80
|
+
end
|
81
|
+
|
82
|
+
abort response.value if response.is_a? ErrorResponse
|
83
|
+
response.value
|
84
|
+
end
|
85
|
+
alias_method :[], :find
|
86
|
+
|
87
|
+
# List all registered actors on this node
|
88
|
+
def actors
|
89
|
+
request = Message::List.new(Thread.mailbox)
|
90
|
+
send_message request
|
91
|
+
|
92
|
+
response = receive do |msg|
|
93
|
+
msg.respond_to?(:request_id) && msg.request_id == request.id
|
94
|
+
end
|
95
|
+
|
96
|
+
abort response.value if response.is_a? ErrorResponse
|
97
|
+
response.value
|
98
|
+
end
|
99
|
+
alias_method :all, :actors
|
100
|
+
|
101
|
+
# Send a message to another DCell node
|
102
|
+
def send_message(message)
|
103
|
+
begin
|
104
|
+
string = Marshal.dump(message)
|
105
|
+
rescue => ex
|
106
|
+
abort ex
|
107
|
+
end
|
108
|
+
|
109
|
+
unless ::ZMQ::Util.resultcode_ok? socket.send_string string
|
110
|
+
raise "error sending 0MQ message: #{::ZMQ::Util.error_string}"
|
111
|
+
end
|
112
|
+
end
|
113
|
+
alias_method :<<, :send_message
|
114
|
+
|
115
|
+
# Friendlier inspection
|
116
|
+
def inspect
|
117
|
+
"#<DCell::Node[#{@id}] @addr=#{@addr.inspect}>"
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|