add router design rationale, see #1772
This commit is contained in:
parent
34df500c92
commit
9ba5043539
3 changed files with 115 additions and 7 deletions
|
|
@ -25,7 +25,7 @@ is really easy to create your own. The routers shipped with Akka are:
|
||||||
* ``akka.routing.BroadcastRouter``
|
* ``akka.routing.BroadcastRouter``
|
||||||
* ``akka.routing.ScatterGatherFirstCompletedRouter``
|
* ``akka.routing.ScatterGatherFirstCompletedRouter``
|
||||||
|
|
||||||
Routers Explained
|
Routers In Action
|
||||||
^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
This is an example of how to create a router that is defined in configuration:
|
This is an example of how to create a router that is defined in configuration:
|
||||||
|
|
@ -45,8 +45,11 @@ You can also give the router already created routees as in:
|
||||||
When you create a router programatically you define the number of routees *or* you pass already created routees to it.
|
When you create a router programatically you define the number of routees *or* you pass already created routees to it.
|
||||||
If you send both parameters to the router *only* the latter will be used, i.e. ``nrOfInstances`` is disregarded.
|
If you send both parameters to the router *only* the latter will be used, i.e. ``nrOfInstances`` is disregarded.
|
||||||
|
|
||||||
*It is also worth pointing out that if you define the ``router`` in the configuration file then this value will be used
|
*It is also worth pointing out that if you define the ``router`` in the
|
||||||
instead of any programmatically sent parameters.*
|
configuration file then this value will be used instead of any programmatically
|
||||||
|
sent parameters. The decision whether to create a router at all, on the other
|
||||||
|
hand, must be taken within the code, i.e. you cannot make something a router by
|
||||||
|
external configuration alone (see below for details).*
|
||||||
|
|
||||||
Once you have the router actor it is just to send messages to it as you would to any actor:
|
Once you have the router actor it is just to send messages to it as you would to any actor:
|
||||||
|
|
||||||
|
|
@ -56,6 +59,45 @@ Once you have the router actor it is just to send messages to it as you would to
|
||||||
|
|
||||||
The router will apply its behavior to the message it receives and forward it to the routees.
|
The router will apply its behavior to the message it receives and forward it to the routees.
|
||||||
|
|
||||||
|
How Routing is Designed within Akka
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Routers behave like single actors, but they should also not hinder scalability.
|
||||||
|
This apparent contradiction is solved by making routers be represented by a
|
||||||
|
special :class:`RoutedActorRef`, which dispatches incoming messages destined
|
||||||
|
for the routees without actually invoking the router actor’s behavior (and thus
|
||||||
|
avoiding its mailbox; the single router actor’s task is to manage all aspects
|
||||||
|
related to the lifecycle of the routees). This means that the code which decides
|
||||||
|
which route to take is invoked concurrently from all possible senders and hence
|
||||||
|
must be thread-safe, it cannot live the simple and happy life of code within an
|
||||||
|
actor.
|
||||||
|
|
||||||
|
There is one part in the above paragraph which warrants some more background
|
||||||
|
explanation: Why does a router need a “head” which is actual parent to all the
|
||||||
|
routees? The initial design tried to side-step this issue, but location
|
||||||
|
transparency as well as mandatory parental supervision required a redesign.
|
||||||
|
Each of the actors which the router spawns must have its unique identity, which
|
||||||
|
translates into a unique actor path. Since the router has only one given name
|
||||||
|
in its parent’s context, another level in the name space is needed, which
|
||||||
|
according to the addressing semantics implies the existence of an actor with
|
||||||
|
the router’s name. This is not only necessary for the internal messaging
|
||||||
|
involved in creating, restarting and terminating actors, it is also needed when
|
||||||
|
the pooled actors need to converse with other actors and receive replies in a
|
||||||
|
deterministic fashion. Since each actor knows its own external representation
|
||||||
|
as well as that of its parent, the routees decide where replies should be sent
|
||||||
|
when reacting to a message:
|
||||||
|
|
||||||
|
.. code-block:: java
|
||||||
|
|
||||||
|
getSender().tell(reply, getContext().parent()); // replies go to the router
|
||||||
|
getSender().tell(reply, getSelf()); // replies go to this routee
|
||||||
|
|
||||||
|
It is apparent now why routing needs to be enabled in code rather than being
|
||||||
|
possible to “bolt on” later: whether or not an actor is routed means a change
|
||||||
|
to the actor hierarchy, changing the actor paths of all children of the router.
|
||||||
|
The routees especially do need to know that they are routed to in order to
|
||||||
|
choose the sender reference for any messages they dispatch as shown above.
|
||||||
|
|
||||||
Router usage
|
Router usage
|
||||||
^^^^^^^^^^^^
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,7 +11,7 @@ import akka.event.Logging
|
||||||
//#imports1
|
//#imports1
|
||||||
|
|
||||||
import akka.dispatch.Future
|
import akka.dispatch.Future
|
||||||
import akka.actor.ActorSystem
|
import akka.actor.{ ActorRef, ActorSystem }
|
||||||
import org.scalatest.{ BeforeAndAfterAll, WordSpec }
|
import org.scalatest.{ BeforeAndAfterAll, WordSpec }
|
||||||
import org.scalatest.matchers.MustMatchers
|
import org.scalatest.matchers.MustMatchers
|
||||||
import akka.testkit._
|
import akka.testkit._
|
||||||
|
|
@ -356,4 +356,29 @@ class ActorDocSpec extends AkkaSpec(Map("akka.loglevel" -> "INFO")) {
|
||||||
//#ask-pipeTo
|
//#ask-pipeTo
|
||||||
}
|
}
|
||||||
|
|
||||||
|
"replying with own or other sender" in {
|
||||||
|
val actor = system.actorOf(Props(new Actor {
|
||||||
|
def receive = {
|
||||||
|
case ref: ActorRef ⇒
|
||||||
|
//#reply-with-sender
|
||||||
|
sender.tell("reply", context.parent) // replies will go back to parent
|
||||||
|
sender.!("reply")(context.parent) // alternative syntax (beware of the parens!)
|
||||||
|
//#reply-with-sender
|
||||||
|
case x ⇒
|
||||||
|
//#reply-without-sender
|
||||||
|
sender ! x // replies will go to this actor
|
||||||
|
//#reply-without-sender
|
||||||
|
}
|
||||||
|
}))
|
||||||
|
implicit val me = testActor
|
||||||
|
actor ! 42
|
||||||
|
expectMsg(42)
|
||||||
|
lastSender must be === actor
|
||||||
|
actor ! me
|
||||||
|
expectMsg("reply")
|
||||||
|
lastSender must be === system.actorFor("/user")
|
||||||
|
expectMsg("reply")
|
||||||
|
lastSender must be === system.actorFor("/user")
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,7 +25,7 @@ is really easy to create your own. The routers shipped with Akka are:
|
||||||
* ``akka.routing.BroadcastRouter``
|
* ``akka.routing.BroadcastRouter``
|
||||||
* ``akka.routing.ScatterGatherFirstCompletedRouter``
|
* ``akka.routing.ScatterGatherFirstCompletedRouter``
|
||||||
|
|
||||||
Routers Explained
|
Routers In Action
|
||||||
^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
This is an example of how to create a router that is defined in configuration:
|
This is an example of how to create a router that is defined in configuration:
|
||||||
|
|
@ -45,8 +45,11 @@ You can also give the router already created routees as in:
|
||||||
When you create a router programatically you define the number of routees *or* you pass already created routees to it.
|
When you create a router programatically you define the number of routees *or* you pass already created routees to it.
|
||||||
If you send both parameters to the router *only* the latter will be used, i.e. ``nrOfInstances`` is disregarded.
|
If you send both parameters to the router *only* the latter will be used, i.e. ``nrOfInstances`` is disregarded.
|
||||||
|
|
||||||
*It is also worth pointing out that if you define the ``router`` in the configuration file then this value will be used
|
*It is also worth pointing out that if you define the ``router`` in the
|
||||||
instead of any programmatically sent parameters.*
|
configuration file then this value will be used instead of any programmatically
|
||||||
|
sent parameters. The decision whether to create a router at all, on the other
|
||||||
|
hand, must be taken within the code, i.e. you cannot make something a router by
|
||||||
|
external configuration alone (see below for details).*
|
||||||
|
|
||||||
Once you have the router actor it is just to send messages to it as you would to any actor:
|
Once you have the router actor it is just to send messages to it as you would to any actor:
|
||||||
|
|
||||||
|
|
@ -56,6 +59,44 @@ Once you have the router actor it is just to send messages to it as you would to
|
||||||
|
|
||||||
The router will apply its behavior to the message it receives and forward it to the routees.
|
The router will apply its behavior to the message it receives and forward it to the routees.
|
||||||
|
|
||||||
|
How Routing is Designed within Akka
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Routers behave like single actors, but they should also not hinder scalability.
|
||||||
|
This apparent contradiction is solved by making routers be represented by a
|
||||||
|
special :class:`RoutedActorRef`, which dispatches incoming messages destined
|
||||||
|
for the routees without actually invoking the router actor’s behavior (and thus
|
||||||
|
avoiding its mailbox; the single router actor’s task is to manage all aspects
|
||||||
|
related to the lifecycle of the routees). This means that the code which decides
|
||||||
|
which route to take is invoked concurrently from all possible senders and hence
|
||||||
|
must be thread-safe, it cannot live the simple and happy life of code within an
|
||||||
|
actor.
|
||||||
|
|
||||||
|
There is one part in the above paragraph which warrants some more background
|
||||||
|
explanation: Why does a router need a “head” which is actual parent to all the
|
||||||
|
routees? The initial design tried to side-step this issue, but location
|
||||||
|
transparency as well as mandatory parental supervision required a redesign.
|
||||||
|
Each of the actors which the router spawns must have its unique identity, which
|
||||||
|
translates into a unique actor path. Since the router has only one given name
|
||||||
|
in its parent’s context, another level in the name space is needed, which
|
||||||
|
according to the addressing semantics implies the existence of an actor with
|
||||||
|
the router’s name. This is not only necessary for the internal messaging
|
||||||
|
involved in creating, restarting and terminating actors, it is also needed when
|
||||||
|
the pooled actors need to converse with other actors and receive replies in a
|
||||||
|
deterministic fashion. Since each actor knows its own external representation
|
||||||
|
as well as that of its parent, the routees decide where replies should be sent
|
||||||
|
when reacting to a message:
|
||||||
|
|
||||||
|
.. includecode:: code/akka/docs/actor/ActorDocSpec.scala#reply-with-sender
|
||||||
|
|
||||||
|
.. includecode:: code/akka/docs/actor/ActorDocSpec.scala#reply-without-sender
|
||||||
|
|
||||||
|
It is apparent now why routing needs to be enabled in code rather than being
|
||||||
|
possible to “bolt on” later: whether or not an actor is routed means a change
|
||||||
|
to the actor hierarchy, changing the actor paths of all children of the router.
|
||||||
|
The routees especially do need to know that they are routed to in order to
|
||||||
|
choose the sender reference for any messages they dispatch as shown above.
|
||||||
|
|
||||||
Router usage
|
Router usage
|
||||||
^^^^^^^^^^^^
|
^^^^^^^^^^^^
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue