Akka is framework for writing highly concurrent, distributed and fault-tolerant applications.  Though written in Scala, it’s usable in Java as well.

Akka is based on the Actor model, where we have message passing between actors with immutable state. Message passing between actors can be achieved using either the tell or ask pattern. In the tell pattern, messages are fired without waiting for response, while in the ask pattern messages are sent with a response expected in future. In Akka messages are sent asynchronously, in non-blocking fashion. One other feature of Akka is actor supervision and monitoring. With actor supervision, a parent actor watches it’s children for exceptions that can result in either restart, resumption, or stoppage of the child actor. The exception can also lead to escalation of the exception to the parent of the parent actor. Another feature, closely related to supervision is deathwatch. In this situation, an actor simply watches an actor marked within it, which may not be a child actor, for termination, and perform appropriate action if the actor terminates. These features represent the fault-tolerance feature of Akka. Another consideration in Akka is how does actor locate one another in case where they are on the same JVM or different JVMs running on same or different nodes. Akka solve this problem using a feature know as location transparency. Location transparency enables actors to work agnostic of where the messages they receive come from. In this tutorial, we explore examples with the message passing between actors using either tell or ask pattern, supervision, and location transparency.

ActorSystem-01

Message Passing

Actors are created either as direct child of ActorSystem or actor context. So, in the first case, to create an actor we first create the ActorSystem,  and then we obtain a reference to the actor by using the actorOf property of the ActorSystem. Actors are not accessed directly but through their ActorRef. This is illustrated below:

[code language=”scala”] import actor.Actor.{ActorRef, Props}
import akka.actor.Actor

object ChildActor {

case class Greet(String msg)
case class Error()

def props = Props (new ChildActor())

}

class ChildActor extends Actor{

def receive = {

case Greet (msg) => sender ! msg
case Error => throw new Exception

}

}

import akka.actor.ActorSystem
import akka.pattern.tell
import ChildActor._
import actor.Actor.{ActorRef, Props}

object Sample extends App {

val system = ActorSystem(“System”)

val childActor = system.actorOf( ChildActor.props)

childActor ! Greet(“Hello World”)

system.shutdown()

}

[/code]

In the illustration, we created a child actor which when it receives a “Greet” command responds by sending the received message back to the sender using the tell pattern. To run the application, we created created an object that extends App, and within it we initialized the ActorSystem and obtain the ActorRef for our childActor. After, we send a message to childActor using the tell pattern. Being a tell pattern the message is fired without waiting for responds. To show the same program using the ask pattern we proceed as follow:

[code language=”scala”] import akka.util.Timeout
import scala.concurrent.duration._
import akka.pattern.ask
import akka.dispatch.ExecutionContexts._
import ChildActor._

implicit val ec = global

object Sample extends App {

val system = ActorSystem(“System”)

val childActor = system.actorOf(ChildActor.props)

implicit val timeout = Timeout(25 seconds)

val future = childActor ? Greet (“Hello world”)

future.map { result => println(“Message received:” + result)}

}

system.shutdown

}[/code]

Fault tolerance and Supervision

Strategy represents how a parent actor responds to message it receives from its child actors. There are two strategies that Akka use in respond to message it receives, we have:

  1. OneForOneStrategy: applies to the one child actor in question
  2. AllForOneStrategy: applies to all child actors of the parent.

In applying the strategy, the parent actor can do one of the following:

  • Resume the child: With this strategy, the parent resumes child ( And its children) , maintaining its internal state since it it is not corrupted by the error in question, and can continue working
  • Restart the child: With this strategy, the parent restart the child actor after resetting its internal state since it has been corrupted by error
  • Stop the child:  With this strategy, the parent stops child completely.
  • Escalate to supervisor: In this case the parent terminates and escalate the error to its supervisor to handle the error

A simple example of OneForOneStrategy is illustrated as follow:

[code language=”scala”]

import akka.actor.OneForOneStrategy
import akka.actor.SupervisorStrategy._
import scala.concurrent.duration._
import akka.actor.Actor

object Supervisor {

case class Greet()
case class Error()

def props = Props (new Supervisor())

}

class Supervisor extends Actor {

import akka.actor.OneForOneStrategy
import akka.actor.SupervisorStrategy._
import scala.concurrent.duration._
import ChildActor._

val childActor = context.actorOf( ChildActor.Props)

override val supervisorStrategy =

OneForOneStrategy() {

case _: ArithmeticException      => Resume
case _: NullPointerException     => Restart
case _: IllegalArgumentException => Stop
case _: Exception                => Escalate

}

def receive = {

case Greet => childActor ! Greet(“Hello World”)
case Error => childActor ! Error

}

}

[/code]

Now, test the supervisor Actor:

[code language=”scala”] import akka.actor.ActorSystem

object Sample extends App {

import akka.pattern.tell
import ChildActor._
import actor.Actor.{ActorRef, Props}

val system = ActorSystem(“System”)

val supervisor = system.actorOf( Supervisor.props)

supervisor ! Error

system.shutdown()

}

[/code]

When the supervisor sends the error message, the child actor receives the message and throws an exception, which is detected in the supervisor and appropriate action is taken

Location Transparency

Location transparency enable actors to work regardless of whether the message they receive originated from a sender in the same JVM as the receiving actor or different JVM, which could be on the same node or different nodes. The location of the remote actor only need to be specified through a configuration file, application.conf as shown:

[code language=”scala”] akka {
actor {
provider = “akka.remote.RemoteActorRefProvider”
}
remote {
transport = “akka.remote.netty.NettyRemoteTransport”
netty {
hostname = “127.0.0.1”
port = 2552
}
}
}[/code]

References

Concurrency and Fault Tolerance Made Easy: An Akka Tutorial with Examples
https://www.toptal.com/scala/concurrency-and-fault-tolerance-made-easy-an-intro-to-akka

Show Buttons
Hide Buttons
Contact Us