Akka patterns

36
Akka Patterns and anti- patterns Roman Timushev 2014

Transcript of Akka patterns

Page 1: Akka patterns

AkkaPatterns and anti-

patterns

Roman Timushev2014

Page 2: Akka patterns

About me

• Work at Qubell Inc.

• Scala since 2011

• Akka since 1.x

• Now: Scala, Akka, Play, MongoDB, RabbitMQ

Page 3: Akka patterns

Agenda• Anti-patterns

• Ask is so cool

• Dependency injection for the win

• I like to block it, block it

• No mutable state is so functional

• Patterns

• Actor initialization

• Safe closures

• Flow control

Page 4: Akka patterns

Ask is so cool

Page 5: Akka patterns

Tell, don’t ask:classic code

class FineService { def getFine(v: Double) = { v * greedinessService.getGreediness() }}

class GreedinessService { def getGreediness(): Double = { db.query("select ...") }}

class DbService { def query(s: String): Double = ???}

Page 6: Akka patterns

Tell, don’t ask:classic actor code

class FineService extends Actor { def receive = { case GetFine(v) => (greedinessService ? GetGreediness) .mapTo[Double].map(_ * v).pipeTo(sender()) }}

class GreedinessService extends Actor { def receive = { case GetGreediness => (db ? Query("select ...")) .mapTo[Double].pipeTo(sender()) }}

Timeout

Timeout

Page 7: Akka patterns

Tell, don’t ask

• Works perfect until loaded

• Timeouts everywhere

• equal — you don’t get the exact cause

• different — unmanageable hell

• Use thoughtful!

Page 8: Akka patterns

Tell, don’t ask:invert control flow

class FineService extends Actor { var greediness: Double = 0 def receive = { case GetFine(v) => sender() ! greediness case SetGreediness(g) => greediness = g }}

class GreedinessService extends Actor { def receive = { case ReloadGreediness => (db ? Query("select ...")) .mapTo[Double].map(SetGreediness).pipeTo(fineService) }}

Page 9: Akka patterns

Dependency injection for the win

Page 10: Akka patterns

Dependency injection:actor references

trait AkkaComponent { def actorSystem: ActorSystem}

trait DividerComponent { def divider: ActorRef}

trait CalculatorComponent { def calculator: ActorRef}

Page 11: Akka patterns

Dependency injection:actor references

trait CalculatorComponentImpl extends CalculatorComponent { this: AkkaComponent with DividerComponent =>

lazy val calculator = actorSystem.actorOf(Props(new Calculator))

class Calculator extends Actor { override val supervisorStrategy = ??? def receive = { case m: Divide => divider forward m } }

}

Page 12: Akka patterns

Dependency injection:actor props

trait AkkaComponent { def actorSystem: ActorSystem}

trait DividerComponent { def divider: Props}

trait CalculatorComponent { def calculator: ActorRef}

Page 13: Akka patterns

I like toblock it, block it

Page 14: Akka patterns

Blockingclass Blocker extends Actor {

import context._

def receive = { case GetThreadCount => sender() ! Await.result(sleep(), 10 seconds) // or sender() ! spinAwait(sleep(), 10 seconds) }

def sleep() = after(10 millis, system.scheduler)(Future.successful())

}

Page 15: Akka patterns

Blocking• Run 1000 actors

• Send 1 request to every actor

• Default executor (fork-join, parallelism-max = 64)

Spin Await

Threads 28 ~ 1000

Success rate 60% 100%

Total time 410s 0.5s

Page 16: Akka patterns

No mutable stateis so functional

Page 17: Akka patterns

Stateless actorclass StatelessActor extends Actor { def receive = { case Divide(a, b) => sender() ! (a / b) }}

class Calculator extends Actor { def receive = { case Calculate => context.actorOf(Props[StatelessActor]) ! Divide(84, 2) case result: Int => println(s"The answer is $result") }}

Page 18: Akka patterns

Just future

class Calculator extends Actor { def receive = { case Calculate => Future { 84 / 2 } pipeTo self case result: Int => println(s"The answer is $result") }}

Page 19: Akka patterns

Actor initialization

Page 20: Akka patterns

Actor initialization• Actor parameters:

• Actor constructor

• Initializing message

• Initialize yourself

• Long initialization:

• Blocking

• Switching actor behavior with dropping

• Switching actor behavior with stashing

Page 21: Akka patterns

Actor initialization:initializing message

class FineCalculator extends Actor {

var greediness = none[Double]

def receive = { case SetGreediness(g) => greediness = some(g) case GetFine(v) => sender() ! (v * greediness.get) }

}

Page 22: Akka patterns

Actor initialization:parameterized receive

class FineCalculator extends Actor {

def receive = uninitialized

def uninitialized: Receive = { case SetGreediness(m) => context.become(initialized(m)) }

def initialized(greediness: Double): Receive = { case GetFine(x) => sender() ! (x * greediness) }

}

Page 23: Akka patterns

Scala.Rxval a = Var(1)val b = Var(2)val c = Rx{ a() + b() }val cObs = Obs(c) { println(c()) }

// prints 3assert(c() == 3)

a() = 4

// prints 6assert(c() == 6)

Page 24: Akka patterns

Actor initialization:reactive receive

class FineCalculator extends Actor {

val greediness = Var(none[Double]) val actorReceive = Rx { greediness().fold(uninitialized)(initialized) } val actorReceiveObs = Obs(actorReceive) { context.become(actorReceive()) }

def receive = uninitialized def uninitialized: Receive = { case SetGreediness(g) => greediness() = some(g) } def initialized(g: Double): Receive = uninitialized orElse { case GetFine(v) => sender() ! (v * g) }

}

Page 25: Akka patterns

Actor initialization:initialize yourself

class FineCalculatorCallback extends Actor {

(context.actorSelection("..") ? GetGreediness) .mapTo[Double].map(SetGreediness).pipeTo(self)

def receive = uninitialized

def uninitialized: Receive = { case SetGreediness(m) => context.become(initialized(m)) }

def initialized(greediness: Double): Receive = { case GetFine(x) => sender() ! (x * greediness) }

}

Page 26: Akka patterns

Safe closures

Page 27: Akka patterns

Closing over actor state

When you use

• Future.apply

• future methods (map, flatMap etc.)

• scheduler

And access

• this

• vars

• context

• sender()

you are asking for trouble

Page 28: Akka patterns

Unsafe closures

class Computer extends Actor { var counter = 0

override def receive: Receive = { case Inc => val requester = sender() Future { counter += 1 // unsafe! requester ! counter } }

}

Page 29: Akka patterns

Local executortrait LocalExecutor { this: Actor =>

implicit val executor = new ExecutionContext { override def execute(runnable: Runnable): Unit = self ! runnable override def reportFailure(t: Throwable): Unit = self ! t }

def receive: Receive = { case r: Runnable => r.run() case t: Throwable => throw t }

}

Page 30: Akka patterns

Safe closures

class Computer extends Actor with LocalExecutor { var counter = 0

override def receive: Receive = super.receive orElse { case Inc => val requester = sender() Future { counter += 1 // safe requester ! counter } }

}

Page 31: Akka patterns

Flow control

Page 32: Akka patterns

Flow control:push

• The simplest option, use by default

Page 33: Akka patterns

Flow control:throttle

• You should know maximum message rate in advance

• TimerBasedThrottler (akka-contrib)

Page 34: Akka patterns

Flow control:push with ack / pull

• Acknowledge individual messages or batches

• Difference: who is first

Page 35: Akka patterns

Questions?

Page 36: Akka patterns

References