2. What is akka
“Akka is a toolkit and runtime for building highly concurrent, distributed, and resilient
message-driven applications” - LightBend
3. The actor model
An actor is a high level concurrency primitive that allows you to model concurrent
computations using entities that interact through message passing.
5. The actor ...
An actor is a container for State, Behavior, a Mailbox, Child Actors and a Supervisor
Strategy.
All of this is encapsulated behind an Actor Reference
6. The actor model ...
1. Actors have an address so they can send and receive messages
2. These messages are stored in mailboxes (default is a FIFPO queue)
3. Actors can create other actors.
4. Actors have defined behaviour : functions which define the actions to be taken in
reaction to the message at that point in time
5. Actors don’t always map one-on-one to threads, several actors could belong to an
execution context on one thread.
6. An actor should not talk directly to another but interact through an Actor reference
- which points to the actor
9. Creating an Actor
The Actor trait defines only one abstract method, the above mentioned receive, which implements the behavior of the actor
import akka.actor.Actor
import akka.actor.Logging
class MyActor extends Actor {
val log = Logging(context.system, this)
def receive = {
case "test" => log.info("received test")
case _ => log.info("received unknown message")
}
}
10. The Actor API
1. self reference to the ActorRef of the actor
2. sender reference sender Actor of the last received message, typically used as
described in Reply to messages
3. supervisorStrategy user overridable definition the strategy to use for supervising
child actors
4. context exposes contextual information for the actor and the current message, such
as:
- factory methods to create child actors (actorOf)s
- system that the actor belongs to
11. Messages and Immutability
Messages can be any kind of object but have to be immutable. Scala can’t enforce
immutability (yet) so this has to be by convention. Primitives like String, Int, Boolean are
always immutable.
Recommended approach is to use Scala case classes which are immutable and work well
with pattern matching at the receiver side
case class Register(user: User)
val message = Register(user)
12. The actor model … design patterns
ask pattern - future; await or asynchronous
tell pattern - fire/ forget
13. Tell :Fire-forget
This is the preferred way of sending messages. No blocking waiting for a message. This
gives the best concurrency and scalability characteristics.
The target actor can use the sender function to reply this to reply to the original sender,
by using sender() ! replyMsg
actorRef ! message
14. Ask: Send-And-Receive-Future
? sends a message asynchronously and returns a Future representing a possible reply.
import akka.pattern.{ ask, pipe }
import system.dispatcher
// The ExecutionContext that will be used
final case class Result(x: Int, s: String, d: Double)
case object Request
implicit val timeout = Timeout(5 seconds) // needed for `?` below
val f: Future[Result] =
actorC ? Request.mapTo[Double]
f pipeTo actorD
15. The example demonstrates ask together with the pipeTo pattern on futures.
It is completely non-blocking and asynchronous: ask produces a Future then
pipeTo installs an onComplete-handler on the future to affect the submission of the
aggregated Result to another actor.
We reply with:
Sender ! replyMsg()
Note: There are performance implications of using ask since something needs to keep
track of when it times out and there needs to be something that bridges a Promise into
an ActorRef. Always prefer tell for performance, and only ask if you must
16. Futures
A Future is a data structure used to retrieve the result of some concurrent operation. This result can be accessed
synchronously (blocking) or asynchronously (non-blocking).
Using an Actor‘s ? method to send a message will return a Future. When using non-blocking it is better to use the
mapTo method to safely try to cast a Future to an expected type. On Complete callback allows you to define
behaviour when the future completes:
import scala.concurrent.Future
import akka.pattern.ask
val future: Future[String] = ask(actor, msg).mapTo[String]
future onComplete {
case Success => //do something
case Failure => //do something
17. Become/Unbecome
Akka supports hotswapping the Actor’s implementation at runtime. Invoke the become
method to implement the new message handler:
class HotSwapActor extends Actor {
import context._
def angry: Receive = {
case "foo" => sender() ! "I am already angry?"
case "bar" => become(happy)
}
def happy: Receive = {
case "bar" => sender() ! "I am already happy :-)"
case "foo" => become(angry)
}
def receive = {
case "foo" => become(angry)
case "bar" => become(happy)
}
18. Fault Handling
Each actor is the supervisor of its children, and as such each actor defines fault handling supervisor strategy.
Depending on the nature of the work to be supervised and the nature of the failure, the supervisor can resume,
restart, stop the actor or escalate the failure.
You can create your own or use a combined strategy as shown below:
import akka.actor.OneForOneStrategy
import akka.actor.SupervisorStrategy._
import scala.concurrent.duration._
override val supervisorStrategy =
OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1 minute) {
case _: ArithmeticException => Resume
case t =>
super.supervisorStrategy.decider.applyOrElse(t, (_: Any) => Escalate)
}
19. Actors at AT
Use of ask with futures(with handlers for success/failure) and pattern matching
Ask with mapTo/pipeTo very useful for object transformations where the upstream
expects a response in a certain format
Routing to load balance particularly busy actors
FSMs
Delayed restarts with Backoff Supervisor
Querying and Persisting
Test cases
20. Actors at AT...cont
Very Reliable and Scales Well
If extensively tested few outages
Bugs are usually the result of high efficiency
All the advantages of the JVM with the benefits of functional programming mixed in
Escalate is used if the defined strategy doesn’t cover the exception that was thrown.
When the supervisor strategy is not defined for an actor the following exceptions are handled by default:
• ActorInitializationException will stop the failing child actor
• ActorKilledException will stop the failing child actor
• DeathPactException will stop the failing child actor
• Exception will restart the failing child actor
• Other types of Throwable will be escalated to parent actor
If the exception escalate all the way up to the root guardian it will handle it in the same way as the default strategy
defined above.
mplements the so-called exponen-
tial backoff supervision strategy, starting a child actor again when it fails, each time with a growing time delay
between restarts.
This pattern is useful when the started actor fails 1 because some external resource is not available,