ActorContext.getParent for Java API #22413
This commit is contained in:
parent
5ff44194a3
commit
2eb226ed32
213 changed files with 1319 additions and 1523 deletions
42
akka-docs/rst/java/code/jdocs/cluster/ClusterDocTest.java
Normal file
42
akka-docs/rst/java/code/jdocs/cluster/ClusterDocTest.java
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* Copyright (C) 2015-2017 Lightbend Inc. <http://www.lightbend.com>
|
||||
*/
|
||||
package jdocs.cluster;
|
||||
|
||||
import com.typesafe.config.ConfigFactory;
|
||||
import jdocs.AbstractJavaTest;
|
||||
import org.junit.AfterClass;
|
||||
import org.junit.BeforeClass;
|
||||
import org.junit.Test;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.cluster.Cluster;
|
||||
import akka.testkit.JavaTestKit;
|
||||
|
||||
|
||||
public class ClusterDocTest extends AbstractJavaTest {
|
||||
|
||||
static ActorSystem system;
|
||||
|
||||
@BeforeClass
|
||||
public static void setup() {
|
||||
system = ActorSystem.create("ClusterDocTest",
|
||||
ConfigFactory.parseString(scala.docs.cluster.ClusterDocSpec.config()));
|
||||
}
|
||||
|
||||
@AfterClass
|
||||
public static void tearDown() {
|
||||
JavaTestKit.shutdownActorSystem(system);
|
||||
system = null;
|
||||
}
|
||||
|
||||
@Test
|
||||
public void demonstrateLeave() {
|
||||
//#leave
|
||||
final Cluster cluster = Cluster.get(system);
|
||||
cluster.leave(cluster.selfAddress());
|
||||
//#leave
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
36
akka-docs/rst/java/code/jdocs/cluster/FactorialBackend.java
Normal file
36
akka-docs/rst/java/code/jdocs/cluster/FactorialBackend.java
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
package jdocs.cluster;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
import akka.actor.AbstractActor;
|
||||
import static akka.pattern.PatternsCS.pipe;
|
||||
|
||||
//#backend
|
||||
public class FactorialBackend extends AbstractActor {
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(Integer.class, n -> {
|
||||
|
||||
CompletableFuture<FactorialResult> result =
|
||||
CompletableFuture.supplyAsync(() -> factorial(n))
|
||||
.thenApply((factorial) -> new FactorialResult(n, factorial));
|
||||
|
||||
pipe(result, getContext().dispatcher()).to(sender());
|
||||
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
BigInteger factorial(int n) {
|
||||
BigInteger acc = BigInteger.ONE;
|
||||
for (int i = 1; i <= n; ++i) {
|
||||
acc = acc.multiply(BigInteger.valueOf(i));
|
||||
}
|
||||
return acc;
|
||||
}
|
||||
}
|
||||
//#backend
|
||||
|
||||
103
akka-docs/rst/java/code/jdocs/cluster/FactorialFrontend.java
Normal file
103
akka-docs/rst/java/code/jdocs/cluster/FactorialFrontend.java
Normal file
|
|
@ -0,0 +1,103 @@
|
|||
package jdocs.cluster;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import akka.actor.Props;
|
||||
import akka.cluster.metrics.AdaptiveLoadBalancingGroup;
|
||||
import akka.cluster.metrics.AdaptiveLoadBalancingPool;
|
||||
import akka.cluster.metrics.HeapMetricsSelector;
|
||||
import akka.cluster.metrics.SystemLoadAverageMetricsSelector;
|
||||
import akka.cluster.routing.ClusterRouterGroup;
|
||||
import akka.cluster.routing.ClusterRouterGroupSettings;
|
||||
import akka.cluster.routing.ClusterRouterPool;
|
||||
import akka.cluster.routing.ClusterRouterPoolSettings;
|
||||
import scala.concurrent.duration.Duration;
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.ReceiveTimeout;
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.event.Logging;
|
||||
import akka.event.LoggingAdapter;
|
||||
import akka.routing.FromConfig;
|
||||
|
||||
//#frontend
|
||||
public class FactorialFrontend extends AbstractActor {
|
||||
final int upToN;
|
||||
final boolean repeat;
|
||||
|
||||
LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);
|
||||
|
||||
ActorRef backend = getContext().actorOf(FromConfig.getInstance().props(),
|
||||
"factorialBackendRouter");
|
||||
|
||||
public FactorialFrontend(int upToN, boolean repeat) {
|
||||
this.upToN = upToN;
|
||||
this.repeat = repeat;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preStart() {
|
||||
sendJobs();
|
||||
getContext().setReceiveTimeout(Duration.create(10, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(FactorialResult.class, result -> {
|
||||
if (result.n == upToN) {
|
||||
log.debug("{}! = {}", result.n, result.factorial);
|
||||
if (repeat)
|
||||
sendJobs();
|
||||
else
|
||||
getContext().stop(self());
|
||||
}
|
||||
})
|
||||
.match(ReceiveTimeout.class, x -> {
|
||||
log.info("Timeout");
|
||||
sendJobs();
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
void sendJobs() {
|
||||
log.info("Starting batch of factorials up to [{}]", upToN);
|
||||
for (int n = 1; n <= upToN; n++) {
|
||||
backend.tell(n, getSelf());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
//#frontend
|
||||
|
||||
//not used, only for documentation
|
||||
abstract class FactorialFrontend2 extends AbstractActor {
|
||||
//#router-lookup-in-code
|
||||
int totalInstances = 100;
|
||||
Iterable<String> routeesPaths = Arrays.asList("/user/factorialBackend", "");
|
||||
boolean allowLocalRoutees = true;
|
||||
String useRole = "backend";
|
||||
ActorRef backend = getContext().actorOf(
|
||||
new ClusterRouterGroup(new AdaptiveLoadBalancingGroup(
|
||||
HeapMetricsSelector.getInstance(), Collections.<String> emptyList()),
|
||||
new ClusterRouterGroupSettings(totalInstances, routeesPaths,
|
||||
allowLocalRoutees, useRole)).props(), "factorialBackendRouter2");
|
||||
//#router-lookup-in-code
|
||||
}
|
||||
|
||||
//not used, only for documentation
|
||||
abstract class FactorialFrontend3 extends AbstractActor {
|
||||
//#router-deploy-in-code
|
||||
int totalInstances = 100;
|
||||
int maxInstancesPerNode = 3;
|
||||
boolean allowLocalRoutees = false;
|
||||
String useRole = "backend";
|
||||
ActorRef backend = getContext().actorOf(
|
||||
new ClusterRouterPool(new AdaptiveLoadBalancingPool(
|
||||
SystemLoadAverageMetricsSelector.getInstance(), 0),
|
||||
new ClusterRouterPoolSettings(totalInstances, maxInstancesPerNode,
|
||||
allowLocalRoutees, useRole)).props(Props
|
||||
.create(FactorialBackend.class)), "factorialBackendRouter3");
|
||||
//#router-deploy-in-code
|
||||
}
|
||||
|
|
@ -0,0 +1,71 @@
|
|||
package jdocs.cluster;
|
||||
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import scala.concurrent.Await;
|
||||
import scala.concurrent.duration.Duration;
|
||||
import com.typesafe.config.Config;
|
||||
import com.typesafe.config.ConfigFactory;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.actor.Props;
|
||||
import akka.cluster.Cluster;
|
||||
|
||||
public class FactorialFrontendMain {
|
||||
|
||||
public static void main(String[] args) {
|
||||
final int upToN = 200;
|
||||
|
||||
final Config config = ConfigFactory.parseString(
|
||||
"akka.cluster.roles = [frontend]").withFallback(
|
||||
ConfigFactory.load("factorial"));
|
||||
|
||||
final ActorSystem system = ActorSystem.create("ClusterSystem", config);
|
||||
system.log().info(
|
||||
"Factorials will start when 2 backend members in the cluster.");
|
||||
//#registerOnUp
|
||||
Cluster.get(system).registerOnMemberUp(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
system.actorOf(Props.create(FactorialFrontend.class, upToN, true),
|
||||
"factorialFrontend");
|
||||
}
|
||||
});
|
||||
//#registerOnUp
|
||||
|
||||
//#registerOnRemoved
|
||||
Cluster.get(system).registerOnMemberRemoved(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
// exit JVM when ActorSystem has been terminated
|
||||
final Runnable exit = new Runnable() {
|
||||
@Override public void run() {
|
||||
System.exit(0);
|
||||
}
|
||||
};
|
||||
system.registerOnTermination(exit);
|
||||
|
||||
// shut down ActorSystem
|
||||
system.terminate();
|
||||
|
||||
// In case ActorSystem shutdown takes longer than 10 seconds,
|
||||
// exit the JVM forcefully anyway.
|
||||
// We must spawn a separate thread to not block current thread,
|
||||
// since that would have blocked the shutdown of the ActorSystem.
|
||||
new Thread() {
|
||||
@Override public void run(){
|
||||
try {
|
||||
Await.ready(system.whenTerminated(), Duration.create(10, TimeUnit.SECONDS));
|
||||
} catch (Exception e) {
|
||||
System.exit(-1);
|
||||
}
|
||||
|
||||
}
|
||||
}.start();
|
||||
}
|
||||
});
|
||||
//#registerOnRemoved
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
15
akka-docs/rst/java/code/jdocs/cluster/FactorialResult.java
Normal file
15
akka-docs/rst/java/code/jdocs/cluster/FactorialResult.java
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
package jdocs.cluster;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.io.Serializable;
|
||||
|
||||
public class FactorialResult implements Serializable {
|
||||
private static final long serialVersionUID = 1L;
|
||||
public final int n;
|
||||
public final BigInteger factorial;
|
||||
|
||||
FactorialResult(int n, BigInteger factorial) {
|
||||
this.n = n;
|
||||
this.factorial = factorial;
|
||||
}
|
||||
}
|
||||
69
akka-docs/rst/java/code/jdocs/cluster/MetricsListener.java
Normal file
69
akka-docs/rst/java/code/jdocs/cluster/MetricsListener.java
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
package jdocs.cluster;
|
||||
|
||||
//#metrics-listener
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.cluster.Cluster;
|
||||
import akka.cluster.ClusterEvent.CurrentClusterState;
|
||||
import akka.cluster.metrics.ClusterMetricsChanged;
|
||||
import akka.cluster.metrics.NodeMetrics;
|
||||
import akka.cluster.metrics.StandardMetrics;
|
||||
import akka.cluster.metrics.StandardMetrics.HeapMemory;
|
||||
import akka.cluster.metrics.StandardMetrics.Cpu;
|
||||
import akka.cluster.metrics.ClusterMetricsExtension;
|
||||
import akka.event.Logging;
|
||||
import akka.event.LoggingAdapter;
|
||||
|
||||
public class MetricsListener extends AbstractActor {
|
||||
LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);
|
||||
|
||||
Cluster cluster = Cluster.get(getContext().getSystem());
|
||||
|
||||
ClusterMetricsExtension extension = ClusterMetricsExtension.get(getContext().getSystem());
|
||||
|
||||
|
||||
// Subscribe unto ClusterMetricsEvent events.
|
||||
@Override
|
||||
public void preStart() {
|
||||
extension.subscribe(getSelf());
|
||||
}
|
||||
|
||||
// Unsubscribe from ClusterMetricsEvent events.
|
||||
@Override
|
||||
public void postStop() {
|
||||
extension.unsubscribe(getSelf());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(ClusterMetricsChanged.class, clusterMetrics -> {
|
||||
for (NodeMetrics nodeMetrics : clusterMetrics.getNodeMetrics()) {
|
||||
if (nodeMetrics.address().equals(cluster.selfAddress())) {
|
||||
logHeap(nodeMetrics);
|
||||
logCpu(nodeMetrics);
|
||||
}
|
||||
}
|
||||
})
|
||||
.match(CurrentClusterState.class, message -> {
|
||||
// Ignore.
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
void logHeap(NodeMetrics nodeMetrics) {
|
||||
HeapMemory heap = StandardMetrics.extractHeapMemory(nodeMetrics);
|
||||
if (heap != null) {
|
||||
log.info("Used heap: {} MB", ((double) heap.used()) / 1024 / 1024);
|
||||
}
|
||||
}
|
||||
|
||||
void logCpu(NodeMetrics nodeMetrics) {
|
||||
Cpu cpu = StandardMetrics.extractCpu(nodeMetrics);
|
||||
if (cpu != null && cpu.systemLoadAverage().isDefined()) {
|
||||
log.info("Load: {} ({} processors)", cpu.systemLoadAverage().get(),
|
||||
cpu.processors());
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
//#metrics-listener
|
||||
|
|
@ -0,0 +1,49 @@
|
|||
package jdocs.cluster;
|
||||
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.cluster.Cluster;
|
||||
import akka.cluster.ClusterEvent;
|
||||
import akka.cluster.ClusterEvent.MemberEvent;
|
||||
import akka.cluster.ClusterEvent.MemberUp;
|
||||
import akka.cluster.ClusterEvent.MemberRemoved;
|
||||
import akka.cluster.ClusterEvent.UnreachableMember;
|
||||
import akka.event.Logging;
|
||||
import akka.event.LoggingAdapter;
|
||||
|
||||
public class SimpleClusterListener extends AbstractActor {
|
||||
LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);
|
||||
Cluster cluster = Cluster.get(getContext().getSystem());
|
||||
|
||||
//subscribe to cluster changes
|
||||
@Override
|
||||
public void preStart() {
|
||||
//#subscribe
|
||||
cluster.subscribe(getSelf(), ClusterEvent.initialStateAsEvents(),
|
||||
MemberEvent.class, UnreachableMember.class);
|
||||
//#subscribe
|
||||
}
|
||||
|
||||
//re-subscribe when restart
|
||||
@Override
|
||||
public void postStop() {
|
||||
cluster.unsubscribe(getSelf());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(MemberUp.class, mUp -> {
|
||||
log.info("Member is Up: {}", mUp.member());
|
||||
})
|
||||
.match(UnreachableMember.class, mUnreachable -> {
|
||||
log.info("Member detected as unreachable: {}", mUnreachable.member());
|
||||
})
|
||||
.match(MemberRemoved.class, mRemoved -> {
|
||||
log.info("Member is Removed: {}", mRemoved.member());
|
||||
})
|
||||
.match(MemberEvent.class, message -> {
|
||||
// ignore
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
package jdocs.cluster;
|
||||
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.cluster.Cluster;
|
||||
import akka.cluster.ClusterEvent.CurrentClusterState;
|
||||
import akka.cluster.ClusterEvent.MemberEvent;
|
||||
import akka.cluster.ClusterEvent.MemberUp;
|
||||
import akka.cluster.ClusterEvent.MemberRemoved;
|
||||
import akka.cluster.ClusterEvent.UnreachableMember;
|
||||
import akka.event.Logging;
|
||||
import akka.event.LoggingAdapter;
|
||||
|
||||
public class SimpleClusterListener2 extends AbstractActor {
|
||||
LoggingAdapter log = Logging.getLogger(getContext().getSystem(), this);
|
||||
Cluster cluster = Cluster.get(getContext().getSystem());
|
||||
|
||||
//subscribe to cluster changes
|
||||
@Override
|
||||
public void preStart() {
|
||||
//#subscribe
|
||||
cluster.subscribe(getSelf(), MemberEvent.class, UnreachableMember.class);
|
||||
//#subscribe
|
||||
}
|
||||
|
||||
//re-subscribe when restart
|
||||
@Override
|
||||
public void postStop() {
|
||||
cluster.unsubscribe(getSelf());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(CurrentClusterState.class, state -> {
|
||||
log.info("Current members: {}", state.members());
|
||||
})
|
||||
.match(MemberUp.class, mUp -> {
|
||||
log.info("Member is Up: {}", mUp.member());
|
||||
})
|
||||
.match(UnreachableMember.class, mUnreachable -> {
|
||||
log.info("Member detected as unreachable: {}", mUnreachable.member());
|
||||
})
|
||||
.match(MemberRemoved.class, mRemoved -> {
|
||||
log.info("Member is Removed: {}", mRemoved.member());
|
||||
})
|
||||
.match(MemberEvent.class, event -> {
|
||||
// ignore
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
55
akka-docs/rst/java/code/jdocs/cluster/StatsAggregator.java
Normal file
55
akka-docs/rst/java/code/jdocs/cluster/StatsAggregator.java
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
package jdocs.cluster;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import jdocs.cluster.StatsMessages.JobFailed;
|
||||
import jdocs.cluster.StatsMessages.StatsResult;
|
||||
import scala.concurrent.duration.Duration;
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.ReceiveTimeout;
|
||||
import akka.actor.AbstractActor;
|
||||
|
||||
//#aggregator
|
||||
public class StatsAggregator extends AbstractActor {
|
||||
|
||||
final int expectedResults;
|
||||
final ActorRef replyTo;
|
||||
final List<Integer> results = new ArrayList<Integer>();
|
||||
|
||||
public StatsAggregator(int expectedResults, ActorRef replyTo) {
|
||||
this.expectedResults = expectedResults;
|
||||
this.replyTo = replyTo;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preStart() {
|
||||
getContext().setReceiveTimeout(Duration.create(3, TimeUnit.SECONDS));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(Integer.class, wordCount -> {
|
||||
results.add(wordCount);
|
||||
if (results.size() == expectedResults) {
|
||||
int sum = 0;
|
||||
for (int c : results) {
|
||||
sum += c;
|
||||
}
|
||||
double meanWordLength = ((double) sum) / results.size();
|
||||
replyTo.tell(new StatsResult(meanWordLength), getSelf());
|
||||
getContext().stop(getSelf());
|
||||
}
|
||||
})
|
||||
.match(ReceiveTimeout.class, x -> {
|
||||
replyTo.tell(new JobFailed("Service unavailable, try again later"),
|
||||
getSelf());
|
||||
getContext().stop(getSelf());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
//#aggregator
|
||||
55
akka-docs/rst/java/code/jdocs/cluster/StatsMessages.java
Normal file
55
akka-docs/rst/java/code/jdocs/cluster/StatsMessages.java
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
package jdocs.cluster;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
//#messages
|
||||
public interface StatsMessages {
|
||||
|
||||
public static class StatsJob implements Serializable {
|
||||
private final String text;
|
||||
|
||||
public StatsJob(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
public static class StatsResult implements Serializable {
|
||||
private final double meanWordLength;
|
||||
|
||||
public StatsResult(double meanWordLength) {
|
||||
this.meanWordLength = meanWordLength;
|
||||
}
|
||||
|
||||
public double getMeanWordLength() {
|
||||
return meanWordLength;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "meanWordLength: " + meanWordLength;
|
||||
}
|
||||
}
|
||||
|
||||
public static class JobFailed implements Serializable {
|
||||
private final String reason;
|
||||
|
||||
public JobFailed(String reason) {
|
||||
this.reason = reason;
|
||||
}
|
||||
|
||||
public String getReason() {
|
||||
return reason;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JobFailed(" + reason + ")";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
//#messages
|
||||
100
akka-docs/rst/java/code/jdocs/cluster/StatsSampleClient.java
Normal file
100
akka-docs/rst/java/code/jdocs/cluster/StatsSampleClient.java
Normal file
|
|
@ -0,0 +1,100 @@
|
|||
package jdocs.cluster;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import jdocs.cluster.StatsMessages.JobFailed;
|
||||
import jdocs.cluster.StatsMessages.StatsJob;
|
||||
import jdocs.cluster.StatsMessages.StatsResult;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import scala.concurrent.duration.Duration;
|
||||
import scala.concurrent.duration.FiniteDuration;
|
||||
import akka.actor.ActorSelection;
|
||||
import akka.actor.Address;
|
||||
import akka.actor.Cancellable;
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.cluster.Cluster;
|
||||
import akka.cluster.ClusterEvent.UnreachableMember;
|
||||
import akka.cluster.ClusterEvent.ReachableMember;
|
||||
import akka.cluster.ClusterEvent.CurrentClusterState;
|
||||
import akka.cluster.ClusterEvent.MemberEvent;
|
||||
import akka.cluster.ClusterEvent.MemberUp;
|
||||
import akka.cluster.ClusterEvent.ReachabilityEvent;
|
||||
import akka.cluster.Member;
|
||||
import akka.cluster.MemberStatus;
|
||||
|
||||
public class StatsSampleClient extends AbstractActor {
|
||||
|
||||
final String servicePath;
|
||||
final Cancellable tickTask;
|
||||
final Set<Address> nodes = new HashSet<Address>();
|
||||
|
||||
Cluster cluster = Cluster.get(getContext().getSystem());
|
||||
|
||||
public StatsSampleClient(String servicePath) {
|
||||
this.servicePath = servicePath;
|
||||
FiniteDuration interval = Duration.create(2, TimeUnit.SECONDS);
|
||||
tickTask = getContext()
|
||||
.getSystem()
|
||||
.scheduler()
|
||||
.schedule(interval, interval, getSelf(), "tick",
|
||||
getContext().dispatcher(), null);
|
||||
}
|
||||
|
||||
//subscribe to cluster changes, MemberEvent
|
||||
@Override
|
||||
public void preStart() {
|
||||
cluster.subscribe(getSelf(), MemberEvent.class, ReachabilityEvent.class);
|
||||
}
|
||||
|
||||
//re-subscribe when restart
|
||||
@Override
|
||||
public void postStop() {
|
||||
cluster.unsubscribe(self());
|
||||
tickTask.cancel();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.matchEquals("tick", x -> !nodes.isEmpty(), x -> {
|
||||
// just pick any one
|
||||
List<Address> nodesList = new ArrayList<Address>(nodes);
|
||||
Address address = nodesList.get(ThreadLocalRandom.current().nextInt(
|
||||
nodesList.size()));
|
||||
ActorSelection service = getContext().actorSelection(address + servicePath);
|
||||
service.tell(new StatsJob("this is the text that will be analyzed"),
|
||||
getSelf());
|
||||
})
|
||||
.match(StatsResult.class, System.out::println)
|
||||
.match(JobFailed.class, System.out::println)
|
||||
.match(CurrentClusterState.class, state -> {
|
||||
nodes.clear();
|
||||
for (Member member : state.getMembers()) {
|
||||
if (member.hasRole("compute") && member.status().equals(MemberStatus.up())) {
|
||||
nodes.add(member.address());
|
||||
}
|
||||
}
|
||||
})
|
||||
.match(MemberUp.class, mUp -> {
|
||||
if (mUp.member().hasRole("compute"))
|
||||
nodes.add(mUp.member().address());
|
||||
})
|
||||
.match(MemberEvent.class, event -> {
|
||||
nodes.remove(event.member().address());
|
||||
})
|
||||
.match(UnreachableMember.class, unreachable -> {
|
||||
nodes.remove(unreachable.member().address());
|
||||
})
|
||||
.match(ReachableMember.class, reachable -> {
|
||||
if (reachable.member().hasRole("compute"))
|
||||
nodes.add(reachable.member().address());
|
||||
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
package jdocs.cluster;
|
||||
|
||||
import com.typesafe.config.ConfigFactory;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.actor.Props;
|
||||
|
||||
public class StatsSampleOneMasterClientMain {
|
||||
|
||||
public static void main(String[] args) {
|
||||
// note that client is not a compute node, role not defined
|
||||
ActorSystem system = ActorSystem.create("ClusterSystem",
|
||||
ConfigFactory.load("stats2"));
|
||||
system.actorOf(Props.create(StatsSampleClient.class, "/user/statsServiceProxy"),
|
||||
"client");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,53 @@
|
|||
package jdocs.cluster;
|
||||
|
||||
import com.typesafe.config.Config;
|
||||
import com.typesafe.config.ConfigFactory;
|
||||
|
||||
import akka.actor.ActorSystem;
|
||||
import akka.actor.PoisonPill;
|
||||
import akka.actor.Props;
|
||||
import akka.cluster.singleton.ClusterSingletonManager;
|
||||
import akka.cluster.singleton.ClusterSingletonManagerSettings;
|
||||
import akka.cluster.singleton.ClusterSingletonProxy;
|
||||
import akka.cluster.singleton.ClusterSingletonProxySettings;
|
||||
|
||||
public class StatsSampleOneMasterMain {
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (args.length == 0) {
|
||||
startup(new String[] { "2551", "2552", "0" });
|
||||
StatsSampleOneMasterClientMain.main(new String[0]);
|
||||
} else {
|
||||
startup(args);
|
||||
}
|
||||
}
|
||||
|
||||
public static void startup(String[] ports) {
|
||||
for (String port : ports) {
|
||||
// Override the configuration of the port
|
||||
Config config = ConfigFactory
|
||||
.parseString("akka.remote.netty.tcp.port=" + port)
|
||||
.withFallback(
|
||||
ConfigFactory.parseString("akka.cluster.roles = [compute]"))
|
||||
.withFallback(ConfigFactory.load("stats2"));
|
||||
|
||||
ActorSystem system = ActorSystem.create("ClusterSystem", config);
|
||||
|
||||
//#create-singleton-manager
|
||||
ClusterSingletonManagerSettings settings = ClusterSingletonManagerSettings.create(system)
|
||||
.withRole("compute");
|
||||
system.actorOf(ClusterSingletonManager.props(
|
||||
Props.create(StatsService.class), PoisonPill.getInstance(), settings),
|
||||
"statsService");
|
||||
//#create-singleton-manager
|
||||
|
||||
//#singleton-proxy
|
||||
ClusterSingletonProxySettings proxySettings =
|
||||
ClusterSingletonProxySettings.create(system).withRole("compute");
|
||||
system.actorOf(ClusterSingletonProxy.props("/user/statsService",
|
||||
proxySettings), "statsServiceProxy");
|
||||
//#singleton-proxy
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
80
akka-docs/rst/java/code/jdocs/cluster/StatsService.java
Normal file
80
akka-docs/rst/java/code/jdocs/cluster/StatsService.java
Normal file
|
|
@ -0,0 +1,80 @@
|
|||
package jdocs.cluster;
|
||||
|
||||
import akka.cluster.routing.ClusterRouterGroup;
|
||||
import akka.cluster.routing.ClusterRouterGroupSettings;
|
||||
import akka.cluster.routing.ClusterRouterPool;
|
||||
import akka.cluster.routing.ClusterRouterPoolSettings;
|
||||
import akka.routing.ConsistentHashingGroup;
|
||||
import akka.routing.ConsistentHashingPool;
|
||||
import jdocs.cluster.StatsMessages.StatsJob;
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.Props;
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.routing.ConsistentHashingRouter.ConsistentHashableEnvelope;
|
||||
import akka.routing.FromConfig;
|
||||
|
||||
import java.util.Collections;
|
||||
|
||||
//#service
|
||||
public class StatsService extends AbstractActor {
|
||||
|
||||
// This router is used both with lookup and deploy of routees. If you
|
||||
// have a router with only lookup of routees you can use Props.empty()
|
||||
// instead of Props.create(StatsWorker.class).
|
||||
ActorRef workerRouter = getContext().actorOf(
|
||||
FromConfig.getInstance().props(Props.create(StatsWorker.class)),
|
||||
"workerRouter");
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(StatsJob.class, job -> !job.getText().isEmpty(), job -> {
|
||||
String[] words = job.getText().split(" ");
|
||||
ActorRef replyTo = getSender();
|
||||
|
||||
// create actor that collects replies from workers
|
||||
ActorRef aggregator = getContext().actorOf(
|
||||
Props.create(StatsAggregator.class, words.length, replyTo));
|
||||
|
||||
// send each word to a worker
|
||||
for (String word : words) {
|
||||
workerRouter.tell(new ConsistentHashableEnvelope(word, word),
|
||||
aggregator);
|
||||
}
|
||||
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
//#service
|
||||
|
||||
//not used, only for documentation
|
||||
abstract class StatsService2 extends AbstractActor {
|
||||
//#router-lookup-in-code
|
||||
int totalInstances = 100;
|
||||
Iterable<String> routeesPaths = Collections
|
||||
.singletonList("/user/statsWorker");
|
||||
boolean allowLocalRoutees = true;
|
||||
String useRole = "compute";
|
||||
ActorRef workerRouter = getContext().actorOf(
|
||||
new ClusterRouterGroup(new ConsistentHashingGroup(routeesPaths),
|
||||
new ClusterRouterGroupSettings(totalInstances, routeesPaths,
|
||||
allowLocalRoutees, useRole)).props(), "workerRouter2");
|
||||
//#router-lookup-in-code
|
||||
}
|
||||
|
||||
//not used, only for documentation
|
||||
abstract class StatsService3 extends AbstractActor {
|
||||
//#router-deploy-in-code
|
||||
int totalInstances = 100;
|
||||
int maxInstancesPerNode = 3;
|
||||
boolean allowLocalRoutees = false;
|
||||
String useRole = "compute";
|
||||
ActorRef workerRouter = getContext().actorOf(
|
||||
new ClusterRouterPool(new ConsistentHashingPool(0),
|
||||
new ClusterRouterPoolSettings(totalInstances, maxInstancesPerNode,
|
||||
allowLocalRoutees, useRole)).props(Props
|
||||
.create(StatsWorker.class)), "workerRouter3");
|
||||
//#router-deploy-in-code
|
||||
}
|
||||
|
||||
27
akka-docs/rst/java/code/jdocs/cluster/StatsWorker.java
Normal file
27
akka-docs/rst/java/code/jdocs/cluster/StatsWorker.java
Normal file
|
|
@ -0,0 +1,27 @@
|
|||
package jdocs.cluster;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import akka.actor.AbstractActor;
|
||||
|
||||
//#worker
|
||||
public class StatsWorker extends AbstractActor {
|
||||
|
||||
Map<String, Integer> cache = new HashMap<String, Integer>();
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(String.class, word -> {
|
||||
Integer length = cache.get(word);
|
||||
if (length == null) {
|
||||
length = word.length();
|
||||
cache.put(word, length);
|
||||
}
|
||||
getSender().tell(length, getSelf());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
}
|
||||
//#worker
|
||||
|
|
@ -0,0 +1,56 @@
|
|||
package jdocs.cluster;
|
||||
|
||||
import static jdocs.cluster.TransformationMessages.BACKEND_REGISTRATION;
|
||||
import jdocs.cluster.TransformationMessages.TransformationJob;
|
||||
import jdocs.cluster.TransformationMessages.TransformationResult;
|
||||
import akka.actor.AbstractActor;
|
||||
import akka.cluster.Cluster;
|
||||
import akka.cluster.ClusterEvent.CurrentClusterState;
|
||||
import akka.cluster.ClusterEvent.MemberUp;
|
||||
import akka.cluster.Member;
|
||||
import akka.cluster.MemberStatus;
|
||||
|
||||
//#backend
|
||||
public class TransformationBackend extends AbstractActor {
|
||||
|
||||
Cluster cluster = Cluster.get(getContext().getSystem());
|
||||
|
||||
//subscribe to cluster changes, MemberUp
|
||||
@Override
|
||||
public void preStart() {
|
||||
cluster.subscribe(self(), MemberUp.class);
|
||||
}
|
||||
|
||||
//re-subscribe when restart
|
||||
@Override
|
||||
public void postStop() {
|
||||
cluster.unsubscribe(self());
|
||||
}
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(TransformationJob.class, job -> {
|
||||
getSender().tell(new TransformationResult(job.getText().toUpperCase()),
|
||||
getSelf());
|
||||
})
|
||||
.match(CurrentClusterState.class, state -> {
|
||||
for (Member member : state.getMembers()) {
|
||||
if (member.status().equals(MemberStatus.up())) {
|
||||
register(member);
|
||||
}
|
||||
}
|
||||
})
|
||||
.match(MemberUp.class, mUp -> {
|
||||
register(mUp.member());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
void register(Member member) {
|
||||
if (member.hasRole("frontend"))
|
||||
getContext().actorSelection(member.address() + "/user/frontend").tell(
|
||||
BACKEND_REGISTRATION, getSelf());
|
||||
}
|
||||
}
|
||||
//#backend
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
package jdocs.cluster;
|
||||
|
||||
import static jdocs.cluster.TransformationMessages.BACKEND_REGISTRATION;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import jdocs.cluster.TransformationMessages.JobFailed;
|
||||
import jdocs.cluster.TransformationMessages.TransformationJob;
|
||||
import akka.actor.ActorRef;
|
||||
import akka.actor.Terminated;
|
||||
import akka.actor.AbstractActor;
|
||||
|
||||
//#frontend
|
||||
public class TransformationFrontend extends AbstractActor {
|
||||
|
||||
List<ActorRef> backends = new ArrayList<ActorRef>();
|
||||
int jobCounter = 0;
|
||||
|
||||
@Override
|
||||
public Receive createReceive() {
|
||||
return receiveBuilder()
|
||||
.match(TransformationJob.class, job -> backends.isEmpty(), job -> {
|
||||
getSender().tell(
|
||||
new JobFailed("Service unavailable, try again later", job),
|
||||
getSender());
|
||||
})
|
||||
.match(TransformationJob.class, job -> {
|
||||
jobCounter++;
|
||||
backends.get(jobCounter % backends.size())
|
||||
.forward(job, getContext());
|
||||
})
|
||||
.matchEquals(BACKEND_REGISTRATION, x -> {
|
||||
getContext().watch(sender());
|
||||
backends.add(sender());
|
||||
})
|
||||
.match(Terminated.class, terminated -> {
|
||||
backends.remove(terminated.getActor());
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
}
|
||||
//#frontend
|
||||
|
|
@ -0,0 +1,63 @@
|
|||
package jdocs.cluster;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
//#messages
|
||||
public interface TransformationMessages {
|
||||
|
||||
public static class TransformationJob implements Serializable {
|
||||
private final String text;
|
||||
|
||||
public TransformationJob(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
}
|
||||
|
||||
public static class TransformationResult implements Serializable {
|
||||
private final String text;
|
||||
|
||||
public TransformationResult(String text) {
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
public String getText() {
|
||||
return text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "TransformationResult(" + text + ")";
|
||||
}
|
||||
}
|
||||
|
||||
public static class JobFailed implements Serializable {
|
||||
private final String reason;
|
||||
private final TransformationJob job;
|
||||
|
||||
public JobFailed(String reason, TransformationJob job) {
|
||||
this.reason = reason;
|
||||
this.job = job;
|
||||
}
|
||||
|
||||
public String getReason() {
|
||||
return reason;
|
||||
}
|
||||
|
||||
public TransformationJob getJob() {
|
||||
return job;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "JobFailed(" + reason + ")";
|
||||
}
|
||||
}
|
||||
|
||||
public static final String BACKEND_REGISTRATION = "BackendRegistration";
|
||||
|
||||
}
|
||||
//#messages
|
||||
Loading…
Add table
Add a link
Reference in a new issue