├── .gitignore
├── README.md
├── pom.xml
└── src
└── main
├── java
└── sample
│ └── cluster
│ ├── factorial
│ ├── Extra.java
│ ├── FactorialApp.java
│ ├── FactorialBackend.java
│ ├── FactorialBackendMain.java
│ ├── FactorialFrontend.java
│ ├── FactorialFrontendMain.java
│ ├── FactorialResult.java
│ └── MetricsListener.java
│ ├── simple
│ ├── SimpleClusterApp.java
│ ├── SimpleClusterListener.java
│ └── SimpleClusterListener2.java
│ ├── stats
│ ├── Extra.java
│ ├── StatsAggregator.java
│ ├── StatsMessages.java
│ ├── StatsSampleClient.java
│ ├── StatsSampleClientMain.java
│ ├── StatsSampleMain.java
│ ├── StatsSampleOneMasterClientMain.java
│ ├── StatsSampleOneMasterMain.java
│ ├── StatsService.java
│ └── StatsWorker.java
│ └── transformation
│ ├── TransformationApp.java
│ ├── TransformationBackend.java
│ ├── TransformationBackendMain.java
│ ├── TransformationFrontend.java
│ ├── TransformationFrontendMain.java
│ └── TransformationMessages.java
└── resources
├── application.conf
├── factorial.conf
├── stats1.conf
└── stats2.conf
/.gitignore:
--------------------------------------------------------------------------------
1 | .classpath
2 | .project
3 | .settings/
4 | target/
5 | .idea/
6 | *.iml
7 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | akka_cluster_learn
2 | ==================
3 |
4 | akka cluster codes
5 |
--------------------------------------------------------------------------------
/pom.xml:
--------------------------------------------------------------------------------
1 |
3 | 4.0.0
4 |
5 | com.xiaomi
6 | miliao-rootpom
7 | 2.0.4
8 |
9 | com.54chen
10 | akka_cluster_learn
11 | 0.0.1-SNAPSHOT
12 | jar
13 |
14 |
15 |
16 | com.typesafe.akka
17 | akka-actor_2.10
18 | 2.3.1
19 |
20 |
21 | com.typesafe.akka
22 | akka-remote_2.10
23 | 2.3.1
24 |
25 |
26 | com.typesafe.akka
27 | akka-cluster_2.10
28 | 2.3.1
29 |
30 |
31 | com.typesafe.akka
32 | akka-contrib_2.10
33 | 2.3.1
34 |
35 |
36 | com.google.protobuf
37 | protobuf-java
38 | 2.5.0
39 |
40 |
41 | com.typesafe.akka
42 | akka-testkit_2.10
43 | 2.3.1
44 |
45 |
46 |
47 |
48 | log4j
49 | log4j
50 | 1.2.14
51 |
52 |
53 |
54 | org.json
55 | json
56 | 20090211
57 |
58 |
59 | commons-io
60 | commons-io
61 | 2.1
62 |
63 |
64 |
65 | commons-dbcp
66 | commons-dbcp
67 | 1.2.2
68 |
69 |
70 | mysql
71 | mysql-connector-java
72 | 5.1.10
73 |
74 |
75 | com.netflix.curator
76 | curator-framework
77 | 1.0.1
78 |
79 |
80 |
81 |
82 |
83 |
84 | src/main/resources
85 |
86 | **/*.*
87 |
88 |
89 |
90 |
91 |
92 |
93 | org.apache.maven.plugins
94 | maven-compiler-plugin
95 |
96 | 1.6
97 | 1.6
98 | true
99 | true
100 | UTF-8
101 |
102 |
103 | ${project.basedir}/src/main/java
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
--------------------------------------------------------------------------------
/src/main/java/sample/cluster/factorial/Extra.java:
--------------------------------------------------------------------------------
1 | package sample.cluster.factorial;
2 |
3 | import java.util.Arrays;
4 | import java.util.Collections;
5 |
6 | import akka.actor.ActorRef;
7 | import akka.actor.Props;
8 | import akka.actor.UntypedActor;
9 | import akka.cluster.routing.AdaptiveLoadBalancingGroup;
10 | import akka.cluster.routing.AdaptiveLoadBalancingPool;
11 | import akka.cluster.routing.ClusterRouterGroup;
12 | import akka.cluster.routing.ClusterRouterGroupSettings;
13 | import akka.cluster.routing.ClusterRouterPool;
14 | import akka.cluster.routing.ClusterRouterPoolSettings;
15 | import akka.cluster.routing.HeapMetricsSelector;
16 | import akka.cluster.routing.SystemLoadAverageMetricsSelector;
17 |
18 | //not used, only for documentation
19 | abstract class FactorialFrontend2 extends UntypedActor {
20 | //#router-lookup-in-code
21 | int totalInstances = 100;
22 | Iterable routeesPaths = Arrays.asList("/user/factorialBackend", "");
23 | boolean allowLocalRoutees = true;
24 | String useRole = "backend";
25 | ActorRef backend = getContext().actorOf(
26 | new ClusterRouterGroup(new AdaptiveLoadBalancingGroup(
27 | HeapMetricsSelector.getInstance(), Collections. emptyList()),
28 | new ClusterRouterGroupSettings(totalInstances, routeesPaths,
29 | allowLocalRoutees, useRole)).props(), "factorialBackendRouter2");
30 | //#router-lookup-in-code
31 | }
32 |
33 | //not used, only for documentation
34 | abstract class FactorialFrontend3 extends UntypedActor {
35 | //#router-deploy-in-code
36 | int totalInstances = 100;
37 | int maxInstancesPerNode = 3;
38 | boolean allowLocalRoutees = false;
39 | String useRole = "backend";
40 | ActorRef backend = getContext().actorOf(
41 | new ClusterRouterPool(new AdaptiveLoadBalancingPool(
42 | SystemLoadAverageMetricsSelector.getInstance(), 0),
43 | new ClusterRouterPoolSettings(totalInstances, maxInstancesPerNode,
44 | allowLocalRoutees, useRole)).props(Props
45 | .create(FactorialBackend.class)), "factorialBackendRouter3");
46 | //#router-deploy-in-code
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/sample/cluster/factorial/FactorialApp.java:
--------------------------------------------------------------------------------
1 | package sample.cluster.factorial;
2 |
3 | public class FactorialApp {
4 |
5 | public static void main(String[] args) {
6 | // starting 3 backend nodes and 1 frontend node
7 | FactorialBackendMain.main(new String[] { "2551" });
8 | FactorialBackendMain.main(new String[] { "2552" });
9 | FactorialBackendMain.main(new String[0]);
10 | FactorialFrontendMain.main(new String[0]);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/sample/cluster/factorial/FactorialBackend.java:
--------------------------------------------------------------------------------
1 | package sample.cluster.factorial;
2 |
3 | import java.math.BigInteger;
4 | import java.util.concurrent.Callable;
5 | import scala.concurrent.Future;
6 | import akka.actor.UntypedActor;
7 | import akka.dispatch.Mapper;
8 | import static akka.dispatch.Futures.future;
9 | import static akka.pattern.Patterns.pipe;
10 |
11 | //#backend
12 | public class FactorialBackend extends UntypedActor {
13 |
14 | @Override
15 | public void onReceive(Object message) {
16 | if (message instanceof Integer) {
17 | final Integer n = (Integer) message;
18 | Future f = future(new Callable() {
19 | public BigInteger call() {
20 | return factorial(n);
21 | }
22 | }, getContext().dispatcher());
23 |
24 | Future result = f.map(
25 | new Mapper() {
26 | public FactorialResult apply(BigInteger factorial) {
27 | return new FactorialResult(n, factorial);
28 | }
29 | }, getContext().dispatcher());
30 |
31 | pipe(result, getContext().dispatcher()).to(getSender());
32 |
33 | } else {
34 | unhandled(message);
35 | }
36 | }
37 |
38 | BigInteger factorial(int n) {
39 | BigInteger acc = BigInteger.ONE;
40 | for (int i = 1; i <= n; ++i) {
41 | acc = acc.multiply(BigInteger.valueOf(i));
42 | }
43 | return acc;
44 | }
45 | }
46 | //#backend
47 |
48 |
--------------------------------------------------------------------------------
/src/main/java/sample/cluster/factorial/FactorialBackendMain.java:
--------------------------------------------------------------------------------
1 | package sample.cluster.factorial;
2 |
3 | import com.typesafe.config.Config;
4 | import com.typesafe.config.ConfigFactory;
5 | import akka.actor.ActorSystem;
6 | import akka.actor.Props;
7 |
8 | public class FactorialBackendMain {
9 |
10 | public static void main(String[] args) {
11 | // Override the configuration of the port when specified as program argument
12 | final String port = args.length > 0 ? args[0] : "0";
13 | final Config config = ConfigFactory.parseString("akka.remote.netty.tcp.port=" + port).
14 | withFallback(ConfigFactory.parseString("akka.cluster.roles = [backend]")).
15 | withFallback(ConfigFactory.load("factorial"));
16 |
17 | ActorSystem system = ActorSystem.create("ClusterSystem", config);
18 |
19 | system.actorOf(Props.create(FactorialBackend.class), "factorialBackend");
20 |
21 | system.actorOf(Props.create(MetricsListener.class), "metricsListener");
22 |
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/src/main/java/sample/cluster/factorial/FactorialFrontend.java:
--------------------------------------------------------------------------------
1 | package sample.cluster.factorial;
2 |
3 | import java.util.concurrent.TimeUnit;
4 | import scala.concurrent.duration.Duration;
5 | import akka.actor.ActorRef;
6 | import akka.actor.ReceiveTimeout;
7 | import akka.actor.UntypedActor;
8 | import akka.event.Logging;
9 | import akka.event.LoggingAdapter;
10 | import akka.routing.FromConfig;
11 |
12 | //#frontend
13 | public class FactorialFrontend extends UntypedActor {
14 | final int upToN;
15 | final boolean repeat;
16 |
17 | LoggingAdapter log = Logging.getLogger(getContext().system(), this);
18 |
19 | ActorRef backend = getContext().actorOf(FromConfig.getInstance().props(),
20 | "factorialBackendRouter");
21 |
22 | public FactorialFrontend(int upToN, boolean repeat) {
23 | this.upToN = upToN;
24 | this.repeat = repeat;
25 | }
26 |
27 | @Override
28 | public void preStart() {
29 | sendJobs();
30 | getContext().setReceiveTimeout(Duration.create(10, TimeUnit.SECONDS));
31 | }
32 |
33 | @Override
34 | public void onReceive(Object message) {
35 | if (message instanceof FactorialResult) {
36 | FactorialResult result = (FactorialResult) message;
37 | if (result.n == upToN) {
38 | log.debug("{}! = {}", result.n, result.factorial);
39 | if (repeat)
40 | sendJobs();
41 | else
42 | getContext().stop(getSelf());
43 | }
44 |
45 | } else if (message instanceof ReceiveTimeout) {
46 | log.info("Timeout");
47 | sendJobs();
48 |
49 | } else {
50 | unhandled(message);
51 | }
52 | }
53 |
54 | void sendJobs() {
55 | log.info("Starting batch of factorials up to [{}]", upToN);
56 | for (int n = 1; n <= upToN; n++) {
57 | backend.tell(n, getSelf());
58 | }
59 | }
60 |
61 | }
62 |
63 | //#frontend
64 |
65 |
--------------------------------------------------------------------------------
/src/main/java/sample/cluster/factorial/FactorialFrontendMain.java:
--------------------------------------------------------------------------------
1 | package sample.cluster.factorial;
2 |
3 | import com.typesafe.config.Config;
4 | import com.typesafe.config.ConfigFactory;
5 | import akka.actor.ActorSystem;
6 | import akka.actor.Props;
7 | import akka.cluster.Cluster;
8 |
9 | public class FactorialFrontendMain {
10 |
11 | public static void main(String[] args) {
12 | final int upToN = 200;
13 |
14 | final Config config = ConfigFactory.parseString(
15 | "akka.cluster.roles = [frontend]").withFallback(
16 | ConfigFactory.load("factorial"));
17 |
18 | final ActorSystem system = ActorSystem.create("ClusterSystem", config);
19 | system.log().info(
20 | "Factorials will start when 2 backend members in the cluster.");
21 | //#registerOnUp
22 | Cluster.get(system).registerOnMemberUp(new Runnable() {
23 | @Override
24 | public void run() {
25 | system.actorOf(Props.create(FactorialFrontend.class, upToN, true),
26 | "factorialFrontend");
27 | }
28 | });
29 | //#registerOnUp
30 | }
31 |
32 | }
33 |
--------------------------------------------------------------------------------
/src/main/java/sample/cluster/factorial/FactorialResult.java:
--------------------------------------------------------------------------------
1 | package sample.cluster.factorial;
2 |
3 | import java.math.BigInteger;
4 | import java.io.Serializable;
5 |
6 | public class FactorialResult implements Serializable {
7 | public final int n;
8 | public final BigInteger factorial;
9 |
10 | FactorialResult(int n, BigInteger factorial) {
11 | this.n = n;
12 | this.factorial = factorial;
13 | }
14 | }
--------------------------------------------------------------------------------
/src/main/java/sample/cluster/factorial/MetricsListener.java:
--------------------------------------------------------------------------------
1 | package sample.cluster.factorial;
2 |
3 | //#metrics-listener
4 | import akka.actor.UntypedActor;
5 | import akka.cluster.Cluster;
6 | import akka.cluster.ClusterEvent.ClusterMetricsChanged;
7 | import akka.cluster.ClusterEvent.CurrentClusterState;
8 | import akka.cluster.NodeMetrics;
9 | import akka.cluster.StandardMetrics;
10 | import akka.cluster.StandardMetrics.HeapMemory;
11 | import akka.cluster.StandardMetrics.Cpu;
12 | import akka.event.Logging;
13 | import akka.event.LoggingAdapter;
14 |
15 | public class MetricsListener extends UntypedActor {
16 | LoggingAdapter log = Logging.getLogger(getContext().system(), this);
17 |
18 | Cluster cluster = Cluster.get(getContext().system());
19 |
20 | //subscribe to ClusterMetricsChanged
21 | @Override
22 | public void preStart() {
23 | cluster.subscribe(getSelf(), ClusterMetricsChanged.class);
24 | }
25 |
26 | //re-subscribe when restart
27 | @Override
28 | public void postStop() {
29 | cluster.unsubscribe(getSelf());
30 | }
31 |
32 |
33 | @Override
34 | public void onReceive(Object message) {
35 | if (message instanceof ClusterMetricsChanged) {
36 | ClusterMetricsChanged clusterMetrics = (ClusterMetricsChanged) message;
37 | for (NodeMetrics nodeMetrics : clusterMetrics.getNodeMetrics()) {
38 | if (nodeMetrics.address().equals(cluster.selfAddress())) {
39 | logHeap(nodeMetrics);
40 | logCpu(nodeMetrics);
41 | }
42 | }
43 |
44 | } else if (message instanceof CurrentClusterState) {
45 | // ignore
46 |
47 | } else {
48 | unhandled(message);
49 | }
50 | }
51 |
52 | void logHeap(NodeMetrics nodeMetrics) {
53 | HeapMemory heap = StandardMetrics.extractHeapMemory(nodeMetrics);
54 | if (heap != null) {
55 | log.info("Used heap: {} MB", ((double) heap.used()) / 1024 / 1024);
56 | }
57 | }
58 |
59 | void logCpu(NodeMetrics nodeMetrics) {
60 | Cpu cpu = StandardMetrics.extractCpu(nodeMetrics);
61 | if (cpu != null && cpu.systemLoadAverage().isDefined()) {
62 | log.info("Load: {} ({} processors)", cpu.systemLoadAverage().get(),
63 | cpu.processors());
64 | }
65 | }
66 |
67 | }
68 | //#metrics-listener
--------------------------------------------------------------------------------
/src/main/java/sample/cluster/simple/SimpleClusterApp.java:
--------------------------------------------------------------------------------
1 | package sample.cluster.simple;
2 |
3 | import akka.actor.ActorSystem;
4 | import akka.actor.Props;
5 |
6 | import com.typesafe.config.Config;
7 | import com.typesafe.config.ConfigFactory;
8 |
9 | public class SimpleClusterApp {
10 |
11 | public static void main(String[] args) {
12 | if (args.length == 0)
13 | startup(new String[] { "2551", "2552", "0" });
14 | else
15 | startup(args);
16 | }
17 |
18 | public static void startup(String[] ports) {
19 | for (String port : ports) {
20 | // Override the configuration of the port
21 | Config config = ConfigFactory.parseString(
22 | "akka.remote.netty.tcp.port=" + port).withFallback(
23 | ConfigFactory.load());
24 |
25 | // Create an Akka system
26 | ActorSystem system = ActorSystem.create("ClusterSystem", config);
27 |
28 | // Create an actor that handles cluster domain events
29 | system.actorOf(Props.create(SimpleClusterListener.class),
30 | "clusterListener");
31 |
32 | }
33 | }
34 | }
35 |
--------------------------------------------------------------------------------
/src/main/java/sample/cluster/simple/SimpleClusterListener.java:
--------------------------------------------------------------------------------
1 | package sample.cluster.simple;
2 |
3 | import akka.actor.UntypedActor;
4 | import akka.cluster.Cluster;
5 | import akka.cluster.ClusterEvent;
6 | import akka.cluster.ClusterEvent.MemberEvent;
7 | import akka.cluster.ClusterEvent.MemberUp;
8 | import akka.cluster.ClusterEvent.MemberRemoved;
9 | import akka.cluster.ClusterEvent.UnreachableMember;
10 | import akka.event.Logging;
11 | import akka.event.LoggingAdapter;
12 |
13 | public class SimpleClusterListener extends UntypedActor {
14 | LoggingAdapter log = Logging.getLogger(getContext().system(), this);
15 | Cluster cluster = Cluster.get(getContext().system());
16 |
17 | //subscribe to cluster changes
18 | @Override
19 | public void preStart() {
20 | //#subscribe
21 | cluster.subscribe(getSelf(), ClusterEvent.initialStateAsEvents(),
22 | MemberEvent.class, UnreachableMember.class);
23 | //#subscribe
24 | }
25 |
26 | //re-subscribe when restart
27 | @Override
28 | public void postStop() {
29 | cluster.unsubscribe(getSelf());
30 | }
31 |
32 | @Override
33 | public void onReceive(Object message) {
34 | if (message instanceof MemberUp) {
35 | MemberUp mUp = (MemberUp) message;
36 | log.info("Member is Up: {}", mUp.member());
37 |
38 | } else if (message instanceof UnreachableMember) {
39 | UnreachableMember mUnreachable = (UnreachableMember) message;
40 | log.info("Member detected as unreachable: {}", mUnreachable.member());
41 |
42 | } else if (message instanceof MemberRemoved) {
43 | MemberRemoved mRemoved = (MemberRemoved) message;
44 | log.info("Member is Removed: {}", mRemoved.member());
45 |
46 | } else if (message instanceof MemberEvent) {
47 | // ignore
48 |
49 | } else {
50 | unhandled(message);
51 | }
52 |
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/src/main/java/sample/cluster/simple/SimpleClusterListener2.java:
--------------------------------------------------------------------------------
1 | package sample.cluster.simple;
2 |
3 | import akka.actor.UntypedActor;
4 | import akka.cluster.Cluster;
5 | import akka.cluster.ClusterEvent.CurrentClusterState;
6 | import akka.cluster.ClusterEvent.MemberEvent;
7 | import akka.cluster.ClusterEvent.MemberUp;
8 | import akka.cluster.ClusterEvent.MemberRemoved;
9 | import akka.cluster.ClusterEvent.UnreachableMember;
10 | import akka.event.Logging;
11 | import akka.event.LoggingAdapter;
12 |
13 | public class SimpleClusterListener2 extends UntypedActor {
14 | LoggingAdapter log = Logging.getLogger(getContext().system(), this);
15 | Cluster cluster = Cluster.get(getContext().system());
16 |
17 | //subscribe to cluster changes
18 | @Override
19 | public void preStart() {
20 | //#subscribe
21 | cluster.subscribe(getSelf(), MemberEvent.class, UnreachableMember.class);
22 | //#subscribe
23 | }
24 |
25 | //re-subscribe when restart
26 | @Override
27 | public void postStop() {
28 | cluster.unsubscribe(getSelf());
29 | }
30 |
31 | @Override
32 | public void onReceive(Object message) {
33 | if (message instanceof CurrentClusterState) {
34 | CurrentClusterState state = (CurrentClusterState) message;
35 | log.info("Current members: {}", state.members());
36 |
37 | } else if (message instanceof MemberUp) {
38 | MemberUp mUp = (MemberUp) message;
39 | log.info("Member is Up: {}", mUp.member());
40 |
41 | } else if (message instanceof UnreachableMember) {
42 | UnreachableMember mUnreachable = (UnreachableMember) message;
43 | log.info("Member detected as unreachable: {}", mUnreachable.member());
44 |
45 | } else if (message instanceof MemberRemoved) {
46 | MemberRemoved mRemoved = (MemberRemoved) message;
47 | log.info("Member is Removed: {}", mRemoved.member());
48 |
49 | } else if (message instanceof MemberEvent) {
50 | // ignore
51 |
52 | } else {
53 | unhandled(message);
54 | }
55 |
56 | }
57 | }
58 |
--------------------------------------------------------------------------------
/src/main/java/sample/cluster/stats/Extra.java:
--------------------------------------------------------------------------------
1 | package sample.cluster.stats;
2 |
3 | import java.util.Collections;
4 |
5 | import akka.actor.ActorRef;
6 | import akka.actor.Props;
7 | import akka.actor.UntypedActor;
8 | import akka.cluster.routing.ClusterRouterGroup;
9 | import akka.cluster.routing.ClusterRouterGroupSettings;
10 | import akka.cluster.routing.ClusterRouterPool;
11 | import akka.cluster.routing.ClusterRouterPoolSettings;
12 | import akka.routing.ConsistentHashingGroup;
13 | import akka.routing.ConsistentHashingPool;
14 |
15 | //not used, only for documentation
16 | abstract class StatsService2 extends UntypedActor {
17 | //#router-lookup-in-code
18 | int totalInstances = 100;
19 | Iterable routeesPaths = Collections
20 | .singletonList("/user/statsWorker");
21 | boolean allowLocalRoutees = true;
22 | String useRole = "compute";
23 | ActorRef workerRouter = getContext().actorOf(
24 | new ClusterRouterGroup(new ConsistentHashingGroup(routeesPaths),
25 | new ClusterRouterGroupSettings(totalInstances, routeesPaths,
26 | allowLocalRoutees, useRole)).props(), "workerRouter2");
27 | //#router-lookup-in-code
28 | }
29 |
30 | //not used, only for documentation
31 | abstract class StatsService3 extends UntypedActor {
32 | //#router-deploy-in-code
33 | int totalInstances = 100;
34 | int maxInstancesPerNode = 3;
35 | boolean allowLocalRoutees = false;
36 | String useRole = "compute";
37 | ActorRef workerRouter = getContext().actorOf(
38 | new ClusterRouterPool(new ConsistentHashingPool(0),
39 | new ClusterRouterPoolSettings(totalInstances, maxInstancesPerNode,
40 | allowLocalRoutees, useRole)).props(Props
41 | .create(StatsWorker.class)), "workerRouter3");
42 | //#router-deploy-in-code
43 | }
44 |
--------------------------------------------------------------------------------
/src/main/java/sample/cluster/stats/StatsAggregator.java:
--------------------------------------------------------------------------------
1 | package sample.cluster.stats;
2 |
3 | import java.util.ArrayList;
4 | import java.util.List;
5 | import java.util.concurrent.TimeUnit;
6 |
7 | import sample.cluster.stats.StatsMessages.JobFailed;
8 | import sample.cluster.stats.StatsMessages.StatsResult;
9 | import scala.concurrent.duration.Duration;
10 | import akka.actor.ActorRef;
11 | import akka.actor.ReceiveTimeout;
12 | import akka.actor.UntypedActor;
13 |
14 | //#aggregator
15 | public class StatsAggregator extends UntypedActor {
16 |
17 | final int expectedResults;
18 | final ActorRef replyTo;
19 | final List results = new ArrayList();
20 |
21 | public StatsAggregator(int expectedResults, ActorRef replyTo) {
22 | this.expectedResults = expectedResults;
23 | this.replyTo = replyTo;
24 | }
25 |
26 | @Override
27 | public void preStart() {
28 | getContext().setReceiveTimeout(Duration.create(3, TimeUnit.SECONDS));
29 | }
30 |
31 | @Override
32 | public void onReceive(Object message) {
33 | if (message instanceof Integer) {
34 | Integer wordCount = (Integer) message;
35 | results.add(wordCount);
36 | if (results.size() == expectedResults) {
37 | int sum = 0;
38 | for (int c : results)
39 | sum += c;
40 | double meanWordLength = ((double) sum) / results.size();
41 | replyTo.tell(new StatsResult(meanWordLength), getSelf());
42 | getContext().stop(getSelf());
43 | }
44 |
45 | } else if (message == ReceiveTimeout.getInstance()) {
46 | replyTo.tell(new JobFailed("Service unavailable, try again later"),
47 | getSelf());
48 | getContext().stop(getSelf());
49 |
50 | } else {
51 | unhandled(message);
52 | }
53 | }
54 |
55 | }
56 | //#aggregator
57 |
--------------------------------------------------------------------------------
/src/main/java/sample/cluster/stats/StatsMessages.java:
--------------------------------------------------------------------------------
1 | package sample.cluster.stats;
2 |
3 | import java.io.Serializable;
4 |
5 | //#messages
6 | public interface StatsMessages {
7 |
8 | public static class StatsJob implements Serializable {
9 | private final String text;
10 |
11 | public StatsJob(String text) {
12 | this.text = text;
13 | }
14 |
15 | public String getText() {
16 | return text;
17 | }
18 | }
19 |
20 | public static class StatsResult implements Serializable {
21 | private final double meanWordLength;
22 |
23 | public StatsResult(double meanWordLength) {
24 | this.meanWordLength = meanWordLength;
25 | }
26 |
27 | public double getMeanWordLength() {
28 | return meanWordLength;
29 | }
30 |
31 | @Override
32 | public String toString() {
33 | return "meanWordLength: " + meanWordLength;
34 | }
35 | }
36 |
37 | public static class JobFailed implements Serializable {
38 | private final String reason;
39 |
40 | public JobFailed(String reason) {
41 | this.reason = reason;
42 | }
43 |
44 | public String getReason() {
45 | return reason;
46 | }
47 |
48 | @Override
49 | public String toString() {
50 | return "JobFailed(" + reason + ")";
51 | }
52 | }
53 |
54 | }
55 | //#messages
--------------------------------------------------------------------------------
/src/main/java/sample/cluster/stats/StatsSampleClient.java:
--------------------------------------------------------------------------------
1 | package sample.cluster.stats;
2 |
3 | import java.util.ArrayList;
4 | import java.util.HashSet;
5 | import java.util.List;
6 | import java.util.Set;
7 | import java.util.concurrent.TimeUnit;
8 |
9 | import sample.cluster.stats.StatsMessages.JobFailed;
10 | import sample.cluster.stats.StatsMessages.StatsJob;
11 | import sample.cluster.stats.StatsMessages.StatsResult;
12 | import scala.concurrent.forkjoin.ThreadLocalRandom;
13 | import scala.concurrent.duration.Duration;
14 | import scala.concurrent.duration.FiniteDuration;
15 | import akka.actor.ActorSelection;
16 | import akka.actor.Address;
17 | import akka.actor.Cancellable;
18 | import akka.actor.UntypedActor;
19 | import akka.cluster.Cluster;
20 | import akka.cluster.ClusterEvent.UnreachableMember;
21 | import akka.cluster.ClusterEvent.ReachableMember;
22 | import akka.cluster.ClusterEvent.CurrentClusterState;
23 | import akka.cluster.ClusterEvent.MemberEvent;
24 | import akka.cluster.ClusterEvent.MemberUp;
25 | import akka.cluster.ClusterEvent.ReachabilityEvent;
26 | import akka.cluster.Member;
27 | import akka.cluster.MemberStatus;
28 |
29 | public class StatsSampleClient extends UntypedActor {
30 |
31 | final String servicePath;
32 | final Cancellable tickTask;
33 | final Set nodes = new HashSet();
34 |
35 | Cluster cluster = Cluster.get(getContext().system());
36 |
37 | public StatsSampleClient(String servicePath) {
38 | this.servicePath = servicePath;
39 | FiniteDuration interval = Duration.create(2, TimeUnit.SECONDS);
40 | tickTask = getContext()
41 | .system()
42 | .scheduler()
43 | .schedule(interval, interval, getSelf(), "tick",
44 | getContext().dispatcher(), null);
45 | }
46 |
47 | //subscribe to cluster changes, MemberEvent
48 | @Override
49 | public void preStart() {
50 | cluster.subscribe(getSelf(), MemberEvent.class, ReachabilityEvent.class);
51 | }
52 |
53 | //re-subscribe when restart
54 | @Override
55 | public void postStop() {
56 | cluster.unsubscribe(getSelf());
57 | tickTask.cancel();
58 | }
59 |
60 | @Override
61 | public void onReceive(Object message) {
62 | if (message.equals("tick") && !nodes.isEmpty()) {
63 | // just pick any one
64 | List nodesList = new ArrayList(nodes);
65 | Address address = nodesList.get(ThreadLocalRandom.current().nextInt(
66 | nodesList.size()));
67 | ActorSelection service = getContext().actorSelection(address + servicePath);
68 | service.tell(new StatsJob("this is the text that will be analyzed"),
69 | getSelf());
70 |
71 | } else if (message instanceof StatsResult) {
72 | StatsResult result = (StatsResult) message;
73 | System.out.println(result);
74 |
75 | } else if (message instanceof JobFailed) {
76 | JobFailed failed = (JobFailed) message;
77 | System.out.println(failed);
78 |
79 | } else if (message instanceof CurrentClusterState) {
80 | CurrentClusterState state = (CurrentClusterState) message;
81 | nodes.clear();
82 | for (Member member : state.getMembers()) {
83 | if (member.hasRole("compute") && member.status().equals(MemberStatus.up())) {
84 | nodes.add(member.address());
85 | }
86 | }
87 |
88 | } else if (message instanceof MemberUp) {
89 | MemberUp mUp = (MemberUp) message;
90 | if (mUp.member().hasRole("compute"))
91 | nodes.add(mUp.member().address());
92 |
93 | } else if (message instanceof MemberEvent) {
94 | MemberEvent other = (MemberEvent) message;
95 | nodes.remove(other.member().address());
96 |
97 | } else if (message instanceof UnreachableMember) {
98 | UnreachableMember unreachable = (UnreachableMember) message;
99 | nodes.remove(unreachable.member().address());
100 |
101 | } else if (message instanceof ReachableMember) {
102 | ReachableMember reachable = (ReachableMember) message;
103 | if (reachable.member().hasRole("compute"))
104 | nodes.add(reachable.member().address());
105 |
106 | } else {
107 | unhandled(message);
108 | }
109 | }
110 |
111 | }
112 |
--------------------------------------------------------------------------------
/src/main/java/sample/cluster/stats/StatsSampleClientMain.java:
--------------------------------------------------------------------------------
1 | package sample.cluster.stats;
2 |
3 | import akka.actor.ActorSystem;
4 | import akka.actor.Props;
5 |
6 | import com.typesafe.config.ConfigFactory;
7 |
8 | public class StatsSampleClientMain {
9 |
10 | public static void main(String[] args) {
11 | // note that client is not a compute node, role not defined
12 | ActorSystem system = ActorSystem.create("ClusterSystem",
13 | ConfigFactory.load("stats1"));
14 | system.actorOf(Props.create(StatsSampleClient.class, "/user/statsService"),
15 | "client");
16 | }
17 | }
18 |
--------------------------------------------------------------------------------
/src/main/java/sample/cluster/stats/StatsSampleMain.java:
--------------------------------------------------------------------------------
1 | package sample.cluster.stats;
2 |
3 | import com.typesafe.config.Config;
4 | import com.typesafe.config.ConfigFactory;
5 |
6 | import akka.actor.ActorSystem;
7 | import akka.actor.Props;
8 |
9 | public class StatsSampleMain {
10 |
11 | public static void main(String[] args) {
12 | if (args.length == 0) {
13 | startup(new String[] { "2551", "2552", "0" });
14 | StatsSampleClientMain.main(new String[0]);
15 | } else {
16 | startup(args);
17 | }
18 | }
19 |
20 | public static void startup(String[] ports) {
21 | for (String port : ports) {
22 | // Override the configuration of the port
23 | Config config = ConfigFactory
24 | .parseString("akka.remote.netty.tcp.port=" + port)
25 | .withFallback(
26 | ConfigFactory.parseString("akka.cluster.roles = [compute]"))
27 | .withFallback(ConfigFactory.load("stats1"));
28 |
29 | ActorSystem system = ActorSystem.create("ClusterSystem", config);
30 |
31 | system.actorOf(Props.create(StatsWorker.class), "statsWorker");
32 | system.actorOf(Props.create(StatsService.class), "statsService");
33 | }
34 |
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/src/main/java/sample/cluster/stats/StatsSampleOneMasterClientMain.java:
--------------------------------------------------------------------------------
1 | package sample.cluster.stats;
2 |
3 | import com.typesafe.config.ConfigFactory;
4 |
5 | import akka.actor.ActorSystem;
6 | import akka.actor.Props;
7 |
8 | public class StatsSampleOneMasterClientMain {
9 |
10 | public static void main(String[] args) {
11 | // note that client is not a compute node, role not defined
12 | ActorSystem system = ActorSystem.create("ClusterSystem",
13 | ConfigFactory.load("stats2"));
14 | system.actorOf(Props.create(StatsSampleClient.class, "/user/statsServiceProxy"),
15 | "client");
16 |
17 | }
18 |
19 | }
20 |
--------------------------------------------------------------------------------
/src/main/java/sample/cluster/stats/StatsSampleOneMasterMain.java:
--------------------------------------------------------------------------------
1 | package sample.cluster.stats;
2 |
3 | import com.typesafe.config.Config;
4 | import com.typesafe.config.ConfigFactory;
5 |
6 | import akka.actor.ActorSystem;
7 | import akka.actor.PoisonPill;
8 | import akka.actor.Props;
9 | import akka.contrib.pattern.ClusterSingletonManager;
10 | import akka.contrib.pattern.ClusterSingletonProxy;
11 |
12 | public class StatsSampleOneMasterMain {
13 |
14 | public static void main(String[] args) {
15 | if (args.length == 0) {
16 | startup(new String[] { "2551", "2552", "0" });
17 | StatsSampleOneMasterClientMain.main(new String[0]);
18 | } else {
19 | startup(args);
20 | }
21 | }
22 |
23 | public static void startup(String[] ports) {
24 | for (String port : ports) {
25 | // Override the configuration of the port
26 | Config config = ConfigFactory
27 | .parseString("akka.remote.netty.tcp.port=" + port)
28 | .withFallback(
29 | ConfigFactory.parseString("akka.cluster.roles = [compute]"))
30 | .withFallback(ConfigFactory.load("stats2"));
31 |
32 | ActorSystem system = ActorSystem.create("ClusterSystem", config);
33 |
34 | //#create-singleton-manager
35 | system.actorOf(ClusterSingletonManager.defaultProps(
36 | Props.create(StatsService.class), "statsService",
37 | PoisonPill.getInstance(), "compute"), "singleton");
38 | //#create-singleton-manager
39 |
40 | //#singleton-proxy
41 | system.actorOf(ClusterSingletonProxy.defaultProps("/user/singleton/statsService",
42 | "compute"), "statsServiceProxy");
43 | //#singleton-proxy
44 | }
45 |
46 | }
47 | }
48 |
--------------------------------------------------------------------------------
/src/main/java/sample/cluster/stats/StatsService.java:
--------------------------------------------------------------------------------
1 | package sample.cluster.stats;
2 |
3 | import sample.cluster.stats.StatsMessages.StatsJob;
4 | import akka.actor.ActorRef;
5 | import akka.actor.Props;
6 | import akka.actor.UntypedActor;
7 | import akka.routing.ConsistentHashingRouter.ConsistentHashableEnvelope;
8 | import akka.routing.FromConfig;
9 |
10 | //#service
11 | public class StatsService extends UntypedActor {
12 |
13 | // This router is used both with lookup and deploy of routees. If you
14 | // have a router with only lookup of routees you can use Props.empty()
15 | // instead of Props.create(StatsWorker.class).
16 | ActorRef workerRouter = getContext().actorOf(
17 | FromConfig.getInstance().props(Props.create(StatsWorker.class)),
18 | "workerRouter");
19 |
20 | @Override
21 | public void onReceive(Object message) {
22 | if (message instanceof StatsJob) {
23 | StatsJob job = (StatsJob) message;
24 | if (job.getText().equals("")) {
25 | unhandled(message);
26 | } else {
27 | final String[] words = job.getText().split(" ");
28 | final ActorRef replyTo = getSender();
29 |
30 | // create actor that collects replies from workers
31 | ActorRef aggregator = getContext().actorOf(
32 | Props.create(StatsAggregator.class, words.length, replyTo));
33 |
34 | // send each word to a worker
35 | for (String word : words) {
36 | workerRouter.tell(new ConsistentHashableEnvelope(word, word),
37 | aggregator);
38 | }
39 | }
40 |
41 | } else {
42 | unhandled(message);
43 | }
44 | }
45 | }
46 |
47 | //#service
48 |
49 |
--------------------------------------------------------------------------------
/src/main/java/sample/cluster/stats/StatsWorker.java:
--------------------------------------------------------------------------------
1 | package sample.cluster.stats;
2 |
3 | import java.util.HashMap;
4 | import java.util.Map;
5 |
6 | import akka.actor.UntypedActor;
7 |
8 | //#worker
9 | public class StatsWorker extends UntypedActor {
10 |
11 | Map cache = new HashMap();
12 |
13 | @Override
14 | public void onReceive(Object message) {
15 | if (message instanceof String) {
16 | String word = (String) message;
17 | Integer length = cache.get(word);
18 | if (length == null) {
19 | length = word.length();
20 | cache.put(word, length);
21 | }
22 | getSender().tell(length, getSelf());
23 |
24 | } else {
25 | unhandled(message);
26 | }
27 | }
28 |
29 | }
30 | //#worker
--------------------------------------------------------------------------------
/src/main/java/sample/cluster/transformation/TransformationApp.java:
--------------------------------------------------------------------------------
1 | package sample.cluster.transformation;
2 |
3 | public class TransformationApp {
4 |
5 | public static void main(String[] args) {
6 | // starting 2 frontend nodes and 3 backend nodes
7 | TransformationBackendMain.main(new String[] { "2551" });
8 | TransformationBackendMain.main(new String[] { "2552" });
9 | TransformationBackendMain.main(new String[0]);
10 | TransformationFrontendMain.main(new String[0]);
11 | }
12 | }
13 |
--------------------------------------------------------------------------------
/src/main/java/sample/cluster/transformation/TransformationBackend.java:
--------------------------------------------------------------------------------
1 | package sample.cluster.transformation;
2 |
3 | import static sample.cluster.transformation.TransformationMessages.BACKEND_REGISTRATION;
4 | import sample.cluster.transformation.TransformationMessages.TransformationJob;
5 | import sample.cluster.transformation.TransformationMessages.TransformationResult;
6 | import akka.actor.UntypedActor;
7 | import akka.cluster.Cluster;
8 | import akka.cluster.ClusterEvent.CurrentClusterState;
9 | import akka.cluster.ClusterEvent.MemberUp;
10 | import akka.cluster.Member;
11 | import akka.cluster.MemberStatus;
12 |
13 | //#backend
14 | public class TransformationBackend extends UntypedActor {
15 |
16 | Cluster cluster = Cluster.get(getContext().system());
17 |
18 | //subscribe to cluster changes, MemberUp
19 | @Override
20 | public void preStart() {
21 | cluster.subscribe(getSelf(), MemberUp.class);
22 | }
23 |
24 | //re-subscribe when restart
25 | @Override
26 | public void postStop() {
27 | cluster.unsubscribe(getSelf());
28 | }
29 |
30 | @Override
31 | public void onReceive(Object message) {
32 | if (message instanceof TransformationJob) {
33 | TransformationJob job = (TransformationJob) message;
34 | getSender().tell(new TransformationResult(job.getText().toUpperCase()),
35 | getSelf());
36 |
37 | } else if (message instanceof CurrentClusterState) {
38 | CurrentClusterState state = (CurrentClusterState) message;
39 | for (Member member : state.getMembers()) {
40 | if (member.status().equals(MemberStatus.up())) {
41 | register(member);
42 | }
43 | }
44 |
45 | } else if (message instanceof MemberUp) {
46 | MemberUp mUp = (MemberUp) message;
47 | register(mUp.member());
48 |
49 | } else {
50 | unhandled(message);
51 | }
52 | }
53 |
54 | void register(Member member) {
55 | if (member.hasRole("frontend"))
56 | getContext().actorSelection(member.address() + "/user/frontend").tell(
57 | BACKEND_REGISTRATION, getSelf());
58 | }
59 | }
60 | //#backend
61 |
--------------------------------------------------------------------------------
/src/main/java/sample/cluster/transformation/TransformationBackendMain.java:
--------------------------------------------------------------------------------
1 | package sample.cluster.transformation;
2 |
3 | import com.typesafe.config.Config;
4 | import com.typesafe.config.ConfigFactory;
5 |
6 | import akka.actor.ActorSystem;
7 | import akka.actor.Props;
8 |
9 | public class TransformationBackendMain {
10 |
11 | public static void main(String[] args) {
12 | // Override the configuration of the port when specified as program argument
13 | final String port = args.length > 0 ? args[0] : "0";
14 | final Config config = ConfigFactory.parseString("akka.remote.netty.tcp.port=" + port).
15 | withFallback(ConfigFactory.parseString("akka.cluster.roles = [backend]")).
16 | withFallback(ConfigFactory.load());
17 |
18 | ActorSystem system = ActorSystem.create("ClusterSystem", config);
19 |
20 | system.actorOf(Props.create(TransformationBackend.class), "backend");
21 |
22 | }
23 |
24 | }
25 |
--------------------------------------------------------------------------------
/src/main/java/sample/cluster/transformation/TransformationFrontend.java:
--------------------------------------------------------------------------------
1 | package sample.cluster.transformation;
2 |
3 | import static sample.cluster.transformation.TransformationMessages.BACKEND_REGISTRATION;
4 |
5 | import java.util.ArrayList;
6 | import java.util.List;
7 |
8 | import sample.cluster.transformation.TransformationMessages.JobFailed;
9 | import sample.cluster.transformation.TransformationMessages.TransformationJob;
10 | import sample.cluster.transformation.TransformationMessages.TransformationResult;
11 |
12 | import akka.actor.ActorRef;
13 | import akka.actor.Terminated;
14 | import akka.actor.UntypedActor;
15 |
16 | //#frontend
17 | public class TransformationFrontend extends UntypedActor {
18 |
19 | List backends = new ArrayList();
20 | int jobCounter = 0;
21 |
22 | @Override
23 | public void onReceive(Object message) {
24 |
25 | if (message instanceof TransformationResult) {
26 | System.out.println("I am here " + message);
27 | }
28 |
29 | if ((message instanceof TransformationJob) && backends.isEmpty()) {
30 | TransformationJob job = (TransformationJob) message;
31 | getSender().tell(
32 | new JobFailed("Service unavailable, try again later", job),
33 | getSender());
34 |
35 | } else if (message instanceof TransformationJob) {
36 | TransformationJob job = (TransformationJob) message;
37 | jobCounter++;
38 | backends.get(jobCounter % backends.size())
39 | .forward(job, getContext());
40 |
41 | } else if (message.equals(BACKEND_REGISTRATION)) {
42 | getContext().watch(getSender());
43 | backends.add(getSender());
44 |
45 | } else if (message instanceof Terminated) {
46 | Terminated terminated = (Terminated) message;
47 | backends.remove(terminated.getActor());
48 |
49 | } else {
50 | unhandled(message);
51 | }
52 | }
53 |
54 | }
55 | //#frontend
56 |
--------------------------------------------------------------------------------
/src/main/java/sample/cluster/transformation/TransformationFrontendMain.java:
--------------------------------------------------------------------------------
1 | package sample.cluster.transformation;
2 |
3 | import java.util.concurrent.TimeUnit;
4 | import java.util.concurrent.atomic.AtomicInteger;
5 |
6 | import com.typesafe.config.Config;
7 | import com.typesafe.config.ConfigFactory;
8 |
9 | import sample.cluster.transformation.TransformationMessages.TransformationJob;
10 | import scala.concurrent.ExecutionContext;
11 | import scala.concurrent.duration.Duration;
12 | import scala.concurrent.duration.FiniteDuration;
13 | import akka.actor.ActorRef;
14 | import akka.actor.ActorSystem;
15 | import akka.actor.Props;
16 | import akka.dispatch.OnSuccess;
17 | import akka.util.Timeout;
18 | import static akka.pattern.Patterns.ask;
19 |
20 | public class TransformationFrontendMain {
21 |
22 | public static void main(String[] args) {
23 | // Override the configuration of the port when specified as program argument
24 | final String port = args.length > 0 ? args[0] : "0";
25 | final Config config = ConfigFactory.parseString("akka.remote.netty.tcp.port=" + port).
26 | withFallback(ConfigFactory.parseString("akka.cluster.roles = [frontend]")).
27 | withFallback(ConfigFactory.load());
28 |
29 | ActorSystem system = ActorSystem.create("ClusterSystem", config);
30 |
31 | final ActorRef frontend = system.actorOf(
32 | Props.create(TransformationFrontend.class), "frontend");
33 | final FiniteDuration interval = Duration.create(2, TimeUnit.SECONDS);
34 | final Timeout timeout = new Timeout(Duration.create(5, TimeUnit.SECONDS));
35 | final ExecutionContext ec = system.dispatcher();
36 | final AtomicInteger counter = new AtomicInteger();
37 |
38 | system.scheduler().schedule(interval, interval, new Runnable() {
39 | public void run() {
40 |
41 | ask(frontend,
42 | new TransformationJob("hello-" + counter.incrementAndGet()),
43 | timeout).onSuccess(new OnSuccess