A gentle
introduction
into AKKA and
the actor model
Mike Kotsur, 2016 github.com/mkotsur
Goals• A problem (one of them) and traditional solutions
• Actor Model (since 1973)
• Akka Actors
• The rest of Akka goodies
• To choose, or not to choose (Akka)
2 ways
• Simplicity
• Speed
[1] Herb Sutter. The Free Lunch Is Over: A Fundamental Turn Toward Concurrency in Software, 2005. – http://www.gotw.ca/publications/concurrency-ddj.htm
Concurrency is the next major revolution
in how we write software [1].
More CPUs — more threads
Your code:if (check (seat, row))
book (seat, row)
More CPUs — more threads
check (1, 10)
book (1, 10)
check (1, 10)
book (1, 10)
check (1, 10)
check (1, 10)
book (1, 10)
book (1, 10)
:-(
But we have smart databases!
• Managing shared state requires synchronisation;
• Which leads to keeping the threads busy waiting;
• The granularity control is limited;
• There are different solutions to the problem: • synchronized,
• database,
• something else?
– Carl Hewitt
“An actor is the fundamental unit of computation which embodies the 3 things –
processing, storage and communications – that are essential to computation.”
What an actor is not…Future Thread Actor
Processing + + +
Storage + +
Communication +
What an actor can do?
• Designate how to handle the next message it receives;
• Create new actors;
• Send messages to actors it knows.
Messages
• Processed one at a time;
• May be not delivered in the same order as sent;
• At-most-once delivery guarantee.
Why actors are interesting?
• Unified model for concurrency and parallelism;
• Out-of-the box multi-server support.
Could we use this to sell tickets?
Actors in 22 lines
https://gist.github.com/viktorklang/2362563
➡ Actors
• Persistence
• Networking
• Clustering
• Streams
• HTTP
• Testkits
object TicketDeskActor {
case class BookTicket(eventId: String, row: Int, seat: Int)
case class TicketBooked(ticketId: Long)
}
class TicketDeskActor extends Actor {
override def receive: Receive = { case BookTicket(eventId, row, seat) => ??? }
}
val mySystem = ActorSystem("TheatreApp")
val myProps: Props = Props[TicketDeskActor]
val myActor: ActorRef = mySystem. actorOf(myProps, "ticket-desk")
print(s"Created ${myActor.path}") // Created akka://TheatreApp/user/ticket-desk
mySystem.terminate()
Actor recipe
Actor path
Actor ref
val result: Future[Any] = myActor ? BookTicket(…)
myActor ! BookTicket(…)
Tell (a.k.a. “fire and forget”)
Ask
More about messaging tomorrow
class TicketDeskActor extends Actor {
override def receive: Receive = { case msg @ BookTicket(eventId, _, _) => val evtActorName = s"event-$eventId" val evtActorProps = Props(new EventActor(eventId))
val eventActorRef = context.child(evtActorName).getOrElse { context.actorOf(evtActorProps, evtActorName) }
eventActorRef forward msg }
}
class EventActor(eventId: String) extends Actor with ActorLogging {
override def preStart(): Unit = { log.info(s"Starting an event actor for $eventId") }
override def receive: Receive = { case BookTicket(_, seat, row) => log.info("Updating the state with the newly booked ticket") }
}
[INFO] [04/03/2016 20:17:57.907] [TheatreApp-akka.actor.default-dispatcher-3] [akka://TheatreApp/user/ticket-desk/event-JustinBieber] Starting an event actor for JustinBieber
Actor path
Logging trait
Changing the behaviour: delayed sales example.
object DelayedEventActor { case object SaleIsNotStartedYet case object StartSale }
class DelayedEventActor(eventId: String, saleStarts: LocalDateTime) extends EventActor(eventId) with ActorLogging {
var cancellable: Option[Cancellable] = None
override def preStart(): Unit = { super.preStart() val delay = Duration.between(saleStarts, LocalDateTime.now()) import context.dispatcher cancellable = Some( context.system.scheduler.scheduleOnce(delay.toNanos nanos, self, StartSale) ) }
override def postStop(): Unit = { super.postStop() cancellable.foreach(_.cancel()) }
override def receive: Receive = { case StartSale => context.become(super.receive) case msg: BookTicket => log.warning(s"Trying to book a ticket too early") sender() ! SaleIsNotStartedYet }
}
Scheduling a message to self
Changing the behaviour
Extends the normal EventActor
Lifecycle: stopping• Actors can be terminated by stop() or PoisonPill
message;
• postStop() method is called;
• The rest of the messages is sent to the dead letters;
• If an actor is terminated, all its children are terminated as well.
Lifecycle: fault tolerance
• Supervision by parent actors;
• On exception: Resume, Restart, Stop, or Escalate, based on the supervision strategy;
But what about persistence?
Traditional way: Persisting the state
Event sourcing: persisting the events
Clustering• Actor model is very good for clustering;
• Akka has very good clustering support:
• Sharding;
• Cluster singleton;
• Other stuff. Check it out.
Is Akka a good choice for you?
Paul Chiusano: Actors are overly nondeterminstic :-(
Roland Kuhn: …they are the essence of concurrency and therefore by nature as non-
deterministic as they can be.
Actors are overly nondeterminstic, Paul Chiusano's blog, 2013 — http://pchiusano.blogspot.nl/2013/09/actors-are-overly-nondeterminstic.html
How to start?
• Find a task where the advantage is obvious!
• Micro service Pet-project scale first?
• The structure and lifecycle is important!
What next?
Thanks.
Some code is here: https://github.com/mkotsur/actors-introduction
And let’s chat!