Introduction to Akka
by Jan Van Sweevelt
The Challenge
- Clock speed stopped growing since 2006
- Moore's Law still applies but only the number of cores in a single chip is increasing
The Challenge (2)
Modern systems must be:
- Fault tolerant
- highly concurrent
- truly scalable
Issue: Shared Mutable State
- Multithreaded programs are hard to write and test
- Non-deterministic
- Race conditions
- Locks are hard to use
The predominant approach to concurrency today is that of shared mutable state – a large number of stateful objects whose state can be changed by multiple parts of your application, each running in their own thread. Typically, the code is interspersed with read and write locks, to make sure that the state can only be changed in a controlled way and prevent multiple threads from mutating it simultaneously. At the same time, we are trying hard not to lock too big a block of code, as this can drastically slow down the application.
More often than not, code like this has originally been written without having concurrency in mind at all – only to be made fit for a multi-threaded world once the need arose. While writing software without the need for concurrency like this leads to very straightforward code, adapting it to the needs of a concurrent world leads to code that is really, really difficult to read and understand.
The core problem is that low-level synchronization constructs like locks and threads are very hard to reason about. As a consequence, it’s very hard to get it right: If you can’t easily reason about what’s going on, you can be sure that nasty bugs will ensue, from race conditions to deadlocks or just strange behaviour – maybe you’ll only notice after some months, long after your code has been deployed to your production servers.
We need a new high level programming model
- easier to understand
- deterministic
- no shared/mutable state
- fully utilize multi-core processors
Actors
- Formalized in 1973 by Carl Hewitt
- First commercial use by Ericsson, that invented Erlang
- Akka is an Actor Model implementation in Scala
Actors (2)
- Actors instead of Objects
- No shared state between Actors
- Asynchronous message passing
- Location Transparancy
-
write highly performant concurrent code that is easy to reason about
Actors (3)
Actor uses
- a thread
- an object instance or component
- a callback
- a singleton
- a service
- a router, load-balancer
- a Finite State Machine
-
lots of light-weight entities called actors
-
responsible for only a very small task, and is thus easy to reason about
-
communicate with messages
Actor Code
import akka.actor.ActorSystem
import akka.actor.Actor
import akka.actor.Props
class myActor extends Actor {
def receive = {
case "Hello" => sender ! "world"
case x: String => println("Hello, " + x)
case _ =>
}
}
val system = ActorSystem("mySystem")
val myActor = system.actorOf(Props[myActor], "myactor1")
not use new to create actor objects, we use the actorsystem and have an actorref returned
Send Messages
Tell: Fire and Forget
myActor ! "Jan"
Ask: Send and Receive Future
val future = myActor ? "Hello"
What about the Future
In the Scala Standard Library, 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).
Synchronous Access
implicit val timeout = Timeout(5 seconds)
val future = myActor ? "Hello"
val result = Await.result(future, timeout.duration).asInstanceOf[String]
Asynchronous Access
With the map method
implicit val timeout = Timeout(5 seconds)
val future = myActor ? "Hello"
future.map {result => println(result)}
Asynchronous Access
With callbacks
implicit val timeout = Timeout(5 seconds)
val future = myActor ? "Hello"
future.onSuccess {
case result: String => println(result)
}
onFailure and onComplete callbacks are provided too
Routers
Routers are actors that "routes" messages to other actors. Akka offers different
routing strategies
- Round Robin Router
- Random Router
- Smallest Mailbox Router
- Broadcast Router
Creating a Router
val router = system.actorOf(Props[MyActor]
.withRouter(RoundRobinRouter(nrOfInstances = 5)))
router ! message
Supervision
- Actors are organised in a hierarchical structure
- Supervisors are regular actors that monitors other actors
- When an actor creates another actor, it automatically becomes its supervisor
- Monitored actors can be supervisors themselves too
- Monitoring is based on "let it crash" concept (monitored actors throw an exception)
- Supervisors define strategy how to handle the exceptions
Every actor has a parent and every actor can create children.
The actor model is meant to help you achieve a high level of fault tolerance
So how do we we deal with failure
ActorSystem an actor ? no it is the guardian actor
explain via system.actorOf from example
Supervision (2)
Two kind of Strategies
- OneForOneStrategy: if one of the supervised children goes down, only that child is acted on
- OneForAllStrategy: if one of the supervised children goes down, all supervised children are acted on
Supervision (3)
override val supervisorStrategy =
OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1 minute) {
case _: ArithmeticException ⇒ Resume
case _: NullPointerException ⇒ Restart
case _: IllegalArgumentException ⇒ Stop
case _: Exception ⇒ Escalate
}
The Actor Lifecycle
- preStart: Called when an actor is started, allowing you to do some initialization logic. The default implementation is empty.
- postStop: Empty by default, allowing you to clean up resources. Called after stop has been called for the actor.
- preRestart: Called right before a crashed actor is restarted. By default, it stops all children of that actor and then calls postStop to allow cleaning up of resources.
- postRestart: Called immediately after an actor has been restarted. Simply calls preStart by default.
Some links
- http://akka.io/
- https://github.com/vansweej/akkaintro