actory
Actor model like, concurrent and distributed framework for Ruby.
Installation
gem install actory
Setup
path_to_actory="/PATH/TO/GEMS/actory-0.0.1"
export PATH=$PATH:$path_to_actory/bin
- /PATH/TO/GEMS above means the path to the directory of gem files. e.g. $HOME/.rvm/gems/ruby-2.0.0-p247@global/gems
System-wide setup with the root permission
sudo mkdir -p /etc/actory
sudo cp -p $path_to_actory/config/receiver.yml.example /etc/actory
sudo cp -p $path_to_actory/config/sender.yml.example /etc/actory
System-wide setup under the RubyGems
cd $path_to_actory/config
cp receiver.yml.example receiver.yml
cp sender.yml.example sender.yml
sed -i '' "s/\/etc\/actory/\.\.\/\.\.\/\.\.\/config/g" global.yml
Setup inside of each indivisual project
echo "gem actory" >> Gemfile
bundle install --path vendor/bundle
cp ./vendor/bundle
path_to_actory="vendor/bundle/ruby/2.0.0/gems/actory-0.0.1"
cp $path_to_actory/config/receiver.yml.example $path_to_actory/config/receiver.yml
cp $path_to_actory/config/sender.yml.example $path_to_actory/config/sender.yml
sed -i '' "s/\/etc\/actory/\.\.\/\.\.\/\.\.\/config/g" $path_to_actory/config/global.yml
Architecture
- Actor model like message passing API
- Dynamically loaded plugins
- High concurrency
- Low overheads
System-wide
Sending a message to each receiver to make it deal with the message.
+--------+
|user app|
+--------+
| message(method, [args]) # [args].size # => i
v
+------------------+
|Sender::Dispatcher|
+------------------+
| execute
| +----------------+ msgpack-rpc +----------------------+ method(arg[0]) +----------------+
+-->|sub-process #1/n|------------>|Receiver::EventHandler|--------------->|Receiver::Plugin|
| +----------------+ +----------------------+ execute +----------------+
| +----------------+ +----------------------+ method(arg[1]) +----------------+
+-->|sub-process #2/n|------------>|Receiver::EventHandler|--------------->|Receiver::Plugin|
| +----------------+ +----------------------+ : +----------------+
| : : execute :
| +----------------+ +----------------------+ method(arg[i]) +----------------+
`-->|sub-process #n/n|------------>|Receiver::EventHandler|--------------->|Receiver::Plugin|
+----------------+ +----------------------+ +----------------+
Receiving each returned value at once.
+--------+
|user app|
+--------+
^ an array value in [{"host:port" => [return_value(s), ...]}, ... ]
|
+------------------+
|Sender::Dispatcher|
+------------------+
^ return
| +----------------+ msgpack-rpc +----------------------+ value(s) +----------------+
+---|sub-process #1/n|<------------|Receiver::EventHandler|<-------------|Receiver::Plugin|
| +----------------+ +----------------------+ +----------------+
| +----------------+ +----------------------+ +----------------+
+---|sub-process #2/n|<------------|Receiver::EventHandler|<-------------|Receiver::Plugin|
| +----------------+ +----------------------+ +----------------+
| : : :
| +----------------+ +----------------------+ +----------------+
`---|sub-process #n/n|<------------|Receiver::EventHandler|<-------------|Receiver::Plugin|
+----------------+ +----------------------+ +----------------+
Jobs can be assigned flexibly.
+--------+
|user app|
+--------+
| message(method, [args]) # [args].size # => i; i > n # => true
v
+------------------+
|Sender::Dispatcher|
+------------------+
| execute
| +----------------+ msgpack-rpc +----------------------+ method(arg[0]) +----------------+
+-->|sub-process #1/n|------------>|Receiver::EventHandler|----------------->|Receiver::Plugin|
| | | +----------------------+ execute +----------------+
| | | +----------------------+ method(arg[i]) +----------------+
+-->| |------------>|Receiver::EventHandler|----------------->|Receiver::Plugin|
| +----------------+ +----------------------+ execute +----------------+
| +----------------+ +----------------------+ method(arg[1]) +----------------+
+-->|sub-process #2/n|------------>|Receiver::EventHandler|----------------->|Receiver::Plugin|
| +----------------+ +----------------------+ : +----------------+
| : : execute :
| +----------------+ +----------------------+ method(arg[i-1]) +----------------+
`-->|sub-process #n/n|------------>|Receiver::EventHandler|----------------->|Receiver::Plugin|
+----------------+ +----------------------+ +----------------+
+--------+
|user app|
+--------+
| message(method, [args]) # [args].size # => i; n > i # => true
v
+------------------+
|Sender::Dispatcher|
+------------------+
| execute
| +----------------+ msgpack-rpc +----------------------+ method(arg[0]) +----------------+
+-->|sub-process #1/n|------------>|Receiver::EventHandler|--------------->|Receiver::Plugin|
| +----------------+ +----------------------+ execute +----------------+
| +----------------+ +----------------------+ method(arg[i]) +----------------+
`-->|sub-process #2/n|------------>|Receiver::EventHandler|--------------->|Receiver::Plugin|
+----------------+ +----------------------+ +----------------+
: : :
+----------------+ +----------------------+ +----------------+
|sub-process #n/n| |Receiver::EventHandler| |Receiver::Plugin|
+----------------+ +----------------------+ +----------------+
+--------+
|user app|
+--------+
| message(method, [args]) # [args].size # => 1
v
+------------------+
|Sender::Dispatcher|
+------------------+
| execute
| +----------------+ msgpack-rpc +----------------------+ method(arg[0]) +----------------+
`-->|sub-process #1/n|------------>|Receiver::EventHandler|--------------->|Receiver::Plugin|
+----------------+ +----------------------+ +----------------+
+----------------+ +----------------------+ +----------------+
|sub-process #2/n| |Receiver::EventHandler| |Receiver::Plugin|
+----------------+ +----------------------+ +----------------+
: : :
+----------------+ +----------------------+ +----------------+
|sub-process #n/n| |Receiver::EventHandler| |Receiver::Plugin|
+----------------+ +----------------------+ +----------------+
Receiver
+--------------------------------------------------------------------------+
| a receiver |
| |
| +--------+ |
| | Worker | |
| +--------+ |
| | +-------------+ |
| | spawn when started | CPU | |
| | +-------------------------+ | +---------+ | +-----------------+ |
| +-->|Msgpack::RPC::Server #1/n|-->| |core #1/n|--->|EventHandler #1/n| |
| | +-------------------------+ | +---------+ | +-----------------+ |
| | +-------------------------+ | +---------+ | +-----------------+ |
| +-->|Msgpack::RPC::Server #2/n|-->| |core #2/n|--->|EventHandler #2/n| |
| | +-------------------------+ | +---------+ | +-----------------+ |
| | : | : | : |
| | +-------------------------+ | +---------+ | +-----------------+ |
| `-->|Magpack::RPC::Server #n/n|-->| |core #n/n|--->|EventHandler #n/n| |
| +-------------------------+ | +---------+ | +-----------------+ |
| | | |
| +-------------+ |
| |
+--------------------------------------------------------------------------+
Sender
+-----------------------------------------------------+ +-----------------------------+
| a sender | | receiver(s) |
| | | |
| +------------+ | | |
| | Dispatcher | | | |
| +------------+ | | |
| | +-------------+ | | |
| | spawn when instantized | CPU | | | |
| | +-------------------------+ | +---------+ | | | +-------------------------+ |
| +-->|Msgpack::RPC::Client #1/n|-->| |core #1/n|------->|Msgpack::RPC::Server #1/m| |
| | +-------------------------+ | +---------+ | | | +-------------------------+ |
| | +-------------------------+ | +---------+ | | | +-------------------------+ |
| +-->|Msgpack::RPC::Client #2/n|-->| |core #2/n|------->|Msgpack::RPC::Server #2/m| |
| | +-------------------------+ | +---------+ | | | +-------------------------+ |
| | : | : | | | : |
| | +-------------------------+ | +---------+ | | | +-------------------------+ |
| `-->|Magpack::RPC::Client #n/n|-->| |core #n/n|------->|Msgpack::RPC::Server #m/m| |
| +-------------------------+ | +---------+ | | | +-------------------------+ |
| | | | | |
| +-------------+ | | |
| | | |
+-----------------------------------------------------+ +-----------------------------+
Configuration
config/receiver.yml
- protocol
- "tcp" or "udp"
- address
- Binding IP Address.
- port
- Port number to begin increment.
- shared_key
- A pre-shared key string to establish connections with a sender.
- log
- type
- The type of the log. "stdout", "file" or "both".
- level
- A log level. "fatal", "error", "warn", "info" or "debug".
- target
- The log file path, used when the "type" is specified as "file" or "both".
- type
config/sender.yml
- actors
- A list of actors. The format is "host_name_or_ip_address:port".
- policy
- The policy to select actors and assign a message to them. "even", "random" or "safe-random".
- timeout
- Connection timeout value for msgpack-rpc.
- get_interval
- An interval to retry the get method for msgpack-rpc.
- auth
- shared_key
- A pre-shared key string to establish connections with each receiver.
- timeout
- Authentication timeout with each receiver.
- shared_key
- show_progress
- If it is true, the sender shows you a progress bar.
- reload_receiver_plugins
- If it is true, the sender force each receiver reload plugins even in running.
- log
- type
- The type of the log. "stdout", "file" or "both".
- level
- A log level. "fatal", "error", "warn", "info" or "debug".
- target
- The log file path, used when the "type" is specified as "file" or "both".
- type
Usage
Receiver
Foreground
actory-receiver
Background
actory-receiver -d
Sender
require 'actory'
dispacher = Actory::Sender::Dispatcher.new
res = dispatcher.message(METHOD, ARGS)
You can specify an actor or actors with an argument with actors keyword.
require 'actory'
dispacher = Actory::Sender::Dispatcher.new(actors: ["localhost:18800"])
res = dispatcher.message(METHOD, ARGS)
require 'actory'
actors = ["192.168.1.1:18800", "192.168.1.2:18800", "192.168.1.3:18800"]
dispacher = Actory::Sender::Dispatcher.new(actors: actors)
res = dispatcher.message(METHOD, ARGS)
Plugin
You can create your own plugin(s) to be executed by the receiver.
module Actory
module Receiver
class Plugin < Base
def hello(arg)
return "Hello #{arg}."
rescue => e
msg = Actory::Errors::Generator.new.json(level: "ERROR", message: e.message, backtrace: $@)
raise StandardError, msg
end
end
end
end
If you have created a plugin, put it just under the lib/actory/receiver/plugin directory. Then it will be automatically loaded even in the receiver running.
And then, you can call the method from a sender like following:
require 'actory'
dispacher = Actory::Sender::Dispatcher.new
res = dispatcher.message("hello", ["world", "actory"])
The response in an array will be like following:
p res
[{"127.0.0.1:18800"=>["Hello world."]}, {"192.168.1.1:18800"=>["Hello actory."]}]
License
Apache License, Version 2.0