Demystifying the Akka Actor Model

What's an actor model?

The actor model in computer science is a mathematical model of concurrent computation that treats "actors" as the universal primitives of concurrent computation. In response to a message that it receives, an actor can: make local decisions, create more actors, send more messages, and determine how to respond to the next message received. (wiki link).

Creating Actors

  • Extend AbstractActor class
  • Set the "initial behavior" by calling the receive method in the AbstractActor.
public class MyActor extends AbstractActor{
    ...

    @Override
    public Receive createReceive(){
        return receiveBuilder().match(...
            ...
        })
        ...
        .build();
    }
}
  • receive message loop is exhaustive so we need to provide a pattern match for all messages that it can accept. Add a default case to handle unknown messages.

  • Apart from receive method, AbstractActor provides the following APIs:

    • getSelf reference to the ActorRef of the actor.
    • getSender reference sender Actor of the last received message.
    • supervisorStrategy - use for supervising child actors.
    • getContext - exposes contextual information such as:
      • factory methods to create child actors (actorOf)
      • parent supervisor
      • supervised children
      • system that the actor belongs to

What are Props?

Props is a configuration class to specify options for the creation of actors.
Example:

Props props = Props.create(MyActor.class);

Actors are created by passing a Props instance into the actorOf factory method which is available on ActorSystem and ActorContext. Example:

final ActorSystem system = ActorSystem.create("MySystem", config);
final ActorRef myActor = system.actorOf(Props.create(MyActor.class), ",myactor");

The ActorRef is serializable and network-aware.

Difference between ActorRef and ActorSelection

ActorRef represent an incarnation(path and UID) not just a given path. When an actor is stopped and a new one is created, it will point to the new incarnation and not the old one. While ActorSelection is completely oblivious to which incarnation is currently occupying it.

Actor Lifecycle

Actor Lifecycle
Actor Lifecycle

Sending and Receiving messages

Messages are sent to an actor through the following methods(patterns):

  • Tell: Fire-forget

    • Send a message asynchronously and return immediately.
    • No blocking waiting for a message and gives the best concurrency and scalability characteristics.
      target.tell(message, getSelf());
  • Ask: Send And Receive Future

    • Sends a message asynchronously and returns a Future representing a possible reply.
      CompletableFuture<Object> future = ask(actor1, "request", 10).toCompletableFuture();

Messages can be forwarded to another actor (keeping the original sender reference/address even though the message is going through a mediator). Useful when actors work as routers, load-balancers, replicators etc.

target.forward(result, getContext());

Recommended practice for receiving messages is to use small methods. For example:

@Override
public Receive createReceive(){
    return receiveBuilder()
        .match(Msg1.class, this::receiveMsg1)
        .match(Msg2.class, this::receiveMsg2)
        .match(Msg3.class, this::receiveMsg3)
        .build();
}

Replying to messages:

getSender().tell(s, getSelf());

When an actor is terminated the actor suspends its mailbox processing and sends a stop command to all its children. Then it keeps processing the internal terminal notifications from its children until the last one is gone, finally terminating itself. If one of the actors do not respond, this whole process will be stuck.

When an exception is thrown:

  • What happens to the message? -- The message is lost and is not put back on the mailbox.
  • What happens to the mailbox? -- Nothing happens. If the actor is restarted, mailbox will be there with all the messages.
  • What happens to the actor? -- If the code within an actor throws exception, the actor is suspended and the supervision is started.

Reference: akka documentation