Over the past few weeks, I’ve been looking at what it takes to create a distributed computing system. The idea of using the Actor model / Actor pattern came up and it is a concept that seems to fit nicely. What is the Actor pattern then?
From Wikipedia, it’s described as such:
https://en.wikipedia.org/wiki/Actor_model
Here’s another concise definition:
An actor object is used when you have a long running task and some code that needs to be executed after it completes. This kind of object is given the information it needs to perform the task and callbacks to execute when that task is done. The actor runs on its own thread without any further input and is destroyed when it is finished.
How does one go about implementing the actor pattern, though?
There is a framework called Akka:
Akka is an open-source toolkit and runtime simplifying the construction of concurrent and distributed applications on the JVM. Akka supports multiple programming models for concurrency, but it emphasizes actor-based concurrency, with inspiration drawn from Erlang. Language bindings exist for both Java and Scala.
Fortunately, it has been, mostly, ported to .NET as well.
In my mind, the actors are similar to threads. That’s an over simplification, but that’s where my thoughts go. Akka works by having a pool of actors that are effectively your workers. To me, this is similar to using a thread pool and background workers. However, threads differ in many ways. They don’t have a native messaging system, support scheduling, and don’t support any sort of distrubition/clustering across disparate (or homogeneous) systems. This is where the Akka framework and the actor pattern come into play to provide these feature to allow creating a distributed computing system.
Akka achieves much of its power through its routing system. The basic premise is that we create a router, let it know paths to various actors, and let it manage the actors through a messaging system. Akka gives us the ability to spawn actors, work with a predefined, constrained set of actors, or almost any other variation that we want. Akka reaches actors by having routes/paths defined which is not terribly different that any other routing system (think ASP.NET MVC/WebApi routing). Each actor, with Akka’s messaging system, has a mailbox. Messages can be stacked/queued for processing. Akka provides many routers which allow us to control how work is distributed. The framework “tells” actors when to do work and these messages are received in the actor’s mailbox.
The simplest scenario is creating a round-robin router that distributes messages to actors with the smallest mailbox. Here is a very simplistic example that creates a system, router, and distributes messages to Fibonacci actors. Note that I’ve limited the number of actors to (5) in order to illustrate how the messages are stacked up and the actors churn through the work (2000 tasks):
private void StartActors() { var system = ActorSystem.Create("AkkaTest"); var router = system.ActorOf(Props.Create<FiboActor>().WithRouter(new SmallestMailboxPool(5)), "AkkaTestPool"); var rand = new Random(); var numWorkers = 5; var routees = Enumerable.Range(1, numWorkers).Select(x => new ActorRefRoutee(system.ActorOf<FiboActor>(string.Format("fibo{0}", x)))).ToArray(); for (var i = 0; i < 2000; i++) { var fiboRequest = rand.Next(10, 25); router.Tell(new FiboMessage() { FiboRequest = fiboRequest, Message = "Hello", MessageType = FiboMessageType.Start }); } }
The code for the Akka actor is pretty simple. Actors are basically small atomic pieces of code that processes messages and do work. That’s it. As such, we define handlers to deal with messages as they are received. Afaik, actors can only process one message at a time. When they are done processing the message, the actor will go on to the next message in its mailbox. The code for my Fibonaci actor is shown below.
The code for this actor is pretty simple. If it receives a message of type “FiboMessage,” it determines the Fibonacci value for the request series, and “tells” the sender the result:
public class FiboActor : ReceiveActor { public FiboActor() { Receive<FiboMessage>(m => ProcessMessage(m, Sender) ); Receive<string>(m => Sender.Tell("Ok, I'm starting!") ); } public void ProcessMessage(FiboMessage message, IActorRef sender) { System.Diagnostics.Debug.WriteLine(string.Format("Received Message {0}", this.Self.Path.Name)); var fibResult = RecurseFib(message.FiboRequest); Sender.Tell(fibResult); System.Diagnostics.Debug.WriteLine(string.Format("Result: {0}, {1}", fibResult, this.Self.Path.Name)); } private int RecurseFib(int n) { if (n <= 1) return n; return RecurseFib(n - 1) + RecurseFib(n - 2); } }
The FiboMessage is simple too. It has a few properties that are used to pass in what is being request and not much else:
public enum FiboMessageType { Start, Stop, Progress } public class FiboMessage { public FiboMessageType MessageType { get; set; } public int FiboRequest { get; set; } public string Message { get; set; } public FiboMessage() { } public FiboMessage(string message, int fiboRequest, FiboMessageType messageType) { message = Message; messageType = MessageType; fiboRequest = FiboRequest; } }
Running this code will produce an output similar to this:
Result: 610, $d
Result: 4181, $e
Received Message $e
Result: 2584, $f
Received Message $f
Result: 233, $f
Received Message $f
Received Message $b
Result: 89, $b
Received Message $c
Result: 2584, $c
Result: 89, $f
Result: 144, $e
This is an initial cursory view of the actor pattern implemented with Akka, and really, is the tip of the iceberg. Distributing this work becomes possibly thanks to Akka’s clustering and node capabilities. I’ll explore this more in a later post, though.