Improved router documentation. Fixes #2874
* Edit of basic usage and design. * Rearranged order of sections. * Added more information on special message types. * Added receiveN to JavaTestKit.
This commit is contained in:
parent
a31d98340f
commit
3b98ba249b
11 changed files with 718 additions and 203 deletions
|
|
@ -44,7 +44,7 @@ public class RouterViaConfigExample {
|
|||
ActorSystem system = ActorSystem.create("Example", config);
|
||||
//#configurableRouting
|
||||
ActorRef router = system.actorOf(
|
||||
new Props(ExampleActor.class).withRouter(new FromConfig()), "router");
|
||||
new Props(ExampleActor.class).withRouter(new FromConfig()), "myrouter1");
|
||||
//#configurableRouting
|
||||
for (int i = 1; i <= 10; i++) {
|
||||
router.tell(new ExampleActor.Message(i), null);
|
||||
|
|
@ -52,7 +52,7 @@ public class RouterViaConfigExample {
|
|||
|
||||
//#configurableRoutingWithResizer
|
||||
ActorRef router2 = system.actorOf(
|
||||
new Props(ExampleActor.class).withRouter(new FromConfig()), "router2");
|
||||
new Props(ExampleActor.class).withRouter(new FromConfig()), "myrouter2");
|
||||
//#configurableRoutingWithResizer
|
||||
for (int i = 1; i <= 10; i++) {
|
||||
router2.tell(new ExampleActor.Message(i), null);
|
||||
|
|
|
|||
|
|
@ -0,0 +1,5 @@
|
|||
package docs.jrouting;
|
||||
|
||||
import org.scalatest.junit.JUnitSuite
|
||||
|
||||
class RouterViaProgramDocTest extends RouterViaProgramDocTestBase with JUnitSuite
|
||||
|
|
@ -0,0 +1,130 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2013 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package docs.jrouting;
|
||||
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.actor.Kill;
|
||||
import akka.actor.PoisonPill;
|
||||
import akka.actor.Props;
|
||||
import akka.actor.UntypedActor;
|
||||
import akka.remote.routing.RemoteRouterConfig;
|
||||
import akka.routing.Broadcast;
|
||||
import akka.routing.RoundRobinRouter;
|
||||
import akka.testkit.JavaTestKit;
|
||||
import docs.jrouting.RouterViaProgramExample.ExampleActor;
|
||||
import docs.routing.RouterViaProgramDocSpec.Echo;
|
||||
import java.io.Serializable;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.Assert;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
public class RouterViaProgramDocTestBase {
|
||||
|
||||
static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create();
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void teardown() {
|
||||
system.shutdown();
|
||||
}
|
||||
|
||||
private static class JavaTestKitWithSelf extends JavaTestKit {
|
||||
public JavaTestKitWithSelf(ActorSystem system) {
|
||||
super(system);
|
||||
}
|
||||
/**
|
||||
* Wrap `getRef()` so our examples look like they're within a normal actor.
|
||||
*/
|
||||
public ActorRef getSelf() {
|
||||
return getRef();
|
||||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateRouteesFromPaths() {
|
||||
new JavaTestKit(system) {{
|
||||
//#programmaticRoutingRouteePaths
|
||||
ActorRef actor1 = system.actorOf(new Props(ExampleActor.class), "actor1");
|
||||
ActorRef actor2 = system.actorOf(new Props(ExampleActor.class), "actor2");
|
||||
ActorRef actor3 = system.actorOf(new Props(ExampleActor.class), "actor3");
|
||||
Iterable<String> routees = Arrays.asList(
|
||||
new String[] { "/user/actor1", "/user/actor2", "/user/actor3" });
|
||||
ActorRef router = system.actorOf(
|
||||
new Props().withRouter(new RoundRobinRouter(routees)));
|
||||
//#programmaticRoutingRouteePaths
|
||||
for (int i = 1; i <= 6; i++) {
|
||||
router.tell(new ExampleActor.Message(i), null);
|
||||
}
|
||||
}};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateBroadcast() {
|
||||
new JavaTestKitWithSelf(system) {{
|
||||
ActorRef router = system.actorOf(new Props(Echo.class).withRouter(new RoundRobinRouter(5)));
|
||||
//#broadcastDavyJonesWarning
|
||||
router.tell(new Broadcast("Watch out for Davy Jones' locker"), getSelf());
|
||||
//#broadcastDavyJonesWarning
|
||||
receiveN(5, duration("5 seconds"));
|
||||
}};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstratePoisonPill() {
|
||||
new JavaTestKitWithSelf(system) {{
|
||||
ActorRef router = system.actorOf(new Props(Echo.class).withRouter(new RoundRobinRouter(5)));
|
||||
//#poisonPill
|
||||
router.tell(PoisonPill.getInstance(), getSelf());
|
||||
//#poisonPill
|
||||
expectNoMsg(duration("1 seconds"));
|
||||
Assert.assertTrue(router.isTerminated());
|
||||
}};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateBroadcastOfPoisonPill() {
|
||||
new JavaTestKitWithSelf(system) {{
|
||||
ActorRef router = system.actorOf(new Props(Echo.class).withRouter(new RoundRobinRouter(5)));
|
||||
//#broadcastPoisonPill
|
||||
router.tell(new Broadcast(PoisonPill.getInstance()), getSelf());
|
||||
//#broadcastPoisonPill
|
||||
expectNoMsg(duration("1 seconds"));
|
||||
Assert.assertTrue(router.isTerminated());
|
||||
}};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateKill() {
|
||||
new JavaTestKitWithSelf(system) {{
|
||||
ActorRef router = system.actorOf(new Props(Echo.class).withRouter(new RoundRobinRouter(5)));
|
||||
//#kill
|
||||
router.tell(Kill.getInstance(), getSelf());
|
||||
//#kill
|
||||
expectNoMsg(duration("1 seconds"));
|
||||
Assert.assertTrue(router.isTerminated());
|
||||
}};
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateBroadcastOfKill() {
|
||||
new JavaTestKitWithSelf(system) {{
|
||||
ActorRef router = system.actorOf(new Props(Echo.class).withRouter(new RoundRobinRouter(5)));
|
||||
//#broadcastKill
|
||||
router.tell(new Broadcast(Kill.getInstance()), getSelf());
|
||||
//#broadcastKill
|
||||
expectNoMsg(duration("1 seconds"));
|
||||
Assert.assertTrue(router.isTerminated());
|
||||
}};
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -131,6 +131,8 @@ actor systems has to have a JAR containing the class.
|
|||
``/foo/bar`` is considered **more specific** than ``/foo/*`` and only the highest priority match is used.
|
||||
Please note that it **cannot** be used to partially match section, like this: ``/foo*/bar``, ``/f*o/bar`` etc.
|
||||
|
||||
.. _remote-deployment-warnings-java:
|
||||
|
||||
.. warning::
|
||||
|
||||
*Caveat:* Remote deployment ties both systems together in a tight fashion,
|
||||
|
|
|
|||
|
|
@ -4,11 +4,14 @@
|
|||
Routing (Java)
|
||||
==============
|
||||
|
||||
A Router is an actor that routes incoming messages to outbound actors.
|
||||
The router routes the messages sent to it to its underlying actors called 'routees'.
|
||||
A Router is an actor that receives messages and efficiently routes them to other actors, known as
|
||||
its *routees*.
|
||||
|
||||
Akka comes with some defined routers out of the box, but as you will see in this chapter it
|
||||
is really easy to create your own. The routers shipped with Akka are:
|
||||
Different routing strategies can be used, according to your application's needs. Akka comes with
|
||||
several useful routing strategies right out of the box. But, as you will see in this chapter, it is
|
||||
also possible to :ref:`create your own <custom-router-java>`.
|
||||
|
||||
The routers shipped with Akka are:
|
||||
|
||||
* ``akka.routing.RoundRobinRouter``
|
||||
* ``akka.routing.RandomRouter``
|
||||
|
|
@ -17,112 +20,119 @@ is really easy to create your own. The routers shipped with Akka are:
|
|||
* ``akka.routing.ScatterGatherFirstCompletedRouter``
|
||||
* ``akka.routing.ConsistentHashingRouter``
|
||||
|
||||
Routers In Action
|
||||
Routers in Action
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
This is an example of how to create a router that is defined in configuration:
|
||||
|
||||
.. includecode:: ../scala/code/docs/routing/RouterViaConfigDocSpec.scala#config-round-robin
|
||||
|
||||
.. includecode:: code/docs/jrouting/RouterViaConfigExample.java#configurableRouting
|
||||
|
||||
This is an example of how to programmatically create a router and set the number of routees it should create:
|
||||
|
||||
.. includecode:: code/docs/jrouting/RouterViaProgramExample.java#programmaticRoutingNrOfInstances
|
||||
|
||||
You can also give the router already created routees as in:
|
||||
|
||||
.. includecode:: code/docs/jrouting/RouterViaProgramExample.java#programmaticRoutingRoutees
|
||||
|
||||
.. note::
|
||||
|
||||
No actor factory or class needs to be provided in this
|
||||
case, as the ``Router`` will not create any children on its own (which is not
|
||||
true anymore when using a resizer). The routees can also be specified by giving
|
||||
their path strings.
|
||||
|
||||
|
||||
When you create a router programmatically 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.
|
||||
|
||||
*It is also worth pointing out that if you define the ``router`` in the
|
||||
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:
|
||||
Sending a message to a router is easy.
|
||||
|
||||
.. code-block:: java
|
||||
|
||||
router.tell(new MyMsg());
|
||||
|
||||
The router will forward the message to its routees according to its routing policy.
|
||||
A router actor forwards messages to its routees according to its routing policy.
|
||||
|
||||
Remotely Deploying Routees
|
||||
**************************
|
||||
.. note::
|
||||
|
||||
In addition to being able to supply looked-up remote actors as routees, you can
|
||||
make the router deploy its created children on a set of remote hosts; this will
|
||||
be done in round-robin fashion. In order to do that, wrap the router
|
||||
configuration in a :class:`RemoteRouterConfig`, attaching the remote addresses of
|
||||
the nodes to deploy to. Naturally, this requires you to include the
|
||||
``akka-remote`` module on your classpath:
|
||||
In general, any message sent to a router will be sent onwards to its routees. But there are a
|
||||
few exceptions. These are documented in the :ref:`router-special-messages-java` section below.
|
||||
|
||||
Creating a Router
|
||||
*****************
|
||||
|
||||
Routers and routees are closely intertwined. Router actors are created by specifying the desired
|
||||
*routee* :class:`Props` then attaching the router's :class:`RouterConfig`. When you create a router
|
||||
actor it will create routees, as needed, as its children.
|
||||
|
||||
For example, the following code and configuration snippets show how to create a :ref:`round-robin
|
||||
<round-robin-router-java>` router that forwards messages to five ``ExampleActor`` routees. The
|
||||
routees will be created as the router's children.
|
||||
|
||||
.. includecode:: ../scala/code/docs/routing/RouterViaConfigDocSpec.scala#config-round-robin
|
||||
|
||||
.. includecode:: code/docs/jrouting/RouterViaConfigExample.java#configurableRouting
|
||||
|
||||
Here is the same example, but with the router configuration provided programmatically instead of
|
||||
from configuration.
|
||||
|
||||
.. includecode:: code/docs/jrouting/RouterViaProgramExample.java#programmaticRoutingNrOfInstances
|
||||
|
||||
Sometimes, rather than having the router create its routees, it is desirable to create routees
|
||||
separately and provide them to the router for its use. You can do this by passing an
|
||||
:class:`Iterable` of routees to the router's configuration.
|
||||
|
||||
The example below shows how to create a router by providing it with the :class:`ActorRef`\s of three
|
||||
routee actors.
|
||||
|
||||
.. includecode:: code/docs/jrouting/RouterViaProgramExample.java#programmaticRoutingRoutees
|
||||
|
||||
Routees can also be specified by providing their path strings instead of their :class:`ActorRef`\s.
|
||||
|
||||
.. includecode:: code/docs/jrouting/RouterViaProgramDocTestBase.java#programmaticRoutingRouteePaths
|
||||
|
||||
In addition to being able to supply looked-up remote actors as routees, you can ask the router to
|
||||
deploy its created children on a set of remote hosts. Routees will be deployed in round-robin
|
||||
fashion. In order to deploy routees remotely, wrap the router configuration in a
|
||||
:class:`RemoteRouterConfig`, attaching the remote addresses of the nodes to deploy to. Remote
|
||||
deployment requires the ``akka-remote`` module to be included in the classpath.
|
||||
|
||||
.. includecode:: code/docs/jrouting/RouterViaProgramExample.java#remoteRoutees
|
||||
|
||||
How Routing is Designed within Akka
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
There are a few gotchas to be aware of when creating routers:
|
||||
|
||||
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` (implementation detail, what the user gets is
|
||||
an :class:`ActorRef` as usual) 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.
|
||||
* If you define the ``router`` in the configuration file then this value will be used instead of any
|
||||
programmatically provided parameters.
|
||||
* Although routers can be configured in the configuration file, they must still be created
|
||||
programmatically, i.e. you cannot make a router through external configuration alone.
|
||||
* If you provide the ``routees`` in the router configuration then
|
||||
the value of ``nrOfInstances``, if provided, will be disregarded.
|
||||
* When you provide routees programmatically the router will generally ignore the routee
|
||||
:class:`Props`, as it does not need to create routees. However, if you use a :ref:`resizable
|
||||
router <resizable-routers-java>` then the routee :class:`Props` will be used whenever the
|
||||
resizer creates new routees.
|
||||
* The same issues that apply to remotely-deployed actors also apply to remotely-deployed routees.
|
||||
Read about :ref:`the limitations of remote deployment <remote-deployment-warnings-java>` for
|
||||
more information.
|
||||
|
||||
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:
|
||||
Routers, Routees and Senders
|
||||
****************************
|
||||
|
||||
.. includecode:: code/docs/jrouting/RouterViaProgramExample.java#reply-with-parent
|
||||
The router forwards messages onto its routees without changing the original sender. When a routee
|
||||
replies to a routed message, the reply will be sent to the original sender, not to the router.
|
||||
|
||||
When a router creates routees, they are created as the routers children. This gives each routee its
|
||||
own identity in the actor system.
|
||||
|
||||
When a routee replies it can :ref:`set itself <actors-tell-sender-java>` as the sender of the reply.
|
||||
|
||||
.. includecode:: code/docs/jrouting/RouterViaProgramExample.java#reply-with-self
|
||||
|
||||
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.
|
||||
However, it is often useful for routees to set the *router* as the sender. For example, you might
|
||||
want to set the router as the sender if you want to hide the details of the routees behind the
|
||||
router. The following code snippet shows how to set the parent router as sender.
|
||||
|
||||
Routers vs. Supervision
|
||||
.. includecode:: code/docs/jrouting/RouterViaProgramExample.java#reply-with-parent
|
||||
|
||||
Note that different code would be needed if the routees were not children of the router, i.e. if
|
||||
they were provided when the router was created.
|
||||
|
||||
Routers and Supervision
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
As explained in the previous section, routers create new actor instances as
|
||||
children of the “head” router, who therefore also is their supervisor. The
|
||||
supervisor strategy of this actor can be configured by means of the
|
||||
:meth:`RouterConfig.supervisorStrategy` property, which is supported for all
|
||||
built-in router types. It defaults to “always escalate”, which leads to the
|
||||
application of the router’s parent’s supervision directive to all children of
|
||||
the router uniformly (i.e. not only the one which failed). It should be
|
||||
mentioned that the router overrides the default behavior of terminating all
|
||||
children upon restart, which means that a restart—while re-creating them—does
|
||||
not have an effect on the number of actors in the pool.
|
||||
Routees can be created by a router or provided to the router when it is created. Any routees that
|
||||
are created by a router will be created as the router's children. The router is therefore also the
|
||||
children's supervisor.
|
||||
|
||||
The supervision strategy of the router actor can be configured with the
|
||||
:meth:`RouterConfig.supervisorStrategy` property. If no configuration is provided, routers default
|
||||
to a strategy of “always escalate”. This means that errors are passed up to the router's supervisor
|
||||
for handling. The router's supervisor will decide what to do about any errors.
|
||||
|
||||
Note the router's supervisor will treat the error as an error with the router itself. Therefore a
|
||||
directive to stop or restart will cause the router *itself* to stop or restart. The router, in
|
||||
turn, will cause its children to stop and restart.
|
||||
|
||||
It should be mentioned that the router's restart behavior has been overridden so that a restart,
|
||||
while still re-creating the children, will still preserve the same number of actors in the pool.
|
||||
|
||||
Setting the strategy is easily done:
|
||||
|
||||
|
|
@ -133,6 +143,8 @@ Another potentially useful approach is to give the router the same strategy as
|
|||
its parent, which effectively treats all actors in the pool as if they were
|
||||
direct children of their grand-parent instead.
|
||||
|
||||
.. _note-router-terminated-children-java:
|
||||
|
||||
.. note::
|
||||
|
||||
If the child of a router terminates, the router will not automatically spawn
|
||||
|
|
@ -151,6 +163,8 @@ and
|
|||
|
||||
.. includecode:: code/docs/jrouting/FibonacciActor.java#fibonacciActor
|
||||
|
||||
.. _round-robin-router-java:
|
||||
|
||||
RoundRobinRouter
|
||||
****************
|
||||
Routes in a `round-robin <http://en.wikipedia.org/wiki/Round-robin>`_ fashion to its routees.
|
||||
|
|
@ -255,6 +269,12 @@ This is an example of how to define a broadcast router in configuration:
|
|||
|
||||
.. includecode:: ../scala/code/docs/routing/RouterViaConfigDocSpec.scala#config-broadcast
|
||||
|
||||
.. note::
|
||||
|
||||
Broadcast routers always broadcast *every* message to their routees. If you do not want to
|
||||
broadcast every message, then you can use a non-broadcasting router and use
|
||||
:ref:`broadcast-messages-java` as needed.
|
||||
|
||||
ScatterGatherFirstCompletedRouter
|
||||
*********************************
|
||||
The ScatterGatherFirstCompletedRouter will send the message on to all its routees as a future.
|
||||
|
|
@ -323,18 +343,94 @@ This is an example of how to define a consistent-hashing router in configuration
|
|||
|
||||
.. includecode:: ../scala/code/docs/routing/RouterViaConfigDocSpec.scala#config-consistent-hashing
|
||||
|
||||
.. _router-special-messages-java:
|
||||
|
||||
Handling for Special Messages
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Most messages sent to routers will be forwarded according to the routers' usual routing rules.
|
||||
However there are a few types of messages that have special behavior.
|
||||
|
||||
.. _broadcast-messages-java:
|
||||
|
||||
Broadcast Messages
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
******************
|
||||
|
||||
There is a special type of message that will be sent to all routees regardless of the router.
|
||||
This message is called ``Broadcast`` and is used in the following manner:
|
||||
A ``Broadcast`` message can be used to send a message to *all* of a router's routees. When a router
|
||||
receives a ``Broadcast`` message, it will broadcast that message's *payload* to all routees, no
|
||||
matter how that router would normally route its messages.
|
||||
|
||||
.. code-block:: java
|
||||
The example below shows how you would use a ``Broadcast`` message to send a very important message
|
||||
to every routee of a router.
|
||||
|
||||
router.tell(new Broadcast("Watch out for Davy Jones' locker"));
|
||||
.. includecode:: code/docs/jrouting/RouterViaProgramDocTestBase.java#broadcastDavyJonesWarning
|
||||
|
||||
Only the actual message is forwarded to the routees, i.e. "Watch out for Davy Jones' locker" in the example above.
|
||||
It is up to the routee implementation whether to handle the broadcast message or not.
|
||||
In this example the router receives the ``Broadcast`` message, extracts its payload
|
||||
(``"Watch out for Davy Jones' locker"``), and then sends the payload on to all of the router's
|
||||
routees. It is up to each each routee actor to handle the received payload message.
|
||||
|
||||
PoisonPill Messages
|
||||
*******************
|
||||
|
||||
A ``PoisonPill`` message has special handling for all actors, including for routers. When any actor
|
||||
receives a ``PoisonPill`` message, that actor will be stopped. See the :ref:`poison-pill-java`
|
||||
documentation for details.
|
||||
|
||||
.. includecode:: code/docs/jrouting/RouterViaProgramDocTestBase.java#poisonPill
|
||||
|
||||
For a router, which normally passes on messages to routees, it is important to realised that
|
||||
``PoisonPill`` messages are processed by the router only. ``PoisonPill`` messages sent to a router
|
||||
will *not* be sent on to routees.
|
||||
|
||||
However, a ``PoisonPill`` message sent to a router may still affect its routees, because it will
|
||||
stop the router and when the router stops it also stops its children. Stopping children is normal
|
||||
actor behavior. The router will stop routees that it has created as children. Each child will
|
||||
process its current message and then tstop. This may lead to some messages being unprocessed. See
|
||||
the documentation on :ref:`stopping-actors-java` for more information.
|
||||
|
||||
If you wish to stop a router and its routees, but you would like the routees to first process all
|
||||
the messages currently in their mailboxes, then you should not send a ``PoisonPill`` message to the
|
||||
router. Instead you should wrap a ``PoisonPill`` message inside a broadcast message so that each
|
||||
routee will the ``PoisonPill`` message directly. Note that this will stop all routees, even if the
|
||||
routees aren't children of the router, i.e. even routees programmatically provided to the router.
|
||||
|
||||
.. includecode:: code/docs/jrouting/RouterViaProgramDocTestBase.java#broadcastPoisonPill
|
||||
|
||||
With the code shown above, each routee will receive a ``PoisonPill`` message. Each routee will
|
||||
continue to process its messages as normal, eventually processing the ``PoisonPill``. This will
|
||||
cause the routee to stop. After all routees have stopped the router will itself be :ref:`stopped
|
||||
automatically <note-router-terminated-children-java>`.
|
||||
|
||||
.. note::
|
||||
|
||||
Brendan W McAdams' excellent blog post `Distributing Akka Workloads - And Shutting Down Afterwards
|
||||
<http://blog.evilmonkeylabs.com/2013/01/17/Distributing_Akka_Workloads_And_Shutting_Down_After/>`_
|
||||
discusses in more detail how ``PoisonPill`` messages can be used to shut down routers and routees.
|
||||
|
||||
Kill Messages
|
||||
*************
|
||||
|
||||
``Kill`` messages are another type of message that has special handling. See
|
||||
:ref:`killing-actors-java` for general information about how actors handle ``Kill`` messages.
|
||||
|
||||
When a ``Kill`` message is sent to a router the router processes the message internally, and does
|
||||
*not* send it on to its routees. The router will throw an :class:`ActorKilledException` and fail. It
|
||||
will then be either resumed, restarted or terminated, depending how it is supervised.
|
||||
|
||||
Routees that are children of the router will also be suspended, and will be affected by the
|
||||
supervision directive that is applied to the router. Routees that are not the routers children, i.e.
|
||||
those that were created externally to the router, will not be affected.
|
||||
|
||||
.. includecode:: code/docs/jrouting/RouterViaProgramDocTestBase.java#kill
|
||||
|
||||
As with the ``PoisonPill`` message, there is a distinction between killing a router, which
|
||||
indirectly kills its children (who happen to be routees), and killing routees directly (some of whom
|
||||
may not be children.) To kill routees directly the router should be sent a ``Kill`` message wrapped
|
||||
in a ``Broadcast`` message.
|
||||
|
||||
.. includecode:: code/docs/jrouting/RouterViaProgramDocTestBase.java#broadcastKill
|
||||
|
||||
.. _resizable-routers-java:
|
||||
|
||||
Dynamically Resizable Routers
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
@ -368,12 +464,43 @@ will be used instead of any programmatically sent parameters.*
|
|||
this, configure the pool to use a balancing dispatcher, see `Configuring
|
||||
Dispatchers`_ for more information.
|
||||
|
||||
.. _router-design-java:
|
||||
|
||||
How Routing is Designed within Akka
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
On the surface routers look like normal actors, but they are actually implemented differently.
|
||||
Routers are designed to be extremely efficient at receiving messages and passing them quickly on to
|
||||
routees.
|
||||
|
||||
A normal actor can be used for routing messages, but an actor's single-threaded processing can
|
||||
become a bottleneck. Routers can achieve much higher throughput with an optimization to the usual
|
||||
message-processing pipeline that allows concurrent routing. This is achieved by embedding routers'
|
||||
routing logic directly in their :class:`ActorRef` rather than in the router actor. Messages sent to
|
||||
a router's :class:`ActorRef` can be immediately routed to the routee, bypassing the single-threaded
|
||||
router actor entirely.
|
||||
|
||||
The cost to this is, of course, that the internals of routing code are more complicated than if
|
||||
routers were implemented with normal actors. Fortunately all of this complexity is invisible to
|
||||
consumers of the routing API. However, it is something to be aware of when implementing your own
|
||||
routers.
|
||||
|
||||
.. _custom-router-java:
|
||||
|
||||
Custom Router
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
You can also create your own router should you not find any of the ones provided by Akka sufficient for your needs.
|
||||
You can create your own router should you not find any of the ones provided by Akka sufficient for your needs.
|
||||
In order to roll your own router you have to fulfill certain criteria which are explained in this section.
|
||||
|
||||
Before creating your own router you should consider whether a normal actor with router-like
|
||||
behavior might do the job just as well as a full-blown router. As explained
|
||||
:ref:`above <router-design-java>`, the primary benefit of routers over normal actors is their
|
||||
higher performance. But they are somewhat more complicated to write than normal actors. Therefore if
|
||||
lower maximum throughput is acceptable in your application you may wish to stick with traditional
|
||||
actors. This section, however, assumes that you wish to get maximum performance and so demonstrates
|
||||
how you can create your own router.
|
||||
|
||||
The router created in this example is a simple vote counter. It will route the votes to specific vote counter actors.
|
||||
In this case we only have two parties the Republicans and the Democrats. We would like a router that forwards all
|
||||
democrat related messages to the Democrat actor and all republican related messages to the Republican actor.
|
||||
|
|
|
|||
|
|
@ -322,6 +322,8 @@ message. This gives the best concurrency and scalability characteristics.
|
|||
|
||||
actor.tell("Hello");
|
||||
|
||||
.. _actors-tell-sender-java:
|
||||
|
||||
Or with the sender reference passed along with the message and available to the receiving Actor
|
||||
in its ``getSender: ActorRef`` member field. The target actor can use this
|
||||
to reply to the original sender, by using ``getSender().tell(replyMsg)``.
|
||||
|
|
@ -499,6 +501,8 @@ enables cleaning up of resources:
|
|||
actor and create its replacement in response to the :class:`Terminated`
|
||||
message which will eventually arrive.
|
||||
|
||||
.. _poison-pill-java:
|
||||
|
||||
PoisonPill
|
||||
----------
|
||||
|
||||
|
|
@ -628,14 +632,18 @@ actor's state which have the same property. The :class:`Stash` trait’s
|
|||
implementation of :meth:`preRestart` will call ``unstashAll()``, which is
|
||||
usually the desired behavior.
|
||||
|
||||
.. _killing-actors-java:
|
||||
|
||||
Killing an Actor
|
||||
================
|
||||
|
||||
You can kill an actor by sending a ``Kill`` message. This will restart the actor
|
||||
through regular supervisor semantics.
|
||||
You can kill an actor by sending a ``Kill`` message. This will cause the actor
|
||||
to throw a :class:`ActorKilledException`, triggering a failure. The actor will
|
||||
suspend operation and its supervisor will be asked how to handle the failure,
|
||||
which may mean resuming the actor, restarting it or terminating it completely.
|
||||
See :ref:`supervision-directives` for more information.
|
||||
|
||||
Use it like this:
|
||||
Use ``Kill`` like this:
|
||||
|
||||
.. includecode:: code/docs/actor/UntypedActorDocTestBase.java
|
||||
:include: import-actors
|
||||
|
|
|
|||
|
|
@ -441,6 +441,8 @@ message. This gives the best concurrency and scalability characteristics.
|
|||
|
||||
actor ! "hello"
|
||||
|
||||
.. _actors-tell-sender-scala:
|
||||
|
||||
If invoked from within an Actor, then the sending actor reference will be
|
||||
implicitly passed along with the message and available to the receiving Actor
|
||||
in its ``sender: ActorRef`` member field. The target actor can use this
|
||||
|
|
@ -626,6 +628,8 @@ enables cleaning up of resources:
|
|||
actor and create its replacement in response to the :class:`Terminated`
|
||||
message which will eventually arrive.
|
||||
|
||||
.. _poison-pill-scala:
|
||||
|
||||
PoisonPill
|
||||
----------
|
||||
|
||||
|
|
@ -756,18 +760,22 @@ actor's state which have the same property. The :class:`Stash` trait’s
|
|||
implementation of :meth:`preRestart` will call ``unstashAll()``, which is
|
||||
usually the desired behavior.
|
||||
|
||||
.. _killing-actors-scala:
|
||||
|
||||
Killing an Actor
|
||||
================
|
||||
|
||||
You can kill an actor by sending a ``Kill`` message. This will restart the actor
|
||||
through regular supervisor semantics.
|
||||
You can kill an actor by sending a ``Kill`` message. This will cause the actor
|
||||
to throw a :class:`ActorKilledException`, triggering a failure. The actor will
|
||||
suspend operation and its supervisor will be asked how to handle the failure,
|
||||
which may mean resuming the actor, restarting it or terminating it completely.
|
||||
See :ref:`supervision-directives` for more information.
|
||||
|
||||
Use it like this:
|
||||
Use ``Kill`` like this:
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
// kill the actor called 'victim'
|
||||
// kill the 'victim' actor
|
||||
victim ! Kill
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,103 @@
|
|||
/**
|
||||
* Copyright (C) 2009-2013 Typesafe Inc. <http://www.typesafe.com>
|
||||
*/
|
||||
package docs.routing
|
||||
|
||||
import akka.actor.{ Actor, ActorRef, ActorSystem, Props, ActorLogging }
|
||||
import akka.routing.ConsistentHashingRouter.ConsistentHashable
|
||||
import akka.routing.FromConfig
|
||||
import akka.routing.RoundRobinRouter
|
||||
import akka.testkit._
|
||||
import com.typesafe.config.ConfigFactory
|
||||
import scala.concurrent.duration._
|
||||
|
||||
object RouterViaProgramDocSpec {
|
||||
case class Message1(nbr: Int)
|
||||
|
||||
class ExampleActor1 extends Actor {
|
||||
def receive = {
|
||||
case m @ Message1(nbr) ⇒ sender ! (self, m)
|
||||
}
|
||||
}
|
||||
|
||||
class Echo extends Actor {
|
||||
def receive = {
|
||||
case m ⇒ sender ! m
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class RouterViaProgramDocSpec extends AkkaSpec with ImplicitSender {
|
||||
import RouterViaProgramDocSpec._
|
||||
|
||||
"demonstrate routees from paths" in {
|
||||
|
||||
//#programmaticRoutingRouteePaths
|
||||
val actor1 = system.actorOf(Props[ExampleActor1], "actor1")
|
||||
val actor2 = system.actorOf(Props[ExampleActor1], "actor2")
|
||||
val actor3 = system.actorOf(Props[ExampleActor1], "actor3")
|
||||
val routees = Vector[String]("/user/actor1", "/user/actor2", "/user/actor3")
|
||||
val router = system.actorOf(Props().withRouter(
|
||||
RoundRobinRouter(routees = routees)))
|
||||
//#programmaticRoutingRouteePaths
|
||||
1 to 6 foreach { i ⇒ router ! Message1(i) }
|
||||
val received = receiveN(6, 5.seconds.dilated)
|
||||
1 to 6 foreach { i ⇒
|
||||
val expectedActor = system.actorFor(routees((i - 1) % routees.length))
|
||||
val expectedMsg = Message1(i)
|
||||
received must contain[AnyRef]((expectedActor, expectedMsg))
|
||||
}
|
||||
}
|
||||
|
||||
"demonstrate broadcast" in {
|
||||
val router = system.actorOf(Props[Echo].withRouter(RoundRobinRouter(nrOfInstances = 5)))
|
||||
//#broadcastDavyJonesWarning
|
||||
import akka.routing.Broadcast
|
||||
router ! Broadcast("Watch out for Davy Jones' locker")
|
||||
//#broadcastDavyJonesWarning
|
||||
receiveN(5, 5.seconds.dilated) must have length (5)
|
||||
}
|
||||
|
||||
"demonstrate PoisonPill" in {
|
||||
val router = system.actorOf(Props[Echo].withRouter(RoundRobinRouter(nrOfInstances = 5)))
|
||||
//#poisonPill
|
||||
import akka.actor.PoisonPill
|
||||
router ! PoisonPill
|
||||
//#poisonPill
|
||||
expectNoMsg(1.seconds.dilated)
|
||||
router.isTerminated must be(true)
|
||||
}
|
||||
|
||||
"demonstrate broadcast of PoisonPill" in {
|
||||
val router = system.actorOf(Props[Echo].withRouter(RoundRobinRouter(nrOfInstances = 5)))
|
||||
//#broadcastPoisonPill
|
||||
import akka.actor.PoisonPill
|
||||
import akka.routing.Broadcast
|
||||
router ! Broadcast(PoisonPill)
|
||||
//#broadcastPoisonPill
|
||||
expectNoMsg(1.seconds.dilated)
|
||||
router.isTerminated must be(true)
|
||||
}
|
||||
|
||||
"demonstrate Kill" in {
|
||||
val router = system.actorOf(Props[Echo].withRouter(RoundRobinRouter(nrOfInstances = 5)))
|
||||
//#kill
|
||||
import akka.actor.Kill
|
||||
router ! Kill
|
||||
//#kill
|
||||
expectNoMsg(1.seconds.dilated)
|
||||
router.isTerminated must be(true)
|
||||
}
|
||||
|
||||
"demonstrate broadcast of Kill" in {
|
||||
val router = system.actorOf(Props[Echo].withRouter(RoundRobinRouter(nrOfInstances = 5)))
|
||||
//#broadcastKill
|
||||
import akka.actor.Kill
|
||||
import akka.routing.Broadcast
|
||||
router ! Broadcast(Kill)
|
||||
//#broadcastKill
|
||||
expectNoMsg(1.seconds.dilated)
|
||||
router.isTerminated must be(true)
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -138,6 +138,8 @@ actor systems has to have a JAR containing the class.
|
|||
``/foo/bar`` is considered **more specific** than ``/foo/*`` and only the highest priority match is used.
|
||||
Please note that it **cannot** be used to partially match section, like this: ``/foo*/bar``, ``/f*o/bar`` etc.
|
||||
|
||||
.. _remote-deployment-warnings-scala:
|
||||
|
||||
.. warning::
|
||||
|
||||
*Caveat:* Remote deployment ties both systems together in a tight fashion,
|
||||
|
|
|
|||
|
|
@ -4,11 +4,14 @@
|
|||
Routing (Scala)
|
||||
===============
|
||||
|
||||
A Router is an actor that routes incoming messages to outbound actors.
|
||||
The router routes the messages sent to it to its underlying actors called 'routees'.
|
||||
A Router is an actor that receives messages and efficiently routes them to other actors, known as
|
||||
its *routees*.
|
||||
|
||||
Akka comes with some defined routers out of the box, but as you will see in this chapter it
|
||||
is really easy to create your own. The routers shipped with Akka are:
|
||||
Different routing strategies can be used, according to your application's needs. Akka comes with
|
||||
several useful routing strategies right out of the box. But, as you will see in this chapter, it is
|
||||
also possible to :ref:`create your own <custom-router-scala>`.
|
||||
|
||||
The routers shipped with Akka are:
|
||||
|
||||
* ``akka.routing.RoundRobinRouter``
|
||||
* ``akka.routing.RandomRouter``
|
||||
|
|
@ -17,112 +20,120 @@ is really easy to create your own. The routers shipped with Akka are:
|
|||
* ``akka.routing.ScatterGatherFirstCompletedRouter``
|
||||
* ``akka.routing.ConsistentHashingRouter``
|
||||
|
||||
Routers In Action
|
||||
Routers in Action
|
||||
^^^^^^^^^^^^^^^^^
|
||||
|
||||
This is an example of how to create a router that is defined in configuration:
|
||||
|
||||
.. includecode:: code/docs/routing/RouterViaConfigDocSpec.scala#config-round-robin
|
||||
|
||||
.. includecode:: code/docs/routing/RouterViaConfigDocSpec.scala#configurableRouting
|
||||
|
||||
This is an example of how to programmatically create a router and set the number of routees it should create:
|
||||
|
||||
.. includecode:: code/docs/routing/RouterViaProgramExample.scala#programmaticRoutingNrOfInstances
|
||||
|
||||
You can also give the router already created routees as in:
|
||||
|
||||
.. includecode:: code/docs/routing/RouterViaProgramExample.scala#programmaticRoutingRoutees
|
||||
|
||||
.. note::
|
||||
|
||||
No actor factory or class needs to be provided in this
|
||||
case, as the ``Router`` will not create any children on its own (which is not
|
||||
true anymore when using a resizer). The routees can also be specified by giving
|
||||
their path strings.
|
||||
|
||||
|
||||
When you create a router programmatically 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.
|
||||
|
||||
*It is also worth pointing out that if you define the ``router`` in the
|
||||
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:
|
||||
Sending a message to a router is easy.
|
||||
|
||||
.. code-block:: scala
|
||||
|
||||
router ! MyMsg
|
||||
|
||||
The router will forward the message to its routees according to its routing policy.
|
||||
A router actor forwards messages to its routees according to its routing policy.
|
||||
|
||||
Remotely Deploying Routees
|
||||
**************************
|
||||
.. note::
|
||||
|
||||
In addition to being able to supply looked-up remote actors as routees, you can
|
||||
make the router deploy its created children on a set of remote hosts; this will
|
||||
be done in round-robin fashion. In order to do that, wrap the router
|
||||
configuration in a :class:`RemoteRouterConfig`, attaching the remote addresses of
|
||||
the nodes to deploy to. Naturally, this requires you to include the
|
||||
``akka-remote`` module on your classpath:
|
||||
In general, any message sent to a router will be sent onwards to its routees. But there are a
|
||||
few exceptions. These are documented in the :ref:`router-special-messages-scala` section below.
|
||||
|
||||
Creating a Router
|
||||
*****************
|
||||
|
||||
Routers and routees are closely intertwined. Router actors are created by specifying the desired
|
||||
*routee* :class:`Props` then attaching the router's :class:`RouterConfig`. When you create a router
|
||||
actor it will create routees, as needed, as its children.
|
||||
|
||||
For example, the following code and configuration snippets show how to create a :ref:`round-robin
|
||||
<round-robin-router-scala>` router that forwards messages to five ``ExampleActor`` routees. The
|
||||
routees will be created as the router's children.
|
||||
|
||||
.. includecode:: code/docs/routing/RouterViaConfigDocSpec.scala#config-round-robin
|
||||
|
||||
.. includecode:: code/docs/routing/RouterViaConfigDocSpec.scala#configurableRouting
|
||||
|
||||
Here is the same example, but with the router configuration provided programmatically instead of
|
||||
from configuration.
|
||||
|
||||
.. includecode:: code/docs/routing/RouterViaProgramExample.scala#programmaticRoutingNrOfInstances
|
||||
|
||||
Sometimes, rather than having the router create its routees, it is desirable to create routees
|
||||
separately and provide them to the router for its use. You can do this by passing an
|
||||
:class:`Iterable` of routees to the router's configuration.
|
||||
|
||||
The example below shows how to create a router by providing it with the :class:`ActorRef`\s of three
|
||||
routee actors.
|
||||
|
||||
.. includecode:: code/docs/routing/RouterViaProgramExample.scala#programmaticRoutingRoutees
|
||||
|
||||
Routees can also be specified by providing their path strings instead of their :class:`ActorRef`\s.
|
||||
|
||||
.. includecode:: code/docs/routing/RouterViaProgramDocSpec.scala#programmaticRoutingRouteePaths
|
||||
|
||||
In addition to being able to supply looked-up remote actors as routees, you can ask the router to
|
||||
deploy its created children on a set of remote hosts. Routees will be deployed in round-robin
|
||||
fashion. In order to deploy routees remotely, wrap the router configuration in a
|
||||
:class:`RemoteRouterConfig`, attaching the remote addresses of the nodes to deploy to. Remote
|
||||
deployment requires the ``akka-remote`` module to be included in the classpath.
|
||||
|
||||
.. includecode:: code/docs/routing/RouterViaProgramExample.scala#remoteRoutees
|
||||
|
||||
How Routing is Designed within Akka
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
There are a few gotchas to be aware of when creating routers:
|
||||
|
||||
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` (implementation detail, what the user gets is
|
||||
an :class:`ActorRef` as usual) 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.
|
||||
* If you define the ``router`` in the configuration file then this value will be used instead of any
|
||||
programmatically provided parameters.
|
||||
* Although routers can be configured in the configuration file, they must still be created
|
||||
programmatically, i.e. you cannot make a router through external configuration alone.
|
||||
* If you provide the ``routees`` in the router configuration then
|
||||
the value of ``nrOfInstances``, if provided, will be disregarded.
|
||||
* When you provide routees programmatically the router will generally ignore the routee
|
||||
:class:`Props`, as it does not need to create routees. However, if you use a :ref:`resizable
|
||||
router <resizable-routers-scala>` then the routee :class:`Props` will be used whenever the
|
||||
resizer creates new routees.
|
||||
* The same issues that apply to remotely-deployed actors also apply to remotely-deployed routees.
|
||||
Read about :ref:`the limitations of remote deployment <remote-deployment-warnings-scala>` for
|
||||
more information.
|
||||
|
||||
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:
|
||||
Routers, Routees and Senders
|
||||
****************************
|
||||
|
||||
.. includecode:: code/docs/actor/ActorDocSpec.scala#reply-with-sender
|
||||
The router forwards messages onto its routees without changing the original sender. When a routee
|
||||
replies to a routed message, the reply will be sent to the original sender, not to the router.
|
||||
|
||||
When a router creates routees, they are created as the routers children. This gives each routee its
|
||||
own identity in the actor system.
|
||||
|
||||
By default, when a routee sends a message, it will :ref:`implicitly set itself as the sender
|
||||
<actors-tell-sender-scala>`.
|
||||
|
||||
.. includecode:: code/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.
|
||||
However, it is often useful for routees to set the *router* as a sender. For example, you might want
|
||||
to set the router as the sender if you want to hide the details of the routees behind the router.
|
||||
The following code snippet shows how to set the parent router as sender.
|
||||
|
||||
Routers vs. Supervision
|
||||
.. includecode:: code/docs/actor/ActorDocSpec.scala#reply-with-sender
|
||||
|
||||
Note that different code would be needed if the routees were not children of the router, i.e. if
|
||||
they were provided when the router was created.
|
||||
|
||||
Routers and Supervision
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
As explained in the previous section, routers create new actor instances as
|
||||
children of the “head” router, who therefor also is their supervisor. The
|
||||
supervisor strategy of this actor can be configured by means of the
|
||||
:meth:`RouterConfig.supervisorStrategy` property, which is supported for all
|
||||
built-in router types. It defaults to “always escalate”, which leads to the
|
||||
application of the router’s parent’s supervision directive to all children of
|
||||
the router uniformly (i.e. not only the one which failed). It should be
|
||||
mentioned that the router overrides the default behavior of terminating all
|
||||
children upon restart, which means that a restart—while re-creating them—does
|
||||
not have an effect on the number of actors in the pool.
|
||||
Routees can be created by a router or provided to the router when it is created. Any routees that
|
||||
are created by a router will be created as the router's children. The router is therefore also the
|
||||
children's supervisor.
|
||||
|
||||
The supervision strategy of the router actor can be configured with the
|
||||
:meth:`RouterConfig.supervisorStrategy` property. If no configuration is provided, routers default
|
||||
to a strategy of “always escalate”. This means that errors are passed up to the router's supervisor
|
||||
for handling. The router's supervisor will decide what to do about any errors.
|
||||
|
||||
Note the router's supervisor will treat the error as an error with the router itself. Therefore a
|
||||
directive to stop or restart will cause the router *itself* to stop or restart. The router, in
|
||||
turn, will cause its children to stop and restart.
|
||||
|
||||
It should be mentioned that the router's restart behavior has been overridden so that a restart,
|
||||
while still re-creating the children, will still preserve the same number of actors in the pool.
|
||||
|
||||
Setting the strategy is easily done:
|
||||
|
||||
|
|
@ -134,6 +145,8 @@ Another potentially useful approach is to give the router the same strategy as
|
|||
its parent, which effectively treats all actors in the pool as if they were
|
||||
direct children of their grand-parent instead.
|
||||
|
||||
.. _note-router-terminated-children-scala:
|
||||
|
||||
.. note::
|
||||
|
||||
If the child of a router terminates, the router will not automatically spawn
|
||||
|
|
@ -152,6 +165,7 @@ and
|
|||
|
||||
.. includecode:: code/docs/routing/RouterTypeExample.scala#fibonacciActor
|
||||
|
||||
.. _round-robin-router-scala:
|
||||
|
||||
RoundRobinRouter
|
||||
****************
|
||||
|
|
@ -258,6 +272,12 @@ This is an example of how to define a broadcast router in configuration:
|
|||
|
||||
.. includecode:: code/docs/routing/RouterViaConfigDocSpec.scala#config-broadcast
|
||||
|
||||
.. note::
|
||||
|
||||
Broadcast routers always broadcast *every* message to their routees. If you do not want to
|
||||
broadcast every message, then you can use a non-broadcasting router and use
|
||||
:ref:`broadcast-messages-scala` as needed.
|
||||
|
||||
|
||||
ScatterGatherFirstCompletedRouter
|
||||
*********************************
|
||||
|
|
@ -320,19 +340,94 @@ This is an example of how to define a consistent-hashing router in configuration
|
|||
|
||||
.. includecode:: code/docs/routing/RouterViaConfigDocSpec.scala#config-consistent-hashing
|
||||
|
||||
.. _router-special-messages-scala:
|
||||
|
||||
Handling for Special Messages
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Most messages sent to routers will be forwarded according to the routers' usual routing rules.
|
||||
However there are a few types of messages that have special behavior.
|
||||
|
||||
.. _broadcast-messages-scala:
|
||||
|
||||
Broadcast Messages
|
||||
^^^^^^^^^^^^^^^^^^
|
||||
******************
|
||||
|
||||
There is a special type of message that will be sent to all routees regardless of the router.
|
||||
This message is called ``Broadcast`` and is used in the following manner:
|
||||
A ``Broadcast`` message can be used to send a message to *all* of a router's routees. When a router
|
||||
receives a ``Broadcast`` message, it will broadcast that message's *payload* to all routees, no
|
||||
matter how that router would normally route its messages.
|
||||
|
||||
.. code-block:: scala
|
||||
The example below shows how you would use a ``Broadcast`` message to send a very important message
|
||||
to every routee of a router.
|
||||
|
||||
router ! Broadcast("Watch out for Davy Jones' locker")
|
||||
.. includecode:: code/docs/routing/RouterViaProgramDocSpec.scala#broadcastDavyJonesWarning
|
||||
|
||||
Only the actual message is forwarded to the routees, i.e. "Watch out for Davy Jones' locker" in the example above.
|
||||
It is up to the routee implementation whether to handle the broadcast message or not.
|
||||
In this example the router receives the ``Broadcast`` message, extracts its payload
|
||||
(``"Watch out for Davy Jones' locker"``), and then sends the payload on to all of the router's
|
||||
routees. It is up to each each routee actor to handle the received payload message.
|
||||
|
||||
PoisonPill Messages
|
||||
*******************
|
||||
|
||||
A ``PoisonPill`` message has special handling for all actors, including for routers. When any actor
|
||||
receives a ``PoisonPill`` message, that actor will be stopped. See the :ref:`poison-pill-scala`
|
||||
documentation for details.
|
||||
|
||||
.. includecode:: code/docs/routing/RouterViaProgramDocSpec.scala#poisonPill
|
||||
|
||||
For a router, which normally passes on messages to routees, it is important to realised that
|
||||
``PoisonPill`` messages are processed by the router only. ``PoisonPill`` messages sent to a router
|
||||
will *not* be sent on to routees.
|
||||
|
||||
However, a ``PoisonPill`` message sent to a router may still affect its routees, because it will
|
||||
stop the router and when the router stops it also stops its children. Stopping children is normal
|
||||
actor behavior. The router will stop routees that it has created as children. Each child will
|
||||
process its current message and then tstop. This may lead to some messages being unprocessed.
|
||||
See the documentation on :ref:`stopping-actors-scala` for more information.
|
||||
|
||||
If you wish to stop a router and its routees, but you would like the routees to first process all
|
||||
the messages currently in their mailboxes, then you should not send a ``PoisonPill`` message to the
|
||||
router. Instead you should wrap a ``PoisonPill`` message inside a broadcast message so that each
|
||||
routee will the ``PoisonPill`` message directly. Note that this will stop all routees, even if the
|
||||
routees aren't children of the router, i.e. even routees programmatically provided to the router.
|
||||
|
||||
.. includecode:: code/docs/routing/RouterViaProgramDocSpec.scala#broadcastPoisonPill
|
||||
|
||||
With the code shown above, each routee will receive a ``PoisonPill`` message. Each routee will
|
||||
continue to process its messages as normal, eventually processing the ``PoisonPill``. This will
|
||||
cause the routee to stop. After all routees have stopped the router will itself be :ref:`stopped
|
||||
automatically <note-router-terminated-children-scala>`.
|
||||
|
||||
.. note::
|
||||
|
||||
Brendan W McAdams' excellent blog post `Distributing Akka Workloads - And Shutting Down Afterwards
|
||||
<http://blog.evilmonkeylabs.com/2013/01/17/Distributing_Akka_Workloads_And_Shutting_Down_After/>`_
|
||||
discusses in more detail how ``PoisonPill`` messages can be used to shut down routers and routees.
|
||||
|
||||
Kill Messages
|
||||
*************
|
||||
|
||||
``Kill`` messages are another type of message that has special handling. See
|
||||
:ref:`killing-actors-scala` for general information about how actors handle ``Kill`` messages.
|
||||
|
||||
When a ``Kill`` message is sent to a router the router processes the message internally, and does
|
||||
*not* send it on to its routees. The router will throw an :class:`ActorKilledException` and fail. It
|
||||
will then be either resumed, restarted or terminated, depending how it is supervised.
|
||||
|
||||
Routees that are children of the router will also be suspended, and will be affected by the
|
||||
supervision directive that is applied to the router. Routees that are not the routers children, i.e.
|
||||
those that were created externally to the router, will not be affected.
|
||||
|
||||
.. includecode:: code/docs/routing/RouterViaProgramDocSpec.scala#kill
|
||||
|
||||
As with the ``PoisonPill`` message, there is a distinction between killing a router, which
|
||||
indirectly kills its children (who happen to be routees), and killing routees directly (some of whom
|
||||
may not be children.) To kill routees directly the router should be sent a ``Kill`` message wrapped
|
||||
in a ``Broadcast`` message.
|
||||
|
||||
.. includecode:: code/docs/routing/RouterViaProgramDocSpec.scala#broadcastKill
|
||||
|
||||
.. _resizable-routers-scala:
|
||||
|
||||
Dynamically Resizable Routers
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
|
@ -366,12 +461,43 @@ will be used instead of any programmatically sent parameters.*
|
|||
this, configure the pool to use a balancing dispatcher, see `Configuring
|
||||
Dispatchers`_ for more information.
|
||||
|
||||
.. _router-design-scala:
|
||||
|
||||
How Routing is Designed within Akka
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
On the surface routers look like normal actors, but they are actually implemented differently.
|
||||
Routers are designed to be extremely efficient at receiving messages and passing them quickly on to
|
||||
routees.
|
||||
|
||||
A normal actor can be used for routing messages, but an actor's single-threaded processing can
|
||||
become a bottleneck. Routers can achieve much higher throughput with an optimization to the usual
|
||||
message-processing pipeline that allows concurrent routing. This is achieved by embedding routers'
|
||||
routing logic directly in their :class:`ActorRef` rather than in the router actor. Messages sent to
|
||||
a router's :class:`ActorRef` can be immediately routed to the routee, bypassing the single-threaded
|
||||
router actor entirely.
|
||||
|
||||
The cost to this is, of course, that the internals of routing code are more complicated than if
|
||||
routers were implemented with normal actors. Fortunately all of this complexity is invisible to
|
||||
consumers of the routing API. However, it is something to be aware of when implementing your own
|
||||
routers.
|
||||
|
||||
.. _custom-router-scala:
|
||||
|
||||
Custom Router
|
||||
^^^^^^^^^^^^^
|
||||
|
||||
You can also create your own router should you not find any of the ones provided by Akka sufficient for your needs.
|
||||
You can create your own router should you not find any of the ones provided by Akka sufficient for your needs.
|
||||
In order to roll your own router you have to fulfill certain criteria which are explained in this section.
|
||||
|
||||
Before creating your own router you should consider whether a normal actor with router-like
|
||||
behavior might do the job just as well as a full-blown router. As explained
|
||||
:ref:`above <router-design-scala>`, the primary benefit of routers over normal actors is their
|
||||
higher performance. But they are somewhat more complicated to write than normal actors. Therefore if
|
||||
lower maximum throughput is acceptable in your application you may wish to stick with traditional
|
||||
actors. This section, however, assumes that you wish to get maximum performance and so demonstrates
|
||||
how you can create your own router.
|
||||
|
||||
The router created in this example is a simple vote counter. It will route the votes to specific vote counter actors.
|
||||
In this case we only have two parties the Republicans and the Democrats. We would like a router that forwards all
|
||||
democrat related messages to the Democrat actor and all republican related messages to the Republican actor.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue