16. Simpler Concurrent & Distributed
Systems
Actors and Streams let you build systems that scale up,
using the resources of a server more efficiently, and out,
using multiple servers.
Resilient by Design
Building on the principles of The Reactive Manifesto
Akka allows you to write systems that self-heal and stay
responsive in the face of failures.
High Performance
Up to 50 million msg/sec on a single machine. Small
memory footprint; ~2.5 million actors per GB of heap.
Elastic & Decentralized
Distributed systems without single points of failure. Load
balancing and adaptive routing across nodes. Event
Sourcing and CQRS with Cluster Sharding. Distributed
Data for eventual consistency using CRDTs.
Reactive Streaming Data
Asynchronous non-blocking stream processing with
backpressure. Fully async and streaming HTTP server
and client provides a great platform for building
microservices. Streaming integrations with Alpakka.
* How Actors Work?
18. class PersonActor(firstName: String, lastName: String) extends Actor with ActorLogging {
override def receive: Receive = {
case "eat-ice-cream" =>
log.info(s"$firstName has started eating ice cream. ")
Thread.sleep(5.seconds.toMillis)
log.info(s"$firstName has eaten his ice cream. ")
}
}
object MainApp extends App {
val system = ActorSystem("first-system")
val kid1: ActorRef = system.actorOf(Props(classOf[PersonActor], "Rudi", "Brglez"), "rudi")
val kid2: ActorRef = system.actorOf(Props(classOf[PersonActor], "Tinkara", "Brglez"), "tinkara")
val me: ActorRef = system.actorOf(Props(classOf[PersonActor], "Oto", "Brglez"), "oto")
kid1 ! "eat-ice-cream"
kid1 ! "eat-ice-cream"
kid1 ! "eat-ice-cream"
kid2 ! "eat-ice-cream"
me ! "drink-beer"
}
Actor that
represents a
person 👉
“Main” 👉
}business logic
AS initialisation 👉
Spawning actors{
Sending messages{
More messages{
19.
20. object Person {
trait PersonCommand
final case object EatIceCream extends PersonCommand
final case object FinishedEatingIceCream extends PersonCommand
def apply(firstName: String): Behavior[PersonCommand] = idle(firstName)
private def idle(firstName: String): Behavior[PersonCommand] =
Behaviors.receiveMessage[PersonCommand] {
case EatIceCream => eatingIceCream(firstName)
case _ => Behaviors.same
}
private def eatingIceCream(firstName: String): Behavior[PersonCommand] =
Behaviors.setup { context =>
Behaviors.withTimers[PersonCommand] { timers =>
context.log.info(s"$firstName has started eating his ice-cream. ")
timers.startSingleTimer(FinishedEatingIceCream, 3.seconds)
Behaviors.receiveMessage {
case FinishedEatingIceCream =>
context.log.info(s"$firstName is done with ice-cream. ")
idle(firstName)
case _ =>
context.log.info(s"$firstName: Sorry, I'm still eating,...")
Behaviors.same
}
}
}
}
“Idle state” 👉
“Eating state” 👉
Commands {
21. object Family {
sealed trait FamilyCommand
final case class AddPerson(firstName: String) extends FamilyCommand
final case class EatCake(firstName: String) extends FamilyCommand
def apply(): Behavior[FamilyCommand] = Behaviors.receive {
case (context, AddPerson(firstName)) =>
context.spawn(Person(firstName), firstName)
Behaviors.same
case (context, EatCake(firstName)) =>
context.child(firstName)
.map(_.asInstanceOf[ActorRef[Person.PersonCommand]])
.foreach(ref => ref ! Person.EatIceCream)
Behaviors.same
case _ => Behaviors.unhandled
}
}
Commands {
Child is created 👉
“EatIceCream” is sent to
child with given name 👉
22. object MainApp extends App {
val system = ActorSystem[Family.FamilyCommand](Family(), "family")
system ! Family.AddPerson("Rudi")
system ! Family.AddPerson("Tinkara")
system ! Family.AddPerson("Oto")
system ! Family.EatCake("Rudi")
system ! Family.EatCake("Tinkara")
system ! Family.EatCake("Rudi")
system ! Family.EatCake("Tinkara")
system ! "drink beer!"
}
👈 💥
Won’t actually compile since AkkaTyped required
proper types and Scala compiler won’t allow this “String”!
24. The Weather
Temperature
Example ☀
💥 Network Problems
💥 500 Error
💥 Missing tag!
💥 Out of memory
💥 Recipient is br0ken
Network is still down!!! 💥
25. object MainApp extends LazyLogging {
def main(args: Array[String]): Unit = {
implicit val system: ActorSystem = ActorSystem("temperature-system")
import system.dispatcher
val changeDetector: ActorRef = system.actorOf(Props(classOf[ChangeDetector]), "change-detector")
Source.tick(1.seconds, 2.seconds, "Tick")
.mapAsync(1) { _ => getMeasurement }
.alsoTo(Sink.foreach(v => logger.info(s"Latest measurement is ${v.getOrElse("none")} ")))
.mapAsync(1)(measurement => changeDetector.ask(measurement)(1.second))
.collect { case change: ChangeDetector.Changed => change }
.runWith(Sink.foreach(change =>
logger.info(s"Temperature change from ${change.from} to ${change.to}, ${direction(change)}.")))
}
private def getMeasurement(implicit system: ActorSystem, ec: ExecutionContext): Future[Measurement] =
for {
response <- Http().singleRequest(HttpRequest(uri = ARSOLjubljanaURL))
nodes <- Unmarshal(response.entity).to[NodeSeq]
measurement = (nodes "t").headOption.flatMap(_.text.toDoubleOption)
} yield measurement
private val direction: ChangeDetector.Changed => String = {
case ChangeDetector.Changed(from, to) if to > from => "up "
case ChangeDetector.Changed(from, to) if to < from => "down "
case _ => "impossible."
}
}
Every 2 seconds send “Tick” 👉
Execute request to Web Service 👉
In parallel write to output 👉
“Ask” actor to detect changes 👉
Only care about actual “changes” 👉
Run and see the changes 👉
This part is for issuing
request, dealing with
response and XML parsing
{
26. val getMeasurementsFlow: Flow[String, Measurement, NotUsed] =
RestartFlow.onFailuresWithBackoff(RestartSettings(10.seconds, 30.seconds, 0.3)) { () =>
Flow[String].mapAsyncUnordered(1) { _ => getMeasurement }
}
Source.tick(1.seconds, 2.seconds, "Tick")
.via(getMeasurementsFlow)
.alsoTo(Sink.foreach(v => logger.info(s"Latest measurement is ${v.getOrElse("none")} ")))
.mapAsync(1)(measurement => changeDetector.ask(measurement)(1.second))
.collect { case change: ChangeDetector.Changed => change }
.runWith(Sink.foreach(change =>
logger.info(s"The temperature has changed. From ${change.from} to ${change.to}, ${direction(change)}.")))
Pass “Tick” to Flow 👉
This Flow is
restarted if
failures occur 👉