Akka notes: walkthrough of the actor model
The actor model provides a powerful abstraction for writing concurrent programs.As discussed in my post on Concurrency, actors allow you to avoid shared state entirely, eliminating race conditions by design.
It does this by introducing the concept of an "actor". An actor can be thought of as something which can perform computations based on messages that it receives. An actor can hold its own private state and can only communicate with other actors via message passing.
Akka - an implementation of the actor model for the JVM
In order to get things started, one must intialise an actor system. This is a construct responsible for controlling threadpools, config, and all the actors to be created. There is a single actor system per application.
Actors
Actors are uniquely identifiable via an ActorRef. They are lightweight objects which model a unit of computation. As mentioned, actors can only communicate with other actors by passing messages. Each actor possesses a mailbox (message queue) where each received message is stored. Actors processes messages from its mailbox one at a time - it emulates the behaviour of a single-threaded program, no need to worry about locking or synchronising resources.
actors are encapsulated (you cannot access their state directly) You can only communicate via the ActorRef
Class person(name: String) extends Actor{
override def receive = {
case “hi” => println(s“hi my name is, $name”)
}
}
How to instantiate an actor with arguments? Using props with argument eg Props(new Person(“bob”))
Messages sent to an actor must be immutable and serialisable
An actor can have more information about its environment via ActorContext. Such info can be the actor system, its own actor reference (context.self) [similar to this.x in OOP]
This can be useful given that an actor can send a message to itself (using context.self)
Actors can reply to messages
Given actor Alice and actor Bob:
SayHiTo(ref: ActorRef) Alice ! SayHiTo(bob) // sends “hi” to bob
// ...
case “hi” => context.sender() ! “My reply is: Hello!”
Also worth noting is that Deadletters is a ‘fake’ actor that takes care of messages not sent to anyone
Actors also have the ability to forward messages to one another Driver -> router -> pc record MyMesssage(String body, ref: ActorRef) Alice ! MyMesssage(hi, bob); //receive method: // case MyMesssage(body, ref) => ref forward body + “s” //keeps original ref, modifies body to be ‘his’
Concurrency
If everything is async, how to avoid race conditions?
At the heart of Akka's ActorSystem, there is a shared pool of threads. When sending a message, the message is delivered asynchronously and placed in the actor's mailbox. Enqueuing messages is a thread-safe operation, so multiple actors can send messages to a single actor simultaneously without issues.
The Akka runtime continuously monitors mailboxes for messages, the runtime schedules the actor to be executed on one of the threads from the thread pool.
Next, a thread is assigned to the actor. Then it begins processing messages from the mailbox one at a time. Messages are processed in the order they were received. Akka guarantees that only one thread can operate on an actor's state and logic at a time.
Since messages are processed one at at time, the actor's internal state can be modified safely without any need for locks or synchronised constructs. This eases the cognitive burden on the developer, allowing one program as if it were a single-threaded program.
Once an actor has processed its messages, or its allotted time slice is up, the akka runtime scheduler interrupts the thread and it is returned to the threadpool.
The use of the threadpool in this way allows handling of millions of actors on a small number of threads.
Message ordering and delivery guarantees
Akka offers some configurations for ensuing message deliver
At most once delivery (no duplicates): This guarantees message order is kept ie sending A then B, the receiver will always get A before B (but if another actor sends a message C then it’s possible that will be mixed in)
It is possible to enforce stricter guarantees at the cost of performance.
Actor hierarchy
Akka has the following hierarchy: A root guardian, a user guardian and system guardian.The root guardian - the parent to all actors.
the system guardian (manages logging and other library responsibilities)
user -> user-level actor used to start all other actors in the system.
Fault tolerance
One of the great benefits of using an actor based system such as akka is the fault-tolerance it provides. One can easily set up parent actors which supervise child actors. In the event of a failure, the parent can determine if a new child needs to be created, one needs to be killed, or if the failure should be escalated to its own parent.
This allows the developer to write supervised self-healing systems which can handle failure and isolate it to a sub-section of the system.