Remotely Deploying Actors
Deploying an actor means two things simultaneously:
- Creating an actor instance with specific, explicitly configured properties and
- Getting an
IActorRef
to that actor.
With Akka.Remote we get a new exciting detail: the network location to which an actor is deployed becomes a configuration detail.
Remote Deployment Example
That's right - we can deploy code over the network with Akka.Remote.
Here's what that concept looks like expressed as Akka.NET code:
Shared Actor / Message Code
/*
* Create an actor and a message type that gets shared between Deployer and DeployTarget
* in a common DLL
*/
/// <summary>
/// Actor that just replies the message that it received earlier
/// </summary>
public class EchoActor : ReceiveActor
{
public EchoActor()
{
Receive<Hello>(hello =>
{
Console.WriteLine("[{0}]: {1}", Sender, hello.Message);
Sender.Tell(hello);
});
}
}
public class Hello
{
public Hello(string message)
{
Message = message;
}
public string Message { get; private set; }
}
DeployTarget (Process that Gets Deployed Onto)
class Program
{
static void Main(string[] args)
{
using (var system = ActorSystem.Create("DeployTarget", ConfigurationFactory.ParseString(@"
akka {
actor.provider = remote
remote {
dot-netty.tcp {
port = 8090
hostname = localhost
}
}
}")))
{
Console.ReadKey();
}
}
}
Deployer (Process that Does Deploying)
class Program
{
class SayHello { }
class HelloActor : ReceiveActor
{
private IActorRef _remoteActor;
private int _helloCounter;
private ICancelable _helloTask;
public HelloActor(IActorRef remoteActor)
{
_remoteActor = remoteActor;
Receive<Hello>(hello =>
{
Console.WriteLine("Received {1} from {0}", Sender, hello.Message);
});
Receive<SayHello>(sayHello =>
{
_remoteActor.Tell(new Hello("hello"+_helloCounter++));
});
}
protected override void PreStart()
{
_helloTask = Context.System.Scheduler.ScheduleTellRepeatedlyCancelable(TimeSpan.FromSeconds(1),
TimeSpan.FromSeconds(1), Context.Self, new SayHello(), ActorRefs.NoSender);
}
protected override void PostStop()
{
_helloTask.Cancel();
}
}
static void Main(string[] args)
{
using (var system = ActorSystem.Create("Deployer", ConfigurationFactory.ParseString(@"
akka {
actor{
provider = remote
deployment {
/remoteecho {
remote = ""akka.tcp://DeployTarget@localhost:8090""
}
}
}
remote {
dot-netty.tcp {
port = 0
hostname = localhost
}
}
}")))
{
var remoteAddress = Address.Parse("akka.tcp://DeployTarget@localhost:8090");
//deploy remotely via config
var remoteEcho1 = system.ActorOf(Props.Create(() => new EchoActor()), "remoteecho");
//deploy remotely via code
var remoteEcho2 =
system.ActorOf(
Props.Create(() => new EchoActor())
.WithDeploy(Deploy.None.WithScope(new RemoteScope(remoteAddress))), "coderemoteecho");
system.ActorOf(Props.Create(() => new HelloActor(remoteEcho1)));
system.ActorOf(Props.Create(() => new HelloActor(remoteEcho2)));
Console.ReadKey();
}
}
}
So what happens in this sample? Well first, let's take a look at the output if we run this with just one deployer.
As far as the DeployTarget is concerned, it's receiving messages from a RemoteActorRef
belonging to the Deployer, hence why we can see akka.tcp://Deployer@localhost:19600/user/$c
as the Sender
address.
But wait! Wait a minute! We didn't actually create any actors in the DeployTarget process!!!!
That's because the Deployer created the actors... but it created them INSIDE DeployTarget's process. Over the network.
Syntax
In the above example, it's this piece of HOCON configuration:
deployment {
/remoteecho {
remote = "akka.tcp://DeployTarget@localhost:8090"
}
}
And this piece of C# code:
Props.Create(() => new EchoActor()).WithDeploy(Deploy.None.WithScope(new RemoteScope(remoteAddress)))
That actually specify to deploy the configured actor onto the specified remote Address
, which belongs to the DeployTarget in this case.
How Remote Deployment Actually Works
The process of remote deployment feels magical, but it's actually pretty simple.
Rather than wait for a RemoteActorRef
to send us a message, we create one in advance - knowing full well that we're going to be able to deploy one onto the remote actor system if we're able to form an association with the remote system. If we can't form a remote association with the DeployTarget system, then the RemoteActorRef
we've just created will map to Deadletters
and nothing will happen.
However, in the happy event that we can form a remote association with DeployTarget the following things will happen:
- A local name on for the actor we're remote deploying will be reserved on Deployer, since all actors must have a unique name;
- The
Props
for theEchoActor
will be serialized, including all of the constructor arguments for theEchoActor
class, along with anything else in the actor's deployment such as router details, dispatcher configuration, and so forth; - The serialized
Props
and all of the relevant actor path and name information is transmitted over the network by Deployer'sEndpointWriter
and received by DeployTarget'sEndpointReader
; - The
EndpointReader
determines that this is a special "deploy remote actor" message, and tells a special system actor (theRemoteDaemon
) to create a newEchoActor
instance; and - Going forward, all messages sent to the
RemoteActorRef
are automatically sent to theEchoActor
.
And once all of that is done, we've successfully deployed an EchoActor
over the network from Deployer to DeployTarget.
Important Things to Know About Remote Actor Deployments
Here are some important things to remember about remote actor deployments:
- All names for remote actors are determined by the deploying
ActorSystem
. 1000 Deployer instances could all deploy an actor namedechoactor
onto the same DeployTarget instance and all 1000 of those operations would be successful. That's because the local actor created on DeployTarget has anActorPath
that looks likeakka.tcp://DeployTarget@localhost:8090/remote/akka.tcp/Deploye@localhost:19600/user/echoactor/
- the fullAddress
of each DeployerActorSystem
is appended to the front of theActorPath
, thereby guaranteeing that each remote deployed actor name is unique to the Deployer. - The C# code that defines the
EchoActor
type and the message types it expects must be present on both the Deployer and the DeployTarget, or the deployment fails because theProps
can't be deserialized. - All of the constructor arguments for
EchoActor
and any other remote-deployed actor must be serializable, again, because otherwise it can't be deployed.
When to Use Remote Deployment
When would you want to remotely deploy a new actor versus just sending a message to a remote actor that already exists somewhere else on the network?
There are two common scenarios for when you would want to deploy an actor remotely:
- Work distribution - you need to be able to push work onto remote machines using a remote or clustered router. This is exactly what the Cluster.WebCrawler sample does.
- Accessing machine-specific resources - if you wanted to gather
PerformanceCounter
data from an array of remote machines in order to get specific metrics from each machine, remote deployment is an effective tool for accomplishing that.