Introduction to Akka Actors
Defining an Actor
Akka provides a basic unit of abstraction of transparent distribution called actors, which form the basis for writing resilient, elastic, event-driven, and responsive systems.
The key abstraction in Akka is the Actor, which provides behavior and can store state. In Akka, actors are guaranteed to be run in a single-threaded illusion, which means that the Akka framework takes care of threading issues while allowing us to focus on the behavior that needs to be implemented.
Actors may only communicate with each other and the outside world by through messages. Any immutable object can be used as a message in Akka, so a simple String could be used as a message, but case objects and case classes are most often used for messages. Actors are message-driven, that is, they are passive and do nothing unless and until you send messages to them. Once you send them a message, they pick a thread from the thread pool which is also known as a dispatcher, process the message, and release the thread back to the thread pool.
As soon as you send the message to the actor, it receives the message, picks up an underlying Java thread from the thread pool, does it’s work, and releases the thread. The actors never block your current thread of execution, thus, they are asynchronous by nature.
An actor represents an independent computation unit. Some important characteristics are:
- an actor encapsulates its state and part of the application logic
- actors interact only through asynchronous messages and never through direct method calls
- each actor has a unique address and a mailbox in which other actors can deliver messages
- the actor will process all the messages in the mailbox in sequential order (the default implementation of the mailbox being a FIFO queue)
- the actor system is organized in a tree-like hierarchy
- an actor can create other actors, can send messages to any other actor and stop itself or any actor is has created.
Properties of Actors:
- State: Actor objects will typically contain some variables which reflect possible states the actor may be in. This can be an explicit state machine or it could be a counter, set of listeners, pending requests, etc. These data are what make an actor valuable, and they must be protected from corruption by other actors. Akka actors conceptually each have their own light-weight thread, which is completely shielded from the rest of the system. This means that instead of having to synchronize access using locks you can just write your actor code without worrying about concurrency at all. Behind the scenes Akka will run sets of actors on sets of real threads, where typically many actors share one thread, and subsequent invocations of one actor may end up being processed on different threads.
2. Behavior: Every time a message is processed, it is matched against the current behavior of the actor. Behavior means a function which defines the actions to be taken in reaction to the message at that point in time, say forward a request if the client is authorized, deny it otherwise. This behavior may change over time.
3. MailBox: An actor’s purpose is the processing of messages, and these messages were sent to the actor from other actors (or from outside the actor system). The piece which connects sender and receiver is the actor’s mailbox: each actor has exactly one mailbox to which all senders enqueue their messages. Enqueuing happens in the time-order of send operations, which means that messages sent from different actors may not have a defined order at runtime due to the apparent randomness of distributing actors across threads. Sending multiple messages to the same target from the same actor, on the other hand, will enqueue them in the same order.
Example of Actor using Scala:
- class SummingActor extends Actor {
// state inside the actor
var sum = 0
// behaviour which is applied on the state
override def receive: Receive = {
// receives message an integer
case x: Int => sum = sum + x
println(s”my state as sum is $sum”)
// receives default message
case _ => println(“I don’t know what
are you talking about”)
}
}
Here, we are not creating an actor, we are only defining the state and behavior.
Akka prevents us from getting direct access to an Actor and thus ensures that asynchronous messaging is the only way to interact with it: It’s impossible to invoke a method on an actor.
It’s also worth pointing out that sending a message to an actor and processing of that message by the actor are two separate activities, which most probably happen on different threads — of course Akka takes care of the necessary synchronization to make sure that any state changes are visible to any thread. Therefore Akka sort of allows us to program in a single-threaded illusion, i.e. we don’t — and mustn’t — use any primitives for synchronization like volatile or synchronized in our actor code.
Actor Hierarchy
An actor in Akka always belongs to a parent. Typically, you create an actor by calling context.actorOf()
. Rather than creating a “freestanding” actor, this injects the new actor as a child into an already existing tree: the creator actor becomes the parent of the newly created child actor.
As illustrated below, all actors have a common parent, the user guardian. New actor instances can be created under this actor using system.actorOf()
e.g if we create an actor named someActor
with system.actorOf(…, "someActor")
, its reference will include the path /user/someActor
.
/
the so-called root guardian. This is the parent of all actors in the system, and the last one to stop when the system itself is terminated./user
the guardian. This is the parent actor for all user created actors. Every actor you create using the Akka library will have the constant path/user/
prepended to it./system
the system guardian.
ActorSystem
In Akka, an ActorSystem is the starting point of any Akka application that we write.
Technically, an ActorSystem is a heavyweight structure per application, which allocates n number of threads. Thus, it is recommended to create one ActorSystem per application, until we have a reason to create another one. An actor system is a hierarchical group of actors which share common configuration, e.g. dispatchers, deployments, remote capabilities and addresses. It is also the entry point for creating or looking up actors.
Actor Communication
Actors have methods to communicate with each other actors like tell (!) or ask (?) where the first one is fire and forget and the second returns a Future which means the response will come from that actor in the future.
ask
will send the message and return a future, which can be awaited until timeout or a reply is received, tell
will send the message and return immediately.
In the case of ask
, the actor that receives the message should reply to the sender when the operation is completed.
Forward
Forwarding messages is similar to “tell”-ing messages with a subtle difference — a “forward” does not modify the sender of the messages. If a message is sent from A->B->C actor then If we use forward then for C sender will B but if we use tell then sender will come as A.
Dispatchers and executors
The dispatcher in your actor system is responsible for assigning a thread to an actor instance so that it can do work. When an actor instance has no messages to process, it just sits there idle, not taking up any threads. This is why it’s okay to have so many actor instances within your system at once (as long as they are not all trying to do work all the time). They don’t take any resources, aside from a small amount of heap memory, unless they are processing a message.
Akka is an event-driven system. Work is only done in response to an event being received. The event in this case is a message being received in the mailbox of an actor instance. When the dispatcher sees this, a thread is allocated, and the work of processing that message can begin. When there are no messages to process, the threads sit idle until something happens and there is work to be done.
So where does the dispatcher get those threads from? A dispatcher will always be paired up with an executor, which will define what kind of thread pool model is used to support the actors. In fact, MessageDispatcher
in Akka inherits from ExecutionContext.
Simple Hello World Application
import akka.actor.Actorimport akka.actor.ActorSystemimport akka.actor.Propsclass HelloActor extends Actor {def receive = {case "hello" => println("hello back at you") case _ => println("huh?")}}object Main extends App {val system = ActorSystem("HelloSystem")// default Actor constructorval helloActor = system.actorOf(Props[HelloActor], name = "helloactor")helloActor ! "hello"}
Here’s a quick description of this example:
- Import all the Akka actor classes you need.
- Define an Actor, defining behavior in the special “receive” method.
- Create a “main” object where you can test things.
- You need an
ActorSystem
to get things started, so create one, with an arbitrary string. - You create an Actor instance with
actorOf
, and that line shows the syntax for an Actor whose default constructor takes no arguments. - Now that we have an instance of an actor, we send messages.