Typed consistent hashing example (#30036)
Co-authored-by: Renato Cavalcanti <renato@cavalcanti.be>
This commit is contained in:
parent
55ec18962c
commit
fb432b3d8a
3 changed files with 154 additions and 2 deletions
|
|
@ -21,9 +21,18 @@ import akka.actor.typed.javadsl.Routers;
|
||||||
import akka.actor.typed.receptionist.Receptionist;
|
import akka.actor.typed.receptionist.Receptionist;
|
||||||
import akka.actor.typed.receptionist.ServiceKey;
|
import akka.actor.typed.receptionist.ServiceKey;
|
||||||
|
|
||||||
|
import akka.actor.testkit.typed.javadsl.TestKitJunitResource;
|
||||||
|
import akka.actor.testkit.typed.javadsl.TestProbe;
|
||||||
|
|
||||||
|
import org.junit.ClassRule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.scalatestplus.junit.JUnitSuite;
|
||||||
|
|
||||||
// #pool
|
// #pool
|
||||||
|
|
||||||
public class RouterTest {
|
public class RouterTest extends JUnitSuite {
|
||||||
|
|
||||||
|
@ClassRule public static final TestKitJunitResource testKit = new TestKitJunitResource();
|
||||||
|
|
||||||
static // #routee
|
static // #routee
|
||||||
class Worker {
|
class Worker {
|
||||||
|
|
@ -54,6 +63,46 @@ public class RouterTest {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static class Proxy {
|
||||||
|
|
||||||
|
public final ServiceKey<Message> registeringKey =
|
||||||
|
ServiceKey.create(Message.class, "aggregator-key");
|
||||||
|
|
||||||
|
public String mapping(Message message) {
|
||||||
|
return message.getId();
|
||||||
|
}
|
||||||
|
|
||||||
|
static class Message {
|
||||||
|
|
||||||
|
public Message(String id, String content) {
|
||||||
|
this.id = id;
|
||||||
|
this.content = content;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String content;
|
||||||
|
private String id;
|
||||||
|
|
||||||
|
public final String getContent() {
|
||||||
|
return content;
|
||||||
|
}
|
||||||
|
|
||||||
|
public final String getId() {
|
||||||
|
return id;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static Behavior<Message> create(ActorRef<String> monitor) {
|
||||||
|
return Behaviors.receive(Message.class)
|
||||||
|
.onMessage(Message.class, in -> onMyMessage(monitor, in))
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static Behavior<Message> onMyMessage(ActorRef<String> monitor, Message message) {
|
||||||
|
monitor.tell(message.getId());
|
||||||
|
return Behaviors.same();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// #routee
|
// #routee
|
||||||
|
|
||||||
// intentionally outside the routee scope
|
// intentionally outside the routee scope
|
||||||
|
|
@ -138,6 +187,52 @@ public class RouterTest {
|
||||||
// #group
|
// #group
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void showGroupRoutingWithConsistentHashing() throws Exception {
|
||||||
|
|
||||||
|
TestProbe<String> probe1 = testKit.createTestProbe();
|
||||||
|
TestProbe<String> probe2 = testKit.createTestProbe();
|
||||||
|
|
||||||
|
Proxy proxy = new Proxy();
|
||||||
|
|
||||||
|
ActorRef<Proxy.Message> proxy1 = testKit.spawn(proxy.create(probe1.ref()));
|
||||||
|
ActorRef<Proxy.Message> proxy2 = testKit.spawn(proxy.create(probe2.ref()));
|
||||||
|
|
||||||
|
TestProbe<Receptionist.Registered> waiterProbe = testKit.createTestProbe();
|
||||||
|
// registering proxies
|
||||||
|
|
||||||
|
testKit
|
||||||
|
.system()
|
||||||
|
.receptionist()
|
||||||
|
.tell(Receptionist.register(proxy.registeringKey, proxy1, waiterProbe.ref()));
|
||||||
|
testKit
|
||||||
|
.system()
|
||||||
|
.receptionist()
|
||||||
|
.tell(Receptionist.register(proxy.registeringKey, proxy2, waiterProbe.ref()));
|
||||||
|
// wait until both registrations get Receptionist.Registered
|
||||||
|
|
||||||
|
waiterProbe.receiveSeveralMessages(2);
|
||||||
|
// messages sent to a router with consistent hashing
|
||||||
|
// #consistent-hashing
|
||||||
|
ActorRef<Proxy.Message> router =
|
||||||
|
testKit.spawn(
|
||||||
|
Routers.group(proxy.registeringKey)
|
||||||
|
.withConsistentHashingRouting(10, command -> proxy.mapping(command)));
|
||||||
|
|
||||||
|
router.tell(new Proxy.Message("123", "Text1"));
|
||||||
|
router.tell(new Proxy.Message("123", "Text2"));
|
||||||
|
|
||||||
|
router.tell(new Proxy.Message("zh3", "Text3"));
|
||||||
|
router.tell(new Proxy.Message("zh3", "Text4"));
|
||||||
|
// the hash is calculated over the Proxy.Message first parameter obtained through the
|
||||||
|
// Proxy.mapping function
|
||||||
|
// #consistent-hashing
|
||||||
|
// Then messages with equal Message.id reach the same actor
|
||||||
|
// so the first message in each probe queue is equal to its second
|
||||||
|
probe1.expectMessage(probe1.receiveMessage());
|
||||||
|
probe2.expectMessage(probe2.receiveMessage());
|
||||||
|
}
|
||||||
|
|
||||||
public static void main(String[] args) {
|
public static void main(String[] args) {
|
||||||
ActorSystem<Void> system =
|
ActorSystem<Void> system =
|
||||||
ActorSystem.create(
|
ActorSystem.create(
|
||||||
|
|
|
||||||
|
|
@ -7,7 +7,7 @@ package docs.akka.typed
|
||||||
import akka.actor.typed.DispatcherSelector
|
import akka.actor.typed.DispatcherSelector
|
||||||
// #pool
|
// #pool
|
||||||
import akka.actor.testkit.typed.scaladsl.{ LogCapturing, ScalaTestWithActorTestKit }
|
import akka.actor.testkit.typed.scaladsl.{ LogCapturing, ScalaTestWithActorTestKit }
|
||||||
import akka.actor.typed.{ Behavior, SupervisorStrategy }
|
import akka.actor.typed.{ ActorRef, Behavior, SupervisorStrategy }
|
||||||
import akka.actor.typed.receptionist.{ Receptionist, ServiceKey }
|
import akka.actor.typed.receptionist.{ Receptionist, ServiceKey }
|
||||||
import akka.actor.typed.scaladsl.{ Behaviors, Routers }
|
import akka.actor.typed.scaladsl.{ Behaviors, Routers }
|
||||||
|
|
||||||
|
|
@ -143,5 +143,55 @@ class RouterSpec extends ScalaTestWithActorTestKit("akka.loglevel=warning") with
|
||||||
|
|
||||||
probe.receiveMessages(10)
|
probe.receiveMessages(10)
|
||||||
}
|
}
|
||||||
|
"show group routing with consistent hashing" in {
|
||||||
|
|
||||||
|
val probe1 = createTestProbe[String]()
|
||||||
|
val probe2 = createTestProbe[String]()
|
||||||
|
|
||||||
|
object Proxy {
|
||||||
|
|
||||||
|
val RegisteringKey = ServiceKey[Message]("aggregator-key")
|
||||||
|
|
||||||
|
def mapping(message: Message) = message.id
|
||||||
|
|
||||||
|
case class Message(id: String, content: String)
|
||||||
|
|
||||||
|
def apply(monitor: ActorRef[String]): Behavior[Message] =
|
||||||
|
Behaviors.receiveMessage {
|
||||||
|
case Message(id, _) =>
|
||||||
|
monitor ! id
|
||||||
|
Behaviors.same
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
//registering proxies
|
||||||
|
val proxy1 = spawn(Proxy(probe1.ref))
|
||||||
|
val proxy2 = spawn(Proxy(probe2.ref))
|
||||||
|
val waiterProbe = createTestProbe[Receptionist.Registered]()
|
||||||
|
|
||||||
|
system.receptionist ! Receptionist.Register(Proxy.RegisteringKey, proxy1, waiterProbe.ref)
|
||||||
|
system.receptionist ! Receptionist.Register(Proxy.RegisteringKey, proxy2, waiterProbe.ref)
|
||||||
|
//wait until both registrations get Receptionist.Registered
|
||||||
|
waiterProbe.receiveMessages(2)
|
||||||
|
|
||||||
|
//messages sent to a router with consistent hashing
|
||||||
|
// #consistent-hashing
|
||||||
|
val router = spawn(Routers.group(Proxy.RegisteringKey).withConsistentHashingRouting(10, Proxy.mapping))
|
||||||
|
|
||||||
|
router ! Proxy.Message("123", "Text1")
|
||||||
|
router ! Proxy.Message("123", "Text2")
|
||||||
|
|
||||||
|
router ! Proxy.Message("zh3", "Text3")
|
||||||
|
router ! Proxy.Message("zh3", "Text4")
|
||||||
|
// the hash is calculated over the Proxy.Message first parameter obtained through the Proxy.mapping function
|
||||||
|
// #consistent-hashing
|
||||||
|
//Then messages with equal Message.id reach the same actor
|
||||||
|
//so the first message in each probe queue is equal to its second
|
||||||
|
probe1.receiveMessage() shouldBe probe1.receiveMessage()
|
||||||
|
probe2.receiveMessage() shouldBe probe2.receiveMessage()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -139,6 +139,13 @@ hash key. This makes the decision transparent for the sender.
|
||||||
Consistent hashing delivers messages with the same hash to the same routee as long as the set of routees stays the same.
|
Consistent hashing delivers messages with the same hash to the same routee as long as the set of routees stays the same.
|
||||||
When the set of routees changes, consistent hashing tries to make sure, but does not guarantee, that messages with the same hash are routed to the same routee.
|
When the set of routees changes, consistent hashing tries to make sure, but does not guarantee, that messages with the same hash are routed to the same routee.
|
||||||
|
|
||||||
|
|
||||||
|
Scala
|
||||||
|
: @@snip [RouterSpec.scala](/akka-actor-typed-tests/src/test/scala/docs/akka/typed/RouterSpec.scala) { #consistent-hashing }
|
||||||
|
|
||||||
|
Java
|
||||||
|
: @@snip [RouterTest.java](/akka-actor-typed-tests/src/test/java/jdocs/akka/typed/RouterTest.java) { #consistent-hashing }
|
||||||
|
|
||||||
See also @ref[Akka Cluster Sharding](cluster-sharding.md) which provides stable routing and rebalancing of the routee actors.
|
See also @ref[Akka Cluster Sharding](cluster-sharding.md) which provides stable routing and rebalancing of the routee actors.
|
||||||
|
|
||||||
## Routers and performance
|
## Routers and performance
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue