Merge pull request #698 from akka/wip-944-consistent-hashing-patriknw

Consistent hashing router, see #944
This commit is contained in:
Patrik Nordwall 2012-09-19 07:57:27 -07:00
commit 888f81df8d
19 changed files with 1236 additions and 309 deletions

View file

@ -0,0 +1,5 @@
package docs.jrouting;
import org.scalatest.junit.JUnitSuite
class ConsistentHashingRouterDocTest extends ConsistentHashingRouterDocTestBase with JUnitSuite

View file

@ -0,0 +1,136 @@
/**
* Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
*/
package docs.jrouting;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;
import akka.testkit.JavaTestKit;
import akka.actor.ActorSystem;
//#imports1
import akka.actor.UntypedActor;
import akka.routing.ConsistentHashingRouter.ConsistentHashable;
import java.util.Map;
import java.util.HashMap;
import java.io.Serializable;
//#imports1
//#imports2
import akka.actor.Props;
import akka.actor.ActorRef;
import akka.routing.ConsistentHashingRouter;
import akka.routing.ConsistentHashingRouter.ConsistentHashMapper;
import akka.routing.ConsistentHashingRouter.ConsistentHashableEnvelope;
//#imports2
public class ConsistentHashingRouterDocTestBase {
static ActorSystem system;
@BeforeClass
public static void setup() {
system = ActorSystem.create();
}
@AfterClass
public static void teardown() {
system.shutdown();
}
//#cache-actor
public static class Cache extends UntypedActor {
Map<String, String> cache = new HashMap<String, String>();
public void onReceive(Object msg) {
if (msg instanceof Entry) {
Entry entry = (Entry) msg;
cache.put(entry.key, entry.value);
} else if (msg instanceof Get) {
Get get = (Get) msg;
Object value = cache.get(get.key);
getSender().tell(value == null ? NOT_FOUND : value,
getContext().self());
} else if (msg instanceof Evict) {
Evict evict = (Evict) msg;
cache.remove(evict.key);
} else {
unhandled(msg);
}
}
}
public static final class Evict implements Serializable {
public final String key;
public Evict(String key) {
this.key = key;
}
}
public static final class Get implements Serializable, ConsistentHashable {
public final String key;
public Get(String key) {
this.key = key;
}
public Object consistentHashKey() {
return key;
}
}
public static final class Entry implements Serializable {
public final String key;
public final String value;
public Entry(String key, String value) {
this.key = key;
this.value = value;
}
}
public static final String NOT_FOUND = "NOT_FOUND";
//#cache-actor
@Test
public void demonstrateUsageOfConsistentHashableRouter() {
new JavaTestKit(system) {{
//#consistent-hashing-router
final ConsistentHashMapper hashMapper = new ConsistentHashMapper() {
@Override
public Object hashKey(Object message) {
if (message instanceof Evict) {
return ((Evict) message).key;
} else {
return null;
}
}
};
ActorRef cache = system.actorOf(new Props(Cache.class).withRouter(
new ConsistentHashingRouter(10).withHashMapper(hashMapper)),
"cache");
cache.tell(new ConsistentHashableEnvelope(
new Entry("hello", "HELLO"), "hello"), getRef());
cache.tell(new ConsistentHashableEnvelope(
new Entry("hi", "HI"), "hi"), getRef());
cache.tell(new Get("hello"), getRef());
expectMsgEquals("HELLO");
cache.tell(new Get("hi"), getRef());
expectMsgEquals("HI");
cache.tell(new Evict("hi"), getRef());
cache.tell(new Get("hi"), getRef());
expectMsgEquals(NOT_FOUND);
//#consistent-hashing-router
}};
}
}

View file

@ -15,13 +15,14 @@ is really easy to create your own. The routers shipped with Akka are:
* ``akka.routing.SmallestMailboxRouter``
* ``akka.routing.BroadcastRouter``
* ``akka.routing.ScatterGatherFirstCompletedRouter``
* ``akka.routing.ConsistentHashingRouter``
Routers In Action
^^^^^^^^^^^^^^^^^
This is an example of how to create a router that is defined in configuration:
.. includecode:: ../scala/code/docs/routing/RouterViaConfigExample.scala#config
.. includecode:: ../scala/code/docs/routing/RouterViaConfigDocSpec.scala#config-round-robin
.. includecode:: code/docs/jrouting/RouterViaConfigExample.java#configurableRouting
@ -177,6 +178,10 @@ 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.)
This is an example of how to define a round-robin router in configuration:
.. includecode:: ../scala/code/docs/routing/RouterViaConfigDocSpec.scala#config-round-robin
RandomRouter
************
As the name implies this router type selects one of its routees randomly and forwards
@ -204,6 +209,10 @@ When run you should see a similar output to this:
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.
This is an example of how to define a random router in configuration:
.. includecode:: ../scala/code/docs/routing/RouterViaConfigDocSpec.scala#config-random
SmallestMailboxRouter
*********************
A Router that tries to send to the non-suspended routee with fewest messages in mailbox.
@ -219,6 +228,10 @@ Code example:
.. includecode:: code/docs/jrouting/ParentActor.java#smallestMailboxRouter
This is an example of how to define a smallest-mailbox router in configuration:
.. includecode:: ../scala/code/docs/routing/RouterViaConfigDocSpec.scala#config-smallest-mailbox
BroadcastRouter
***************
A broadcast router forwards the message it receives to *all* its routees.
@ -238,6 +251,10 @@ When run you should see a similar output to this:
As you can see here above each of the routees, five in total, received the broadcast message.
This is an example of how to define a broadcast router in configuration:
.. includecode:: ../scala/code/docs/routing/RouterViaConfigDocSpec.scala#config-broadcast
ScatterGatherFirstCompletedRouter
*********************************
The ScatterGatherFirstCompletedRouter will send the message on to all its routees as a future.
@ -255,6 +272,51 @@ When run you should see this:
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.
This is an example of how to define a scatter-gather router in configuration:
.. includecode:: ../scala/code/docs/routing/RouterViaConfigDocSpec.scala#config-scatter-gather
ConsistentHashingRouter
***********************
The ConsistentHashingRouter uses `consistent hashing <http://en.wikipedia.org/wiki/Consistent_hashing>`_
to select a connection 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.
There is 3 ways to define what data to use for the consistent hash key.
* You can define ``withHashMapper`` of the router to map incoming
messages to their consistent hash key. This makes the the decision
transparent for the sender.
* The messages may implement ``akka.routing.ConsistentHashingRouter.ConsistentHashable``.
The key is part of the message and it's convenient to define it together
with the message definition.
* The messages can be be wrapped in a ``akka.routing.ConsistentHashingRouter.ConsistentHashableEnvelope``
to define what data to use for the consistent hash key. The sender knows
the key to use.
These ways to define the consistent hash key can be use together and at
the same time for one router. The ``withHashMapper`` is tried first.
Code example:
.. includecode:: code/docs/jrouting/ConsistentHashingRouterDocTestBase.java
:include: imports1,cache-actor
.. includecode:: code/docs/jrouting/ConsistentHashingRouterDocTestBase.java
:include: imports2,consistent-hashing-router
In the above example you see that the ``Get`` message implements ``ConsistentHashable`` itself,
while the ``Entry`` message is wrapped in a ``ConsistentHashableEnvelope``. The ``Evict``
message is handled by the ``withHashMapper``.
This is an example of how to define a consistent-hashing router in configuration:
.. includecode:: ../scala/code/docs/routing/RouterViaConfigDocSpec.scala#config-consistent-hashing
Broadcast Messages
^^^^^^^^^^^^^^^^^^
@ -276,7 +338,7 @@ of routees dynamically.
This is an example of how to create a resizable router that is defined in configuration:
.. includecode:: ../scala/code/docs/routing/RouterViaConfigExample.scala#config-resize
.. includecode:: ../scala/code/docs/routing/RouterViaConfigDocSpec.scala#config-resize
.. includecode:: code/docs/jrouting/RouterViaConfigExample.java#configurableRoutingWithResizer