PageRenderTime 64ms CodeModel.GetById 38ms RepoModel.GetById 1ms app.codeStats 0ms

/akka-docs/scala/routing.rst

https://github.com/d5nguyenvan/akka
ReStructuredText | 264 lines | 190 code | 74 blank | 0 comment | 0 complexity | 38a341386549cd78d304473e994aa3b0 MD5 | raw file
  1. Routing (Scala)
  2. ===============
  3. .. sidebar:: Contents
  4. .. contents:: :local:
  5. Akka-core includes some building blocks to build more complex message flow handlers, they are listed and explained below:
  6. Router
  7. ----------
  8. A Router is an actor that routes incoming messages to outbound actors.
  9. To use it you can either create a Router through the ``routerActor()`` factory method
  10. .. code-block:: scala
  11. import akka.actor.Actor._
  12. import akka.actor.Actor
  13. import akka.routing.Routing._
  14. //Our message types
  15. case object Ping
  16. case object Pong
  17. //Two actors, one named Pinger and one named Ponger
  18. //The actor(pf) method creates an anonymous actor and starts it
  19. val pinger = actorOf(new Actor { def receive = { case x => println("Pinger: " + x) } }).start()
  20. val ponger = actorOf(new Actor { def receive = { case x => println("Ponger: " + x) } }).start()
  21. //A router that dispatches Ping messages to the pinger
  22. //and Pong messages to the ponger
  23. val d = routerActor {
  24. case Ping => pinger
  25. case Pong => ponger
  26. }
  27. d ! Ping //Prints "Pinger: Ping"
  28. d ! Pong //Prints "Ponger: Pong"
  29. Or by mixing in akka.routing.Router:
  30. .. code-block:: scala
  31. import akka.actor.Actor
  32. import akka.actor.Actor._
  33. import akka.routing.Router
  34. //Our message types
  35. case object Ping
  36. case object Pong
  37. class MyRouter extends Actor with Router {
  38. //Our pinger and ponger actors
  39. val pinger = actorOf(new Actor { def receive = { case x => println("Pinger: " + x) } }).start()
  40. val ponger = actorOf(new Actor { def receive = { case x => println("Ponger: " + x) } }).start()
  41. //When we get a ping, we dispatch to the pinger
  42. //When we get a pong, we dispatch to the ponger
  43. def routes = {
  44. case Ping => pinger
  45. case Pong => ponger
  46. }
  47. }
  48. //Create an instance of our router, and start it
  49. val d = actorOf[MyRouter].start()
  50. d ! Ping //Prints "Pinger: Ping"
  51. d ! Pong //Prints "Ponger: Pong"
  52. LoadBalancer
  53. ------------
  54. A LoadBalancer is an actor that forwards messages it receives to a boundless sequence of destination actors.
  55. Example using the ``loadBalancerActor()`` factory method:
  56. .. code-block:: scala
  57. import akka.actor.Actor._
  58. import akka.actor.Actor
  59. import akka.routing.Routing._
  60. import akka.routing.CyclicIterator
  61. //Our message types
  62. case object Ping
  63. case object Pong
  64. //Two actors, one named Pinger and one named Ponger
  65. //The actor(pf) method creates an anonymous actor and starts it
  66. val pinger = actorOf(new Actor { def receive = { case x => println("Pinger: " + x) } }).start()
  67. val ponger = actorOf(new Actor { def receive = { case x => println("Ponger: " + x) } }).start()
  68. //A load balancer that given a sequence of actors dispatches them accordingly
  69. //a CyclicIterator works in a round-robin-fashion
  70. val d = loadBalancerActor( new CyclicIterator( List(pinger,ponger) ) )
  71. d ! Pong //Prints "Pinger: Pong"
  72. d ! Pong //Prints "Ponger: Pong"
  73. d ! Ping //Prints "Pinger: Ping"
  74. d ! Ping //Prints "Ponger: Ping"
  75. Or by mixing in akka.routing.LoadBalancer
  76. .. code-block:: scala
  77. import akka.actor._
  78. import akka.actor.Actor._
  79. import akka.routing.{ LoadBalancer, CyclicIterator }
  80. //Our message types
  81. case object Ping
  82. case object Pong
  83. //A load balancer that balances between a pinger and a ponger
  84. class MyLoadBalancer extends Actor with LoadBalancer {
  85. val pinger = actorOf(new Actor { def receive = { case x => println("Pinger: " + x) } }).start()
  86. val ponger = actorOf(new Actor { def receive = { case x => println("Ponger: " + x) } }).start()
  87. val seq = new CyclicIterator[ActorRef](List(pinger,ponger))
  88. }
  89. //Create an instance of our loadbalancer, and start it
  90. val d = actorOf[MyLoadBalancer].start()
  91. d ! Pong //Prints "Pinger: Pong"
  92. d ! Pong //Prints "Ponger: Pong"
  93. d ! Ping //Prints "Pinger: Ping"
  94. d ! Ping //Prints "Ponger: Ping"
  95. Also, instead of using the CyclicIterator, you can create your own message distribution algorithms, theres already `one <@http://github.com/jboner/akka/blob/master/akka-core/src/main/scala/routing/Iterators.scala#L31>`_ that dispatches depending on target mailbox size, effectively dispatching to the one thats got fewest messages to process right now.
  96. Example `<http://pastie.org/984889>`_
  97. You can also send a 'Routing.Broadcast(msg)' message to the router to have it be broadcasted out to all the actors it represents.
  98. .. code-block:: scala
  99. router ! Routing.Broadcast(PoisonPill)
  100. Actor Pool
  101. ----------
  102. An actor pool is similar to the load balancer is that it routes incoming messages to other actors. It has different semantics however when it comes to how those actors are managed and selected for dispatch. Therein lies the difference. The pool manages, from start to shutdown, the lifecycle of all delegated actors. The number of actors in a pool can be fixed or grow and shrink over time. Also, messages can be routed to more than one actor in the pool if so desired. This is a useful little feature for accounting for expected failure - especially with remoting - where you can invoke the same request of multiple actors and just take the first, best response.
  103. The actor pool is built around three concepts: capacity, filtering and selection.
  104. Selection
  105. ^^^^^^^^^
  106. All pools require a *Selector* to be mixed-in. This trait controls how and how many actors in the pool will receive the incoming message. Define *selectionCount* to some positive number greater than one to route to multiple actors. Currently two are provided:
  107. * `SmallestMailboxSelector <https://github.com/jboner/akka/blob/master/akka-actor/src/main/scala/akka/routing/Pool.scala#L133>`_ - Using the exact same logic as the iterator of the same name, the pooled actor with the fewest number of pending messages will be chosen.
  108. * `RoundRobinSelector <https://github.com/jboner/akka/blob/master/akka-actor/src/main/scala/akka/routing/Pool.scala#L158>`_ - Performs a very simple index-based selection, wrapping around the end of the list, very much like the CyclicIterator does.
  109. Partial Fills
  110. *************
  111. When selecting more than one pooled actor, its possible that in order to fulfill the requested amount, the selection set must contain duplicates. By setting *partialFill* to **true**, you instruct the selector to return only unique actors from the pool.
  112. Capacity
  113. ^^^^^^^^
  114. As you'd expect, capacity traits determine how the pool is funded with actors. There are two types of strategies that can be employed:
  115. * `FixedCapacityStrategy <https://github.com/jboner/akka/blob/master/akka-actor/src/main/scala/akka/routing/Pool.scala#L268>`_ - When you mix this into your actor pool, you define a pool size and when the pool is started, it will have that number of actors within to which messages will be delegated.
  116. * `BoundedCapacityStrategy <https://github.com/jboner/akka/blob/master/akka-actor/src/main/scala/akka/routing/Pool.scala#L269>`_ - When you mix this into your actor pool, you define upper and lower bounds, and when the pool is started, it will have the minimum number of actors in place to handle messages. You must also mix-in a Capacitor and a Filter when using this strategy (see below).
  117. The *BoundedCapacityStrategy* requires additional logic to function. Specifically it requires a *Capacitor* and a *Filter*. Capacitors are used to determine the pressure that the pool is under and provide a (usually) raw reading of this information. Currently we provide for the use of either mailbox backlog or active futures count as a means of evaluating pool pressure. Each expresses itself as a simple number - a reading of the number of actors either with mailbox sizes over a certain threshold or blocking a thread waiting on a future to complete or expire.
  118. Filtering
  119. ^^^^^^^^^
  120. A *Filter* is a trait that modifies the raw pressure reading returned from a Capacitor such that it drives the adjustment of the pool capacity to a desired end. More simply, if we just used the pressure reading alone, we might only ever increase the size of the pool (to respond to overload) or we might only have a single mechanism for reducing the pool size when/if it became necessary. This behavior is fully under your control through the use of *Filters*. Let's take a look at some code to see how this works:
  121. .. code-block:: scala
  122. trait BoundedCapacitor
  123. {
  124. def lowerBound:Int
  125. def upperBound:Int
  126. def capacity(delegates:Seq[ActorRef]):Int =
  127. {
  128. val current = delegates length
  129. var delta = _eval(delegates)
  130. val proposed = current + delta
  131. if (proposed < lowerBound) delta += (lowerBound - proposed)
  132. else if (proposed > upperBound) delta -= (proposed - upperBound)
  133. delta
  134. }
  135. protected def _eval(delegates:Seq[ActorRef]):Int
  136. }
  137. trait CapacityStrategy
  138. {
  139. import ActorPool._
  140. def pressure(delegates:Seq[ActorRef]):Int
  141. def filter(pressure:Int, capacity:Int):Int
  142. protected def _eval(delegates:Seq[ActorRef]):Int = filter(pressure(delegates), delegates.size)
  143. }
  144. Here we see how the filter function will have the chance to modify the pressure reading to influence the capacity change. You are free to implement filter() however you like. We provide a `Filter <https://github.com/jboner/akka/blob/master/akka-actor/src/main/scala/akka/routing/Pool.scala#L279>`_ trait that evaluates both a rampup and a backoff subfilter to determine how to use the pressure reading to alter the pool capacity. There are several subfilters available to use, though again you may create whatever makes the most sense for you pool:
  145. * `BasicRampup <https://github.com/jboner/akka/blob/master/akka-actor/src/main/scala/akka/routing/Pool.scala#L308>`_ - When pressure exceeds current capacity, increase the number of actors in the pool by some factor (*rampupRate*) of the current pool size.
  146. * `BasicBackoff <https://github.com/jboner/akka/blob/master/akka-actor/src/main/scala/akka/routing/Pool.scala#L322>`_ - When the pressure ratio falls under some predefined amount (*backoffThreshold*), decrease the number of actors in the pool by some factor of the current pool size.
  147. * `RunningMeanBackoff <https://github.com/jboner/akka/blob/master/akka-actor/src/main/scala/akka/routing/Pool.scala#L341>`_ - This filter tracks the average pressure-to-capacity over the lifetime of the pool (or since the last time the filter was reset) and will begin to reduce capacity once this mean falls below some predefined amount. The number of actors that will be stopped is determined by some factor of the difference between the current capacity and pressure. The idea behind this filter is to reduce the likelihood of "thrashing" (removing then immediately creating...) pool actors by delaying the backoff until some quiescent stage of the pool. Put another way, use this subfilter to allow quick rampup to handle load and more subtle backoff as that decreases over time.
  148. Examples
  149. ^^^^^^^^
  150. .. code-block:: scala
  151. class TestPool extends Actor with DefaultActorPool
  152. with BoundedCapacityStrategy
  153. with ActiveFuturesPressureCapacitor
  154. with SmallestMailboxSelector
  155. with BasicNoBackoffFilter
  156. {
  157. def receive = _route
  158. def lowerBound = 2
  159. def upperBound = 4
  160. def rampupRate = 0.1
  161. def partialFill = true
  162. def selectionCount = 1
  163. def instance = actorOf(new Actor {def receive = {case n:Int =>
  164. Thread.sleep(n)
  165. counter.incrementAndGet
  166. latch.countDown()}})
  167. }
  168. .. code-block:: scala
  169. class TestPool extends Actor with DefaultActorPool
  170. with BoundedCapacityStrategy
  171. with MailboxPressureCapacitor
  172. with SmallestMailboxSelector
  173. with Filter
  174. with RunningMeanBackoff
  175. with BasicRampup
  176. {
  177. def receive = _route
  178. def lowerBound = 1
  179. def upperBound = 5
  180. def pressureThreshold = 1
  181. def partialFill = true
  182. def selectionCount = 1
  183. def rampupRate = 0.1
  184. def backoffRate = 0.50
  185. def backoffThreshold = 0.50
  186. def instance = actorOf(new Actor {def receive = {case n:Int =>
  187. Thread.sleep(n)
  188. latch.countDown()}})
  189. }
  190. Taken from the unit test `spec <https://github.com/jboner/akka/blob/master/akka-actor/src/test/scala/akka/routing/RoutingSpec.scala>`_.