Akka
Patterns and anti-patterns
Roman Timushev
2014
About me
• Work at Qubell Inc.
• Scala since 2011
• Akka since 1.x
• Now: Scala, Akka, Play, MongoDB, RabbitMQ
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
Ask is so cool
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 = ???
}
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
Tell, don’t ask
• Works perfect until loaded
• Timeouts everywhere
• equal — you don’t get the exact cause
• different — unmanageable hell
• Use thoughtful!
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)
}
}
Dependency injection
for the win
Dependency injection:
actor references
trait AkkaComponent {
def actorSystem: ActorSystem
}
trait DividerComponent {
def divider: ActorRef
}
trait CalculatorComponent {
def calculator: ActorRef
}
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
}
}
}
Dependency injection:
actor props
trait AkkaComponent {
def actorSystem: ActorSystem
}
trait DividerComponent {
def divider: Props
}
trait CalculatorComponent {
def calculator: ActorRef
}
I like to
block it, block it
Blocking
class 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())
}
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
No mutable state
is so functional
Stateless actor
class 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")
}
}
Just future
class Calculator extends Actor {
def receive = {
case Calculate =>
Future { 84 / 2 } pipeTo self
case result: Int =>
println(s"The answer is $result")
}
}
Actor initialization
Actor initialization
• Actor parameters:
• Actor constructor
• Initializing message
• Initialize yourself
• Long initialization:
• Blocking
• Switching actor behavior with dropping
• Switching actor behavior with stashing
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)
}
}
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)
}
}
Scala.Rx
val a = Var(1)
val b = Var(2)
val c = Rx{ a() + b() }
val cObs = Obs(c) { println(c()) }
// prints 3
assert(c() == 3)
a() = 4
// prints 6
assert(c() == 6)
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)
}
}
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)
}
}
Safe closures
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
Unsafe closures
class Computer extends Actor {
var counter = 0
override def receive: Receive = {
case Inc =>
val requester = sender()
Future {
counter += 1 // unsafe!
requester ! counter
}
}
}
Local executor
trait 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
}
}
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
}
}
}
Flow control
Flow control:
push
• The simplest option, use by default
Flow control:
throttle
• You should know maximum message rate in
advance
• TimerBasedThrottler (akka-contrib)
Flow control:
push with ack / pull
• Acknowledge individual messages or batches
• Difference: who is first
Questions?
References

Akka patterns

  • 1.
  • 2.
    About me • Workat Qubell Inc. • Scala since 2011 • Akka since 1.x • Now: Scala, Akka, Play, MongoDB, RabbitMQ
  • 3.
    Agenda • Anti-patterns • Askis 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
  • 4.
  • 5.
    Tell, don’t ask: classiccode class FineService { def getFine(v: Double) = { v * greedinessService.getGreediness() } } class GreedinessService { def getGreediness(): Double = { db.query("select ...") } } class DbService { def query(s: String): Double = ??? }
  • 6.
    Tell, don’t ask: classicactor 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
  • 7.
    Tell, don’t ask •Works perfect until loaded • Timeouts everywhere • equal — you don’t get the exact cause • different — unmanageable hell • Use thoughtful!
  • 8.
    Tell, don’t ask: invertcontrol 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) } }
  • 9.
  • 10.
    Dependency injection: actor references traitAkkaComponent { def actorSystem: ActorSystem } trait DividerComponent { def divider: ActorRef } trait CalculatorComponent { def calculator: ActorRef }
  • 11.
    Dependency injection: actor references traitCalculatorComponentImpl 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 } } }
  • 12.
    Dependency injection: actor props traitAkkaComponent { def actorSystem: ActorSystem } trait DividerComponent { def divider: Props } trait CalculatorComponent { def calculator: ActorRef }
  • 13.
    I like to blockit, block it
  • 14.
    Blocking class Blocker extendsActor { 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()) }
  • 15.
    Blocking • Run 1000actors • 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
  • 16.
    No mutable state isso functional
  • 17.
    Stateless actor class StatelessActorextends 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") } }
  • 18.
    Just future class Calculatorextends Actor { def receive = { case Calculate => Future { 84 / 2 } pipeTo self case result: Int => println(s"The answer is $result") } }
  • 19.
  • 20.
    Actor initialization • Actorparameters: • Actor constructor • Initializing message • Initialize yourself • Long initialization: • Blocking • Switching actor behavior with dropping • Switching actor behavior with stashing
  • 21.
    Actor initialization: initializing message classFineCalculator extends Actor { var greediness = none[Double] def receive = { case SetGreediness(g) => greediness = some(g) case GetFine(v) => sender() ! (v * greediness.get) } }
  • 22.
    Actor initialization: parameterized receive classFineCalculator 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) } }
  • 23.
    Scala.Rx val a =Var(1) val b = Var(2) val c = Rx{ a() + b() } val cObs = Obs(c) { println(c()) } // prints 3 assert(c() == 3) a() = 4 // prints 6 assert(c() == 6)
  • 24.
    Actor initialization: reactive receive classFineCalculator 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) } }
  • 25.
    Actor initialization: initialize yourself classFineCalculatorCallback 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) } }
  • 26.
  • 27.
    Closing over actorstate When you use • Future.apply • future methods (map, flatMap etc.) • scheduler And access • this • vars • context • sender() you are asking for trouble
  • 28.
    Unsafe closures class Computerextends Actor { var counter = 0 override def receive: Receive = { case Inc => val requester = sender() Future { counter += 1 // unsafe! requester ! counter } } }
  • 29.
    Local executor trait 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 } }
  • 30.
    Safe closures class Computerextends Actor with LocalExecutor { var counter = 0 override def receive: Receive = super.receive orElse { case Inc => val requester = sender() Future { counter += 1 // safe requester ! counter } } }
  • 31.
  • 32.
    Flow control: push • Thesimplest option, use by default
  • 33.
    Flow control: throttle • Youshould know maximum message rate in advance • TimerBasedThrottler (akka-contrib)
  • 34.
    Flow control: push withack / pull • Acknowledge individual messages or batches • Difference: who is first
  • 35.
  • 36.