Akka patterns

4,268 views

Published on

Published in: Software, Business, Technology

Akka patterns

  1. 1. Akka Patterns and anti-patterns Roman Timushev 2014
  2. 2. About me • Work at Qubell Inc. • Scala since 2011 • Akka since 1.x • Now: Scala, Akka, Play, MongoDB, RabbitMQ
  3. 3. 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
  4. 4. Ask is so cool
  5. 5. 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 = ??? }
  6. 6. 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
  7. 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. 8. 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) } }
  9. 9. Dependency injection for the win
  10. 10. Dependency injection: actor references trait AkkaComponent { def actorSystem: ActorSystem } trait DividerComponent { def divider: ActorRef } trait CalculatorComponent { def calculator: ActorRef }
  11. 11. 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 } } }
  12. 12. Dependency injection: actor props trait AkkaComponent { def actorSystem: ActorSystem } trait DividerComponent { def divider: Props } trait CalculatorComponent { def calculator: ActorRef }
  13. 13. I like to block it, block it
  14. 14. 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()) }
  15. 15. 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
  16. 16. No mutable state is so functional
  17. 17. 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") } }
  18. 18. Just future class Calculator extends Actor { def receive = { case Calculate => Future { 84 / 2 } pipeTo self case result: Int => println(s"The answer is $result") } }
  19. 19. Actor initialization
  20. 20. Actor initialization • Actor parameters: • Actor constructor • Initializing message • Initialize yourself • Long initialization: • Blocking • Switching actor behavior with dropping • Switching actor behavior with stashing
  21. 21. 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) } }
  22. 22. 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) } }
  23. 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. 24. 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) } }
  25. 25. 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) } }
  26. 26. Safe closures
  27. 27. 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
  28. 28. Unsafe closures class Computer extends Actor { var counter = 0 override def receive: Receive = { case Inc => val requester = sender() Future { counter += 1 // unsafe! requester ! counter } } }
  29. 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. 30. 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 } } }
  31. 31. Flow control
  32. 32. Flow control: push • The simplest option, use by default
  33. 33. Flow control: throttle • You should know maximum message rate in advance • TimerBasedThrottler (akka-contrib)
  34. 34. Flow control: push with ack / pull • Acknowledge individual messages or batches • Difference: who is first
  35. 35. Questions?
  36. 36. References

×