One of the things I cover in my new Pluralsight course is the awesome feature of routers in Akka.NET.
Routers, with very little code/config change, allow us to spread messages across multiple instances of actors. This means we can process messages concurrently and thus improve the overall throughput of messages in the actor system.
Routers distribute message to a set of actor instance “routees”.
Router Types
Routers can be grouped into two categories: Group routers and Pool routers.
Group routers distribute messages to actors that have already been created in the actor system, this means that a Group router does not supervise its routees.
A Pool router on the other hand does create and supervise its routees.
Routing Strategies
Both Pool and Group routers use routing strategies to determine which routee(s) a message should be routed to. Not all routing strategies are available in both Group and Pool routers.
One of the routing strategies is the Round-Robin strategy. This strategy will route incoming messages in turn to each routee in order, then return back to the first routee and continue around all routees.
An Example
As an example, consider the following console application that simulates some work being done with a Thread.Sleep(200).
using System;
using System.Diagnostics;
using System.Threading;
using Akka.Actor;
namespace ConsoleApplication1
{
public class CompletedSomeWorkMessage
{
}
public class DoSomeWorkMessage
{
public DoSomeWorkMessage(int workItem)
{
WorkItem = workItem;
}
public int WorkItem { get; private set; }
}
public class WorkerActor : ReceiveActor
{
private readonly IActorRef _counterActorRef;
public WorkerActor(IActorRef counterActorRef)
{
_counterActorRef = counterActorRef;
Receive<DoSomeWorkMessage>(
message =>
{
Console.WriteLine("Working on {0}", message.WorkItem);
// simulate some work
Thread.Sleep(200);
_counterActorRef.Tell(new CompletedSomeWorkMessage());
});
}
}
public class WorkCounterActor : ReceiveActor
{
private int _workLeft;
public WorkCounterActor(int workToBeDone)
{
_workLeft = workToBeDone;
Receive<CompletedSomeWorkMessage>(
message =>
{
_workLeft--;
if (_workLeft == 0)
{
// all work is done so shutdown actor system
Context.System.Shutdown();
}
});
}
}
internal class Program
{
private static void Main(string[] args)
{
const int totalWorkToBeDone = 20;
var system = ActorSystem.Create("MySystem");
var counter = system.ActorOf(Props.Create(() => new WorkCounterActor(totalWorkToBeDone)));
var worker = system.ActorOf(Props.Create(() => new WorkerActor(counter)));
var timeTaken = Stopwatch.StartNew();
for (int i = 1; i <= totalWorkToBeDone; i++)
{
worker.Tell(new DoSomeWorkMessage(i));
}
system.AwaitTermination();
timeTaken.Stop();
Console.WriteLine("Took {0}ms", timeTaken.ElapsedMilliseconds);
Console.ReadLine();
}
}
}
Running this application results in the following screenshot:
Notice in the preceding screenshot that to process 20 work items took about 4 seconds.
We can increase the concurrency in the system by adding a Round Robin Pool router.
The following modified code shows the creation of 5 worker actors as part of a pool.
var worker = system.ActorOf(Props.Create(() => new WorkerActor(counter)).WithRouter(new RoundRobinPool(5)));
Now when we execute worker.Tell(new DoSomeWorkMessage(i)) the message will be routed to one of five automatically created WorkerActor instances.
Running the program now results in an execution time of about 1.5 seconds rather than the non-router version that took about 4 seconds.
To learn more about the different types of routers and other ways to improve message throughput check out my Pluralsight course: Improving Message Throughput in Akka.NET or check out the Akka.NET documentation.
You can start watching with a Pluralsight free trial.
SHARE: