├── .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() { 44 | public void onSuccess(Object result) { 45 | System.out.println(result); 46 | frontend.tell(result,frontend); 47 | } 48 | }, ec); 49 | } 50 | 51 | }, ec); 52 | 53 | } 54 | } 55 | -------------------------------------------------------------------------------- /src/main/java/sample/cluster/transformation/TransformationMessages.java: -------------------------------------------------------------------------------- 1 | package sample.cluster.transformation; 2 | 3 | import java.io.Serializable; 4 | 5 | //#messages 6 | public interface TransformationMessages { 7 | 8 | public static class TransformationJob implements Serializable { 9 | private final String text; 10 | 11 | public TransformationJob(String text) { 12 | this.text = text; 13 | } 14 | 15 | public String getText() { 16 | return text; 17 | } 18 | } 19 | 20 | public static class TransformationResult implements Serializable { 21 | private final String text; 22 | 23 | public TransformationResult(String text) { 24 | this.text = text; 25 | } 26 | 27 | public String getText() { 28 | return text; 29 | } 30 | 31 | @Override 32 | public String toString() { 33 | return "TransformationResult(" + text + ")"; 34 | } 35 | } 36 | 37 | public static class JobFailed implements Serializable { 38 | private final String reason; 39 | private final TransformationJob job; 40 | 41 | public JobFailed(String reason, TransformationJob job) { 42 | this.reason = reason; 43 | this.job = job; 44 | } 45 | 46 | public String getReason() { 47 | return reason; 48 | } 49 | 50 | public TransformationJob getJob() { 51 | return job; 52 | } 53 | 54 | @Override 55 | public String toString() { 56 | return "JobFailed(" + reason + ")"; 57 | } 58 | } 59 | 60 | public static final String BACKEND_REGISTRATION = "BackendRegistration"; 61 | 62 | } 63 | //#messages -------------------------------------------------------------------------------- /src/main/resources/application.conf: -------------------------------------------------------------------------------- 1 | akka { 2 | actor { 3 | provider = "akka.cluster.ClusterActorRefProvider" 4 | } 5 | remote { 6 | log-remote-lifecycle-events = off 7 | netty.tcp { 8 | hostname = "127.0.0.1" 9 | port = 0 10 | } 11 | } 12 | 13 | cluster { 14 | seed-nodes = [ 15 | "akka.tcp://ClusterSystem@127.0.0.1:2551", 16 | "akka.tcp://ClusterSystem@127.0.0.1:2552"] 17 | 18 | auto-down-unreachable-after = 10s 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /src/main/resources/factorial.conf: -------------------------------------------------------------------------------- 1 | include "application" 2 | 3 | # //#min-nr-of-members 4 | akka.cluster.min-nr-of-members = 3 5 | # //#min-nr-of-members 6 | 7 | # //#role-min-nr-of-members 8 | akka.cluster.role { 9 | frontend.min-nr-of-members = 1 10 | backend.min-nr-of-members = 2 11 | } 12 | # //#role-min-nr-of-members 13 | 14 | # //#adaptive-router 15 | akka.actor.deployment { 16 | /factorialFrontend/factorialBackendRouter = { 17 | router = adaptive-group 18 | # metrics-selector = heap 19 | # metrics-selector = load 20 | # metrics-selector = cpu 21 | metrics-selector = mix 22 | nr-of-instances = 100 23 | routees.paths = ["/user/factorialBackend"] 24 | cluster { 25 | enabled = on 26 | use-role = backend 27 | allow-local-routees = off 28 | } 29 | } 30 | } 31 | # //#adaptive-router 32 | -------------------------------------------------------------------------------- /src/main/resources/stats1.conf: -------------------------------------------------------------------------------- 1 | include "application" 2 | 3 | # //#config-router-lookup 4 | akka.actor.deployment { 5 | /statsService/workerRouter { 6 | router = consistent-hashing-group 7 | nr-of-instances = 100 8 | routees.paths = ["/user/statsWorker"] 9 | cluster { 10 | enabled = on 11 | allow-local-routees = on 12 | use-role = compute 13 | } 14 | } 15 | } 16 | # //#config-router-lookup 17 | -------------------------------------------------------------------------------- /src/main/resources/stats2.conf: -------------------------------------------------------------------------------- 1 | include "application" 2 | 3 | # //#config-router-deploy 4 | akka.actor.deployment { 5 | /singleton/statsService/workerRouter { 6 | router = consistent-hashing-pool 7 | nr-of-instances = 100 8 | cluster { 9 | enabled = on 10 | max-nr-of-instances-per-node = 3 11 | allow-local-routees = on 12 | use-role = compute 13 | } 14 | } 15 | } 16 | # //#config-router-deploy 17 | 18 | --------------------------------------------------------------------------------