!act,rem,clu #3549 Simplify and enhance routers
* Separate routing logic, to be usable stand alone, e.g. in actors * Simplify RouterConfig, only a factory * Move reading of config from Deployer to the RouterConfig * Distiction between Pool and Group router types * Remove usage of actorFor, use ActorSelection * Management messages to add and remove routees * Simplify the internals of RoutedActorCell & co * Move resize specific code to separate RoutedActorCell subclass * Change resizer api to only return capacity change * Resizer only allowed together with Pool * Re-implement all routers, and keep old api during deprecation phase * Replace ClusterRouterConfig, deprecation * Rewrite documentation * Migration guide * Also includes related ticket: +act #3087 Create nicer Props factories for RouterConfig
This commit is contained in:
parent
81ca6fe8c8
commit
ebadd567b2
104 changed files with 9671 additions and 5006 deletions
|
|
@ -2,102 +2,110 @@
|
|||
.. _routing-scala:
|
||||
|
||||
Routing
|
||||
===============
|
||||
=======
|
||||
|
||||
A Router is an actor that receives messages and efficiently routes them to other actors, known as
|
||||
its *routees*.
|
||||
Messages can be sent via a router to efficiently route them to destination actors, known as
|
||||
its *routees*. A ``Router`` can be used inside or outside of an actor, and you can manage the
|
||||
routees yourselves or use a self contained router actor with configuration capabilities.
|
||||
|
||||
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:
|
||||
.. _simple-router-scala:
|
||||
|
||||
* ``akka.routing.RoundRobinRouter``
|
||||
* ``akka.routing.RandomRouter``
|
||||
* ``akka.routing.SmallestMailboxRouter``
|
||||
* ``akka.routing.BroadcastRouter``
|
||||
* ``akka.routing.ScatterGatherFirstCompletedRouter``
|
||||
* ``akka.routing.ConsistentHashingRouter``
|
||||
A Simple Router
|
||||
^^^^^^^^^^^^^^^
|
||||
|
||||
Routers in Action
|
||||
^^^^^^^^^^^^^^^^^
|
||||
The following example illustrates how to use a ``Router`` and manage the routees from within an actor.
|
||||
|
||||
Sending a message to a router is easy.
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#router-in-actor
|
||||
|
||||
.. code-block:: scala
|
||||
We create a ``Router`` and specify that it should use ``RoundRobinRoutingLogic`` when routing the
|
||||
messages to the routees.
|
||||
|
||||
router ! MyMsg
|
||||
The routing logic shipped with Akka are:
|
||||
|
||||
A router actor forwards messages to its routees according to its routing policy.
|
||||
* ``akka.routing.RoundRobinRoutingLogic``
|
||||
* ``akka.routing.RandomRoutingLogic``
|
||||
* ``akka.routing.SmallestMailboxRoutingLogic``
|
||||
* ``akka.routing.BroadcastRoutingLogic``
|
||||
* ``akka.routing.ScatterGatherFirstCompletedRoutingLogic``
|
||||
* ``akka.routing.ConsistentHashingRoutingLogic``
|
||||
|
||||
We create the routees as ordinary child actors wrapped in ``ActorRefRoutee``. We watch
|
||||
the routees to be able to replace them if they are terminated.
|
||||
|
||||
Sending messages via the router is done with the ``route`` method, as is done for the ``Work`` messages
|
||||
in the example above.
|
||||
|
||||
The ``Router`` is immutable and the ``RoutingLogic`` is thread safe; meaning that they can also be used
|
||||
outside of actors.
|
||||
|
||||
.. note::
|
||||
|
||||
In general, any message sent to a router will be sent onwards to its routees. But there are a
|
||||
In general, any message sent to a router will be sent onwards to its routees, but there is one exception.
|
||||
The special :ref:`broadcast-messages-scala` will send to *all* of a router's routees
|
||||
|
||||
A Router Actor
|
||||
^^^^^^^^^^^^^^
|
||||
|
||||
A router can also be created as a self contained actor that manages the routees itself and
|
||||
loads routing logic and other settings from configuration.
|
||||
|
||||
This type of router actor comes in two distinct flavors:
|
||||
|
||||
* Pool - The router creates routees as child actors and removes them from the router if they
|
||||
terminate.
|
||||
|
||||
* Group - The routee actors are created externally to the router and the router sends
|
||||
messages to the specified path using actor selection, without watching for termination.
|
||||
|
||||
The settings for a router actor can be defined in configuration or programmatically.
|
||||
Although router actors can be defined in the configuration file, they must still be created
|
||||
programmatically, i.e. you cannot make a router through external configuration alone.
|
||||
If you define the router actor in the configuration file then these settings will be used
|
||||
instead of any programmatically provided parameters.
|
||||
|
||||
You send messages to the routees via the router actor in the same way as for ordinary actors,
|
||||
i.e. via its ``ActorRef``. The router actor 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 actor.
|
||||
|
||||
.. note::
|
||||
|
||||
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
|
||||
*****************
|
||||
Pool
|
||||
----
|
||||
|
||||
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
|
||||
The following code and configuration snippets show how to create a :ref:`round-robin
|
||||
<round-robin-router-scala>` router that forwards messages to five ``Worker`` routees. The
|
||||
routees will be created as the router's children.
|
||||
|
||||
.. includecode:: code/docs/routing/RouterViaConfigDocSpec.scala#config-round-robin
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#config-round-robin-pool
|
||||
|
||||
.. includecode:: code/docs/routing/RouterViaConfigDocSpec.scala#configurableRouting
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#round-robin-pool-1
|
||||
|
||||
Here is the same example, but with the router configuration provided programmatically instead of
|
||||
from configuration.
|
||||
|
||||
.. includecode:: code/docs/routing/RouterViaProgramExample.scala#programmaticRoutingNrOfInstances
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#round-robin-pool-2
|
||||
|
||||
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.
|
||||
Remote Deployed Routees
|
||||
***********************
|
||||
|
||||
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
|
||||
In addition to being able to create local actors as routees, you can instruct 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
|
||||
``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
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#remoteRoutees
|
||||
|
||||
There are a few gotchas to be aware of when creating routers:
|
||||
|
||||
* 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.
|
||||
|
||||
Routers, Routees and Senders
|
||||
****************************
|
||||
|
||||
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.
|
||||
Senders
|
||||
*******
|
||||
|
||||
By default, when a routee sends a message, it will :ref:`implicitly set itself as the sender
|
||||
<actors-tell-sender-scala>`.
|
||||
|
|
@ -110,18 +118,15 @@ The following code snippet shows how to set the parent router as sender.
|
|||
|
||||
.. 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
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Supervision
|
||||
***********
|
||||
|
||||
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.
|
||||
Routees that are created by a pool 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
|
||||
``supervisorStrategy`` property of the Pool. 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.
|
||||
|
||||
|
|
@ -142,97 +147,115 @@ by specifying the strategy when defining the router.
|
|||
Setting the strategy is easily done:
|
||||
|
||||
.. includecode:: ../../../akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala#supervision
|
||||
:include: supervision
|
||||
:exclude: custom-strategy
|
||||
|
||||
.. _note-router-terminated-children-scala:
|
||||
|
||||
.. note::
|
||||
|
||||
If the child of a router terminates, the router will not automatically spawn
|
||||
a new child. In the event that all children of a router have terminated the
|
||||
If the child of a pool router terminates, the pool router will not automatically spawn
|
||||
a new child. In the event that all children of a pool router have terminated the
|
||||
router will terminate itself unless it is a dynamic router, e.g. using
|
||||
a resizer.
|
||||
|
||||
Group
|
||||
-----
|
||||
|
||||
Sometimes, rather than having the router actor 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
|
||||
paths of the routees to the router's configuration. Messages will be sent with ``ActorSelection``
|
||||
to these paths.
|
||||
|
||||
The example below shows how to create a router by providing it with the path strings of three
|
||||
routee actors.
|
||||
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#config-round-robin-group
|
||||
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#round-robin-group-1
|
||||
|
||||
Here is the same example, but with the router configuration provided programmatically instead of
|
||||
from configuration.
|
||||
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#round-robin-group-2
|
||||
|
||||
The routee actors are created externally from the router:
|
||||
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#create-workers
|
||||
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#create-worker-actors
|
||||
|
||||
The paths may contain protocol and address information for actors running on remote hosts.
|
||||
Remoting requires the ``akka-remote`` module to be included in the classpath.
|
||||
|
||||
Router usage
|
||||
^^^^^^^^^^^^
|
||||
|
||||
In this section we will describe how to use the different router types.
|
||||
First we need to create some actors that will be used in the examples:
|
||||
In this section we will describe how to create the different types of router actors.
|
||||
|
||||
.. includecode:: code/docs/routing/RouterTypeExample.scala#printlnActor
|
||||
The router actors in this section are created from within a top level actor named ``parent``.
|
||||
Note that deployment paths in the configuration starts with ``/parent/`` followed by the name
|
||||
of the router actor.
|
||||
|
||||
and
|
||||
|
||||
.. includecode:: code/docs/routing/RouterTypeExample.scala#fibonacciActor
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#create-parent
|
||||
|
||||
.. _round-robin-router-scala:
|
||||
|
||||
RoundRobinRouter
|
||||
****************
|
||||
RoundRobinPool and RoundRobinGroup
|
||||
----------------------------------
|
||||
|
||||
Routes in a `round-robin <http://en.wikipedia.org/wiki/Round-robin>`_ fashion to its routees.
|
||||
Code example:
|
||||
|
||||
.. includecode:: code/docs/routing/RouterTypeExample.scala#roundRobinRouter
|
||||
RoundRobinPool defined in configuration:
|
||||
|
||||
When run you should see a similar output to this:
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#config-round-robin-pool
|
||||
|
||||
.. code-block:: scala
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#round-robin-pool-1
|
||||
|
||||
Received message '1' in actor $b
|
||||
Received message '2' in actor $c
|
||||
Received message '3' in actor $d
|
||||
Received message '6' in actor $b
|
||||
Received message '4' in actor $e
|
||||
Received message '8' in actor $d
|
||||
Received message '5' in actor $f
|
||||
Received message '9' in actor $e
|
||||
Received message '10' in actor $f
|
||||
Received message '7' in actor $c
|
||||
RoundRobinPool defined in code:
|
||||
|
||||
If you look closely to the output you can see that each of the routees received two messages which
|
||||
is exactly what you would expect from a round-robin router to happen.
|
||||
(The name of an actor is automatically created in the format ``$letter`` unless you specify it -
|
||||
hence the names printed above.)
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#round-robin-pool-2
|
||||
|
||||
This is an example of how to define a round-robin router in configuration:
|
||||
RoundRobinGroup defined in configuration:
|
||||
|
||||
.. includecode:: code/docs/routing/RouterViaConfigDocSpec.scala#config-round-robin
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#config-round-robin-group
|
||||
|
||||
RandomRouter
|
||||
************
|
||||
As the name implies this router type selects one of its routees randomly and forwards
|
||||
the message it receives to this routee.
|
||||
This procedure will happen each time it receives a message.
|
||||
Code example:
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#round-robin-group-1
|
||||
|
||||
.. includecode:: code/docs/routing/RouterTypeExample.scala#randomRouter
|
||||
RoundRobinGroup defined in code:
|
||||
|
||||
When run you should see a similar output to this:
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala
|
||||
:include: paths,round-robin-group-2
|
||||
|
||||
.. code-block:: scala
|
||||
RandomPool and RandomGroup
|
||||
--------------------------
|
||||
|
||||
Received message '1' in actor $e
|
||||
Received message '2' in actor $c
|
||||
Received message '4' in actor $b
|
||||
Received message '5' in actor $d
|
||||
Received message '3' in actor $e
|
||||
Received message '6' in actor $c
|
||||
Received message '7' in actor $d
|
||||
Received message '8' in actor $e
|
||||
Received message '9' in actor $d
|
||||
Received message '10' in actor $d
|
||||
This router type selects one of its routees randomly for each message.
|
||||
|
||||
The result from running the random router should be different, or at least random, every time you run it.
|
||||
Try to run it a couple of times to verify its behavior if you don't trust us.
|
||||
RandomPool defined in configuration:
|
||||
|
||||
This is an example of how to define a random router in configuration:
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#config-random-pool
|
||||
|
||||
.. includecode:: code/docs/routing/RouterViaConfigDocSpec.scala#config-random
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#random-pool-1
|
||||
|
||||
SmallestMailboxRouter
|
||||
*********************
|
||||
A Router that tries to send to the non-suspended routee with fewest messages in mailbox.
|
||||
RandomPool defined in code:
|
||||
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#random-pool-2
|
||||
|
||||
RandomGroup defined in configuration:
|
||||
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#config-random-group
|
||||
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#random-group-1
|
||||
|
||||
RandomGroup defined in code:
|
||||
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala
|
||||
:include: paths,random-group-2
|
||||
|
||||
|
||||
SmallestMailboxPool
|
||||
-------------------
|
||||
|
||||
A Router that tries to send to the non-suspended child routee with fewest messages in mailbox.
|
||||
The selection is done in this order:
|
||||
|
||||
* pick any idle routee (not processing message) with empty mailbox
|
||||
|
|
@ -241,37 +264,45 @@ The selection is done in this order:
|
|||
* pick any remote routee, remote actors are consider lowest priority,
|
||||
since their mailbox size is unknown
|
||||
|
||||
Code example:
|
||||
SmallestMailboxPool defined in configuration:
|
||||
|
||||
.. includecode:: code/docs/routing/RouterTypeExample.scala#smallestMailboxRouter
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#config-smallest-mailbox-pool
|
||||
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#smallest-mailbox-pool-1
|
||||
|
||||
This is an example of how to define a smallest-mailbox router in configuration:
|
||||
SmallestMailboxPool defined in code:
|
||||
|
||||
.. includecode:: code/docs/routing/RouterViaConfigDocSpec.scala#config-smallest-mailbox
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#smallest-mailbox-pool-2
|
||||
|
||||
There is no Group variant of the SmallestMailboxPool because the size of the mailbox
|
||||
and the internal dispatching state of the actor is not practically available from the paths
|
||||
of the routees.
|
||||
|
||||
BroadcastPool and BroadcastGroup
|
||||
--------------------------------
|
||||
|
||||
BroadcastRouter
|
||||
***************
|
||||
A broadcast router forwards the message it receives to *all* its routees.
|
||||
Code example:
|
||||
|
||||
.. includecode:: code/docs/routing/RouterTypeExample.scala#broadcastRouter
|
||||
BroadcastPool defined in configuration:
|
||||
|
||||
When run you should see a similar output to this:
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#config-broadcast-pool
|
||||
|
||||
.. code-block:: scala
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#broadcast-pool-1
|
||||
|
||||
Received message 'this is a broadcast message' in actor $f
|
||||
Received message 'this is a broadcast message' in actor $d
|
||||
Received message 'this is a broadcast message' in actor $e
|
||||
Received message 'this is a broadcast message' in actor $c
|
||||
Received message 'this is a broadcast message' in actor $b
|
||||
BroadcastPool defined in code:
|
||||
|
||||
As you can see here above each of the routees, five in total, received the broadcast message.
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#broadcast-pool-2
|
||||
|
||||
This is an example of how to define a broadcast router in configuration:
|
||||
BroadcastGroup defined in configuration:
|
||||
|
||||
.. includecode:: code/docs/routing/RouterViaConfigDocSpec.scala#config-broadcast
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#config-broadcast-group
|
||||
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#broadcast-group-1
|
||||
|
||||
BroadcastGroup defined in code:
|
||||
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala
|
||||
:include: paths,broadcast-group-2
|
||||
|
||||
.. note::
|
||||
|
||||
|
|
@ -280,33 +311,42 @@ This is an example of how to define a broadcast router in configuration:
|
|||
:ref:`broadcast-messages-scala` as needed.
|
||||
|
||||
|
||||
ScatterGatherFirstCompletedRouter
|
||||
*********************************
|
||||
The ScatterGatherFirstCompletedRouter will send the message on to all its routees as a future.
|
||||
It then waits for first result it gets back. This result will be sent back to original sender.
|
||||
Code example:
|
||||
ScatterGatherFirstCompletedPool and ScatterGatherFirstCompletedGroup
|
||||
--------------------------------------------------------------------
|
||||
|
||||
.. includecode:: code/docs/routing/RouterTypeExample.scala#scatterGatherFirstCompletedRouter
|
||||
The ScatterGatherFirstCompletedRouter will send the message on to all its routees.
|
||||
It then waits for first reply it gets back. This result will be sent back to original sender.
|
||||
Other replies are discarded.
|
||||
|
||||
When run you should see this:
|
||||
It is expecting at least one reply within a configured duration, otherwise it will reply with
|
||||
``akka.pattern.AskTimeoutException`` in a ``akka.actor.Status.Failure``.
|
||||
|
||||
.. code-block:: scala
|
||||
ScatterGatherFirstCompletedPool defined in configuration:
|
||||
|
||||
The result of calculating Fibonacci for 10 is 55
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#config-scatter-gather-pool
|
||||
|
||||
From the output above you can't really see that all the routees performed the calculation, but they did!
|
||||
The result you see is from the first routee that returned its calculation to the router.
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#scatter-gather-pool-1
|
||||
|
||||
This is an example of how to define a scatter-gather router in configuration:
|
||||
ScatterGatherFirstCompletedPool defined in code:
|
||||
|
||||
.. includecode:: code/docs/routing/RouterViaConfigDocSpec.scala#config-scatter-gather
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#scatter-gather-pool-2
|
||||
|
||||
ScatterGatherFirstCompletedGroup defined in configuration:
|
||||
|
||||
ConsistentHashingRouter
|
||||
***********************
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#config-scatter-gather-group
|
||||
|
||||
The ConsistentHashingRouter uses `consistent hashing <http://en.wikipedia.org/wiki/Consistent_hashing>`_
|
||||
to select a connection based on the sent message. This
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#scatter-gather-group-1
|
||||
|
||||
ScatterGatherFirstCompletedGroup defined in code:
|
||||
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala
|
||||
:include: paths,scatter-gather-group-2
|
||||
|
||||
ConsistentHashingPool and ConsistentHashingGroup
|
||||
------------------------------------------------
|
||||
|
||||
The ConsistentHashingPool uses `consistent hashing <http://en.wikipedia.org/wiki/Consistent_hashing>`_
|
||||
to select a routee based on the sent message. This
|
||||
`article <http://weblogs.java.net/blog/tomwhite/archive/2007/11/consistent_hash.html>`_ gives good
|
||||
insight into how consistent hashing is implemented.
|
||||
|
||||
|
|
@ -327,6 +367,7 @@ There is 3 ways to define what data to use for the consistent hash key.
|
|||
These ways to define the consistent hash key can be use together and at
|
||||
the same time for one router. The ``hashMapping`` is tried first.
|
||||
|
||||
|
||||
Code example:
|
||||
|
||||
.. includecode:: code/docs/routing/ConsistentHashingRouterDocSpec.scala#cache-actor
|
||||
|
|
@ -337,22 +378,47 @@ In the above example you see that the ``Get`` message implements ``ConsistentHas
|
|||
while the ``Entry`` message is wrapped in a ``ConsistentHashableEnvelope``. The ``Evict``
|
||||
message is handled by the ``hashMapping`` partial function.
|
||||
|
||||
This is an example of how to define a consistent-hashing router in configuration:
|
||||
ConsistentHashingPool defined in configuration:
|
||||
|
||||
.. includecode:: code/docs/routing/RouterViaConfigDocSpec.scala#config-consistent-hashing
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#config-consistent-hashing-pool
|
||||
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#consistent-hashing-pool-1
|
||||
|
||||
ConsistentHashingPool defined in code:
|
||||
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#consistent-hashing-pool-2
|
||||
|
||||
ConsistentHashingGroup defined in configuration:
|
||||
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#config-consistent-hashing-group
|
||||
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#consistent-hashing-group-1
|
||||
|
||||
ConsistentHashingGroup defined in code:
|
||||
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala
|
||||
:include: paths,consistent-hashing-group-2
|
||||
|
||||
|
||||
``virtual-nodes-factor`` is the number of virtual nodes per routee that is used in the
|
||||
consistent hash node ring to make the distribution more uniform.
|
||||
|
||||
.. _router-special-messages-scala:
|
||||
|
||||
Handling for Special Messages
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Specially Handled Messages
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
Most messages sent to routers will be forwarded according to the routers' usual routing rules.
|
||||
Most messages sent to router actors will be forwarded according to the routers' routing logic.
|
||||
However there are a few types of messages that have special behavior.
|
||||
|
||||
Note that these special messages, except for the ``Broadcast`` message, are only handled by
|
||||
self contained router actors and not by the ``akka.routing.Router`` component described
|
||||
in :ref:`simple-router-scala`.
|
||||
|
||||
.. _broadcast-messages-scala:
|
||||
|
||||
Broadcast Messages
|
||||
******************
|
||||
------------------
|
||||
|
||||
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
|
||||
|
|
@ -361,38 +427,38 @@ matter how that router would normally route its messages.
|
|||
The example below shows how you would use a ``Broadcast`` message to send a very important message
|
||||
to every routee of a router.
|
||||
|
||||
.. includecode:: code/docs/routing/RouterViaProgramDocSpec.scala#broadcastDavyJonesWarning
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#broadcastDavyJonesWarning
|
||||
|
||||
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
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#poisonPill
|
||||
|
||||
For a router, which normally passes on messages to routees, it is important to realised that
|
||||
For a router, which normally passes on messages to routees, it is important to realise 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.
|
||||
process its current message and then stop. 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
|
||||
router. Instead you should wrap a ``PoisonPill`` message inside a ``Broadcast`` message so that each
|
||||
routee will receive the ``PoisonPill`` message. 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
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.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
|
||||
|
|
@ -407,48 +473,63 @@ a resizer.
|
|||
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
|
||||
*not* send it on to its routees. The router will throw an ``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
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.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
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#broadcastKill
|
||||
|
||||
Managagement Messages
|
||||
---------------------
|
||||
|
||||
* Sending ``akka.routing.GetRoutees`` to a router actor will make it send back its currently used routees
|
||||
in a ``akka.routing.Routees`` message.
|
||||
* Sending ``akka.routing.AddRoutee`` to a router actor will add that routee to its collection of routees.
|
||||
* Sending ``akka.routing.RemoveRoutee`` to a router actor will remove that routee to its collection of routees.
|
||||
* Sending ``akka.routing.AdjustPoolSize`` to a pool router actor will add or remove that number of routees to
|
||||
its collection of routees.
|
||||
|
||||
These management messages may be handled after other messages, so if you send ``AddRoutee`` immediately followed
|
||||
an ordinary message you are not guaranteed that the routees have been changed when the ordinary message
|
||||
is routed. If you need to know when the change has been applied you can send ``AddRoutee`` followed by ``GetRoutees``
|
||||
and when you receive the ``Routees`` reply you know that the preceeding change has been applied.
|
||||
|
||||
.. _resizable-routers-scala:
|
||||
|
||||
Dynamically Resizable Routers
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
Dynamically Resizable Pool
|
||||
^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
All routers can be used with a fixed number of routees or with a resize strategy to adjust the number
|
||||
All pools can be used with a fixed number of routees or with a resize strategy to adjust the number
|
||||
of routees dynamically.
|
||||
|
||||
This is an example of how to create a resizable router that is defined in configuration:
|
||||
Pool with resizer defined in configuration:
|
||||
|
||||
.. includecode:: code/docs/routing/RouterViaConfigDocSpec.scala#config-resize
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#config-resize-pool
|
||||
|
||||
.. includecode:: code/docs/routing/RouterViaConfigDocSpec.scala#configurableRoutingWithResizer
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#resize-pool-1
|
||||
|
||||
Several more configuration options are available and described in ``akka.actor.deployment.default.resizer``
|
||||
section of the reference :ref:`configuration`.
|
||||
|
||||
This is an example of how to programmatically create a resizable router:
|
||||
Pool with resizer defined in code:
|
||||
|
||||
.. includecode:: code/docs/routing/RouterViaProgramExample.scala#programmaticRoutingWithResizer
|
||||
.. includecode:: code/docs/routing/RouterDocSpec.scala#resize-pool-2
|
||||
|
||||
*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.*
|
||||
|
|
@ -457,7 +538,7 @@ will be used instead of any programmatically sent parameters.*
|
|||
|
||||
Resizing is triggered by sending messages to the actor pool, but it is not
|
||||
completed synchronously; instead a message is sent to the “head”
|
||||
:class:`Router` to perform the size change. Thus you cannot rely on resizing
|
||||
``RouterActor`` to perform the size change. Thus you cannot rely on resizing
|
||||
to instantaneously create new workers when all others are busy, because the
|
||||
message just sent will be queued to the mailbox of a busy actor. To remedy
|
||||
this, configure the pool to use a balancing dispatcher, see `Configuring
|
||||
|
|
@ -475,8 +556,8 @@ 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
|
||||
routing logic directly in their ``ActorRef`` rather than in the router actor. Messages sent to
|
||||
a router's ``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
|
||||
|
|
@ -500,80 +581,54 @@ lower maximum throughput is acceptable in your application you may wish to stick
|
|||
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.
|
||||
The router created in this example is replicating each message to a few destinations.
|
||||
|
||||
We begin with defining the class:
|
||||
Start with the routing logic:
|
||||
|
||||
.. includecode:: ../../../akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala#crRouter
|
||||
:exclude: crRoute
|
||||
.. includecode:: code/docs/routing/CustomRouterDocSpec.scala#routing-logic
|
||||
|
||||
The next step is to implement the ``createRoute`` method in the class just defined:
|
||||
``select`` will be called for each message and in this example pick a few destinations by round-robin,
|
||||
by reusing the existing ``RoundRobinRoutingLogic`` and wrap the result in a ``SeveralRoutees``
|
||||
instance. ``SeveralRoutees`` will send the message to all of the supplied routues.
|
||||
|
||||
.. includecode:: ../../../akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala#crRoute
|
||||
The implementation of the routing logic must be thread safe, since it might be used outside of actors.
|
||||
|
||||
As you can see above we start off by creating the routees and put them in a collection.
|
||||
A unit test of the routing logic:
|
||||
|
||||
Make sure that you don't miss to implement the line below as it is *really* important.
|
||||
It registers the routees internally and failing to call this method will
|
||||
cause a ``ActorInitializationException`` to be thrown when the router is used.
|
||||
Therefore always make sure to do the following in your custom router:
|
||||
.. includecode:: code/docs/routing/CustomRouterDocSpec.scala#unit-test-logic
|
||||
|
||||
.. includecode:: ../../../akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala#crRegisterRoutees
|
||||
You could stop here and use the ``RedundancyRoutingLogic`` with a ``akka.routing.Router``
|
||||
as described in :ref:`simple-router-scala`.
|
||||
|
||||
The routing logic is where your magic sauce is applied. In our example it inspects the message types
|
||||
and forwards to the correct routee based on this:
|
||||
Let us continue and make this into a self contained, configurable, router actor.
|
||||
|
||||
.. includecode:: ../../../akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala#crRoutingLogic
|
||||
Create a class that extends ``Pool``, ``Group`` or ``CustomRouterConfig``. That class is a factory
|
||||
for the routing logic and holds the configuration for the router. Here we make it a ``Group``.
|
||||
|
||||
As you can see above what's returned in the partial function is a ``List`` of ``Destination(sender, routee)``.
|
||||
The sender is what "parent" the routee should see - changing this could be useful if you for example want
|
||||
another actor than the original sender to intermediate the result of the routee (if there is a result).
|
||||
For more information about how to alter the original sender we refer to the source code of
|
||||
`ScatterGatherFirstCompletedRouter <https://github.com/akka/akka/blob/master/akka-actor/src/main/scala/akka/routing/Routing.scala#L375>`_
|
||||
.. includecode:: code/docs/routing/CustomRouterDocSpec.scala#group
|
||||
|
||||
All in all the custom router looks like this:
|
||||
This can be used exactly as the router actors provided by Akka.
|
||||
|
||||
.. includecode:: ../../../akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala#CustomRouter
|
||||
.. includecode:: code/docs/routing/CustomRouterDocSpec.scala#usage-1
|
||||
|
||||
If you are interested in how to use the VoteCountRouter you can have a look at the test class
|
||||
`RoutingSpec <https://github.com/akka/akka/blob/master/akka-actor-tests/src/test/scala/akka/routing/RoutingSpec.scala>`_
|
||||
Note that we added a constructor in ``RedundancyGroup`` that takes a ``Config`` parameter.
|
||||
That makes it possible to define it in configuration.
|
||||
|
||||
.. caution::
|
||||
.. includecode:: code/docs/routing/CustomRouterDocSpec.scala#config
|
||||
|
||||
When creating a cutom router the resulting RoutedActorRef optimizes the
|
||||
sending of the message so that it does NOT go through the router’s mailbox
|
||||
unless the route returns an empty recipient set.
|
||||
|
||||
This means that the ``route`` function defined in the ``RouterConfig``
|
||||
or the function returned from ``CreateCustomRoute`` in
|
||||
``CustomRouterConfig`` is evaluated concurrently without protection by
|
||||
the RoutedActorRef: either provide a reentrant (i.e. pure) implementation
|
||||
or do the locking yourself!
|
||||
|
||||
|
||||
Configured Custom Router
|
||||
************************
|
||||
|
||||
It is possible to define configuration properties for custom routers. In the ``router`` property of the deployment
|
||||
configuration you define the fully qualified class name of the router class. The router class must extend
|
||||
``akka.routing.RouterConfig`` and have constructor with one ``com.typesafe.config.Config`` parameter.
|
||||
Note the fully qualified class name in the ``router`` property. The router class must extend
|
||||
``akka.routing.RouterConfig`` (``Pool``, ``Group`` or ``CustomRouterConfig``) and have
|
||||
constructor with one ``com.typesafe.config.Config`` parameter.
|
||||
The deployment section of the configuration is passed to the constructor.
|
||||
|
||||
Custom Resizer
|
||||
**************
|
||||
|
||||
A router with dynamically resizable number of routees is implemented by providing a ``akka.routing.Resizer``
|
||||
in ``resizer`` method of the ``RouterConfig``. See ``akka.routing.DefaultResizer`` for inspiration
|
||||
of how to write your own resize strategy.
|
||||
|
||||
.. includecode:: code/docs/routing/CustomRouterDocSpec.scala#usage-2
|
||||
|
||||
Configuring Dispatchers
|
||||
^^^^^^^^^^^^^^^^^^^^^^^
|
||||
|
||||
The dispatcher for created children of the router will be taken from
|
||||
:class:`Props` as described in :ref:`dispatchers-scala`. For a dynamic pool it
|
||||
makes sense to configure the :class:`BalancingDispatcher` if the precise
|
||||
``Props`` as described in :ref:`dispatchers-scala`. For a pool it
|
||||
makes sense to configure the ``BalancingDispatcher`` if the precise
|
||||
routing is not so important (i.e. no consistent hashing or round-robin is
|
||||
required); this enables newly created routees to pick up work immediately by
|
||||
stealing it from their siblings.
|
||||
|
|
@ -586,7 +641,7 @@ stealing it from their siblings.
|
|||
|
||||
The “head” router cannot always run on the same dispatcher, because it
|
||||
does not process the same type of messages, hence this special actor does
|
||||
not use the dispatcher configured in :class:`Props`, but takes the
|
||||
not use the dispatcher configured in ``Props``, but takes the
|
||||
``routerDispatcher`` from the :class:`RouterConfig` instead, which defaults to
|
||||
the actor system’s default dispatcher. All standard routers allow setting this
|
||||
property in their constructor or factory method, custom routers have to
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue