diff --git a/akka-docs/rst/java/cluster-usage.rst b/akka-docs/rst/java/cluster-usage.rst index 8dd2db1661..4e003aa082 100644 --- a/akka-docs/rst/java/cluster-usage.rst +++ b/akka-docs/rst/java/cluster-usage.rst @@ -23,15 +23,12 @@ The Akka cluster is a separate jar file. Make sure that you have the following d A Simple Cluster Example ^^^^^^^^^^^^^^^^^^^^^^^^ -The following small program together with its configuration starts an ``ActorSystem`` -with the Cluster enabled. It joins the cluster and logs some membership events. +The following configuration enables the ``Cluster`` extension to be used. +It joins the cluster and an actor subscribes to cluster membership events and logs them. -Try it out: +The ``application.conf`` configuration looks like this: -1. Add the following ``application.conf`` in your project, place it in ``src/main/resources``: - - -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/resources/application.conf#cluster +.. includecode:: ../../../akka-samples/akka-sample-cluster-java/src/main/resources/application.conf To enable cluster capabilities in your Akka project you should, at a minimum, add the :ref:`remoting-java` settings, but with ``akka.cluster.ClusterActorRefProvider``. @@ -42,49 +39,17 @@ The seed nodes are configured contact points for initial, automatic, join of the Note that if you are going to start the nodes on different machines you need to specify the ip-addresses or host names of the machines in ``application.conf`` instead of ``127.0.0.1`` -2. Add the following main program to your project, place it in ``src/main/java``: +An actor that uses the cluster extension may look like this: -.. literalinclude:: ../../../akka-samples/akka-sample-cluster/src/main/java/sample/cluster/simple/japi/SimpleClusterApp.java +.. literalinclude:: ../../../akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/simple/SimpleClusterListener.java :language: java -3. Start the first seed node. Open a terminal window and run (one line):: +The actor registers itself as subscriber of certain cluster events. It gets notified with a snapshot event, ``CurrentClusterState`` +that holds full state information of the cluster. After that it receives events for changes that happen in the cluster. - mvn exec:java -Dexec.mainClass="sample.cluster.simple.japi.SimpleClusterApp" \ - -Dexec.args="2551" - -2551 corresponds to the port of the first seed-nodes element in the configuration. -In the log output you see that the cluster node has been started and changed status to 'Up'. - -4. Start the second seed node. Open another terminal window and run:: - - mvn exec:java -Dexec.mainClass="sample.cluster.simple.japi.SimpleClusterApp" \ - -Dexec.args="2552" - - -2552 corresponds to the port of the second seed-nodes element in the configuration. -In the log output you see that the cluster node has been started and joins the other seed node -and becomes a member of the cluster. Its status changed to 'Up'. - -Switch over to the first terminal window and see in the log output that the member joined. - -5. Start another node. Open a maven session in yet another terminal window and run:: - - mvn exec:java -Dexec.mainClass="sample.cluster.simple.japi.SimpleClusterApp" - -Now you don't need to specify the port number, and it will use a random available port. -It joins one of the configured seed nodes. Look at the log output in the different terminal -windows. - -Start even more nodes in the same way, if you like. - -6. Shut down one of the nodes by pressing 'ctrl-c' in one of the terminal windows. -The other nodes will detect the failure after a while, which you can see in the log -output in the other terminals. - -Look at the source code of the program again. What it does is to create an actor -and register it as subscriber of certain cluster events. It gets notified with -an snapshot event, ``CurrentClusterState`` that holds full state information of -the cluster. After that it receives events for changes that happen in the cluster. +The easiest way to run this example yourself is to download `Typesafe Activator `_ +and open the tutorial named `Akka Cluster Samples with Java `_. +It contains instructions of how to run the SimpleClusterApp. Joining to Seed Nodes ^^^^^^^^^^^^^^^^^^^^^ @@ -237,17 +202,13 @@ backend workers, which performs the transformation job, and sends the result bac the original client. New backend nodes, as well as new frontend nodes, can be added or removed to the cluster dynamically. -In this example the following imports are used: - -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/java/sample/cluster/transformation/japi/TransformationBackend.java#imports - Messages: -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/java/sample/cluster/transformation/japi/TransformationMessages.java#messages +.. includecode:: ../../../akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/transformation/TransformationMessages.java#messages The backend worker that performs the transformation job: -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/java/sample/cluster/transformation/japi/TransformationBackend.java#backend +.. includecode:: ../../../akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/transformation/TransformationBackend.java#backend Note that the ``TransformationBackend`` actor subscribes to cluster events to detect new, potential, frontend nodes, and send them a registration message so that they know @@ -255,36 +216,17 @@ that they can use the backend worker. The frontend that receives user jobs and delegates to one of the registered backend workers: -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/java/sample/cluster/transformation/japi/TransformationFrontend.java#frontend +.. includecode:: ../../../akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/transformation/TransformationFrontend.java#frontend Note that the ``TransformationFrontend`` actor watch the registered backend -to be able to remove it from its list of availble backend workers. +to be able to remove it from its list of available backend workers. Death watch uses the cluster failure detector for nodes in the cluster, i.e. it detects network failures and JVM crashes, in addition to graceful termination of watched actor. -This example is included in ``akka-samples/akka-sample-cluster`` and you can try it by copying the -`source <@github@/akka-samples/akka-sample-cluster>`_ to your -maven project, defined as in :ref:`cluster_simple_example_java`. -Run it by starting nodes in different terminal windows. For example, starting 2 -frontend nodes and 3 backend nodes:: - - mvn exec:java \ - -Dexec.mainClass="sample.cluster.transformation.japi.TransformationFrontendMain" \ - -Dexec.args="2551" - - mvn exec:java \ - -Dexec.mainClass="sample.cluster.transformation.japi.TransformationBackendMain" \ - -Dexec.args="2552" - - mvn exec:java \ - -Dexec.mainClass="sample.cluster.transformation.japi.TransformationBackendMain" - - mvn exec:java \ - -Dexec.mainClass="sample.cluster.transformation.japi.TransformationBackendMain" - - mvn exec:java \ - -Dexec.mainClass="sample.cluster.transformation.japi.TransformationFrontendMain" +The `Typesafe Activator `_ tutorial named +`Akka Cluster Samples with Java `_. +contains the full source code and instructions of how to run the **Worker Dial-in Example**. Node Roles ^^^^^^^^^^ @@ -307,18 +249,18 @@ members have joined, and the cluster has reached a certain size. With a configuration option you can define required number of members before the leader changes member status of 'Joining' members to 'Up'. -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/resources/factorial.conf#min-nr-of-members +.. includecode:: ../../../akka-samples/akka-sample-cluster-java/src/main/resources/factorial.conf#min-nr-of-members In a similar way you can define required number of members of a certain role before the leader changes member status of 'Joining' members to 'Up'. -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/resources/factorial.conf#role-min-nr-of-members +.. includecode:: ../../../akka-samples/akka-sample-cluster-java/src/main/resources/factorial.conf#role-min-nr-of-members You can start the actors in a ``registerOnMemberUp`` callback, which will be invoked when the current member status is changed tp 'Up', i.e. the cluster has at least the defined number of members. -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/java/sample/cluster/factorial/japi/FactorialFrontendMain.java#registerOnUp +.. includecode:: ../../../akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/factorial/FactorialFrontendMain.java#registerOnUp This callback can be used for other things than starting actors. @@ -448,7 +390,7 @@ Router with Group of Routees When using a ``Group`` you must start the routee actors on the cluster member nodes. That is not done by the router. The configuration for a group looks like this: -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSpec.scala#router-lookup-config +.. includecode:: ../../../akka-samples/akka-sample-cluster-java/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSpec.scala#router-lookup-config .. note:: @@ -466,7 +408,7 @@ to a high value will result in new routees added to the router when nodes join t The same type of router could also have been defined in code: -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsService.java#router-lookup-in-code +.. includecode:: ../../../akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/Extra.java#router-lookup-in-code See :ref:`cluster_configuration_java` section for further descriptions of the settings. @@ -482,23 +424,19 @@ to count number of characters in each word to a separate worker, a routee of a r The character count for each word is sent back to an aggregator that calculates the average number of characters per word when all results have been collected. -In this example we use the following imports: - -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsService.java#imports - Messages: -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsMessages.java#messages +.. includecode:: ../../../akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/StatsMessages.java#messages The worker that counts number of characters in each word: -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsWorker.java#worker +.. includecode:: ../../../akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/StatsWorker.java#worker The service that receives text from users and splits it up into words, delegates to workers and aggregates: -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsService.java#service +.. includecode:: ../../../akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/StatsService.java#service -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsAggregator.java#aggregator +.. includecode:: ../../../akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/StatsAggregator.java#aggregator Note, nothing cluster specific so far, just plain actors. @@ -506,31 +444,14 @@ Note, nothing cluster specific so far, just plain actors. All nodes start ``StatsService`` and ``StatsWorker`` actors. Remember, routees are the workers in this case. The router is configured with ``routees.paths``: -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/resources/application.conf#config-router-lookup +.. includecode:: ../../../akka-samples/akka-sample-cluster-java/src/main/resources/stats1.conf#config-router-lookup This means that user requests can be sent to ``StatsService`` on any node and it will use -``StatsWorker`` on all nodes. There can only be one worker per node, but that worker could easily -fan out to local children if more parallelism is needed. +``StatsWorker`` on all nodes. -This example is included in ``akka-samples/akka-sample-cluster`` and you can try it by copying the -`source <@github@/akka-samples/akka-sample-cluster>`_ to your -maven project, defined as in :ref:`cluster_simple_example_java`. -Run it by starting nodes in different terminal windows. For example, starting 3 -service nodes and 1 client:: - - mvn exec:java \ - -Dexec.mainClass="sample.cluster.stats.japi.StatsSampleMain" \ - -Dexec.args="2551" - - mvn exec:java \ - -Dexec.mainClass="sample.cluster.stats.japi.StatsSampleMain" \ - -Dexec.args="2552" - - mvn exec:java \ - -Dexec.mainClass="sample.cluster.stats.japi.StatsSampleMain" - - mvn exec:java \ - -Dexec.mainClass="sample.cluster.stats.japi.StatsSampleMain" +The `Typesafe Activator `_ tutorial named +`Akka Cluster Samples with Java `_. +contains the full source code and instructions of how to run the **Router Example with Group of Routees**. Router with Pool of Remote Deployed Routees ------------------------------------------- @@ -538,7 +459,7 @@ Router with Pool of Remote Deployed Routees When using a ``Pool`` with routees created and deployed on the cluster member nodes the configuration for a router looks like this: -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSingleMasterSpec.scala#router-deploy-config +.. includecode:: ../../../akka-samples/akka-sample-cluster-java/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSingleMasterSpec.scala#router-deploy-config It is possible to limit the deployment of routees to member nodes tagged with a certain role by specifying ``use-role``. @@ -550,7 +471,7 @@ the cluster. The same type of router could also have been defined in code: -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsService.java#router-deploy-in-code +.. includecode:: ../../../akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/Extra.java#router-deploy-in-code See :ref:`cluster_configuration_java` section for further descriptions of the settings. @@ -561,44 +482,23 @@ Let's take a look at how to use a cluster aware router on single master node tha and deploys workers. To keep track of a single master we use the :ref:`cluster-singleton` in the contrib module. The ``ClusterSingletonManager`` is started on each node. -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsSampleOneMasterMain.java#create-singleton-manager +.. includecode:: ../../../akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/StatsSampleOneMasterMain.java#create-singleton-manager We also need an actor on each node that keeps track of where current single master exists and delegates jobs to the ``StatsService``. -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsFacade.java#facade +.. includecode:: ../../../akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/StatsFacade.java#facade The ``StatsFacade`` receives text from users and delegates to the current ``StatsService``, the single master. It listens to cluster events to lookup the ``StatsService`` on the oldest node. All nodes start ``StatsFacade`` and the ``ClusterSingletonManager``. The router is now configured like this: -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/resources/application.conf#config-router-deploy +.. includecode:: ../../../akka-samples/akka-sample-cluster-java/src/main/resources/stats2.conf#config-router-deploy -This example is included in ``akka-samples/akka-sample-cluster`` and you can try it by copying the -`source <@github@/akka-samples/akka-sample-cluster>`_ to your -maven project, defined as in :ref:`cluster_simple_example_java`. Also add the `akka-contrib` dependency -to your pom.xml. - -Run it by starting nodes in different terminal windows. For example, starting 3 -service nodes and 1 client:: - - mvn exec:java \ - -Dexec.mainClass="sample.cluster.stats.japi.StatsSampleOneMasterMain" \ - -Dexec.args="2551" - - mvn exec:java \ - -Dexec.mainClass="sample.cluster.stats.japi.StatsSampleOneMasterMain" \ - -Dexec.args="2552" - - mvn exec:java \ - -Dexec.mainClass="sample.cluster.stats.japi.StatsSampleOneMasterClientMain" - - mvn exec:java \ - -Dexec.mainClass="sample.cluster.stats.japi.StatsSampleOneMasterMain" - - -.. note:: The above example will be simplified when the cluster handles automatic actor partitioning. +The `Typesafe Activator `_ tutorial named +`Akka Cluster Samples with Java `_. +contains the full source code and instructions of how to run the **Router Example with Pool of Remote Deployed Routees**. Cluster Metrics ^^^^^^^^^^^^^^^ @@ -637,63 +537,40 @@ It can be configured to use a specific MetricsSelector to produce the probabilit The collected metrics values are smoothed with `exponential weighted moving average `_. In the :ref:`cluster_configuration_java` you can adjust how quickly past data is decayed compared to new data. -Let's take a look at this router in action. - -In this example the following imports are used: - -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/java/sample/cluster/factorial/japi/FactorialBackend.java#imports +Let's take a look at this router in action. What can be more demanding than calculating factorials? The backend worker that performs the factorial calculation: -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/java/sample/cluster/factorial/japi/FactorialBackend.java#backend +.. includecode:: ../../../akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/factorial/FactorialBackend.java#backend The frontend that receives user jobs and delegates to the backends via the router: -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/java/sample/cluster/factorial/japi/FactorialFrontend.java#frontend +.. includecode:: ../../../akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/factorial/FactorialFrontend.java#frontend As you can see, the router is defined in the same way as other routers, and in this case it is configured as follows: -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/resources/application.conf#adaptive-router +.. includecode:: ../../../akka-samples/akka-sample-cluster-java/src/main/resources/factorial.conf#adaptive-router It is only router type ``adaptive`` and the ``metrics-selector`` that is specific to this router, other things work in the same way as other routers. The same type of router could also have been defined in code: -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/java/sample/cluster/factorial/japi/FactorialFrontend.java#router-lookup-in-code +.. includecode:: ../../../akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/factorial/Extra.java#router-lookup-in-code -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/java/sample/cluster/factorial/japi/FactorialFrontend.java#router-deploy-in-code - -This example is included in ``akka-samples/akka-sample-cluster`` and you can try it by copying the -`source <@github@/akka-samples/akka-sample-cluster>`_ to your -maven project, defined as in :ref:`cluster_simple_example_java`. -Run it by starting nodes in different terminal windows. For example, starting 3 backend nodes and -one frontend:: - - mvn exec:java \ - -Dexec.mainClass="sample.cluster.factorial.japi.FactorialBackendMain" \ - -Dexec.args="2551" - - mvn exec:java \ - -Dexec.mainClass="sample.cluster.factorial.japi.FactorialBackendMain" \ - -Dexec.args="2552" - - mvn exec:java \ - -Dexec.mainClass="sample.cluster.factorial.japi.FactorialBackendMain" - - mvn exec:java \ - -Dexec.mainClass="sample.cluster.factorial.japi.FactorialFrontendMain" - -Press ctrl-c in the terminal window of the frontend to stop the factorial calculations. +.. includecode:: ../../../akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/factorial/Extra.java#router-deploy-in-code +The `Typesafe Activator `_ tutorial named +`Akka Cluster Samples with Java `_. +contains the full source code and instructions of how to run the **Adaptive Load Balancing** sample. Subscribe to Metrics Events --------------------------- It is possible to subscribe to the metrics events directly to implement other functionality. -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/java/sample/cluster/factorial/japi/MetricsListener.java#metrics-listener +.. includecode:: ../../../akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/factorial/MetricsListener.java#metrics-listener Custom Metrics Collector ------------------------ diff --git a/akka-docs/rst/scala/cluster-usage.rst b/akka-docs/rst/scala/cluster-usage.rst index aeba1ec383..72fd751ada 100644 --- a/akka-docs/rst/scala/cluster-usage.rst +++ b/akka-docs/rst/scala/cluster-usage.rst @@ -17,15 +17,12 @@ The Akka cluster is a separate jar file. Make sure that you have the following d A Simple Cluster Example ^^^^^^^^^^^^^^^^^^^^^^^^ -The following small program together with its configuration starts an ``ActorSystem`` -with the Cluster enabled. It joins the cluster and logs some membership events. +The following configuration enables the ``Cluster`` extension to be used. +It joins the cluster and an actor subscribes to cluster membership events and logs them. -Try it out: +The ``application.conf`` configuration looks like this: -1. Add the following ``application.conf`` in your project, place it in ``src/main/resources``: - - -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/resources/application.conf#cluster +.. includecode:: ../../../akka-samples/akka-sample-cluster-scala/src/main/resources/application.conf To enable cluster capabilities in your Akka project you should, at a minimum, add the :ref:`remoting-scala` settings, but with ``akka.cluster.ClusterActorRefProvider``. @@ -36,48 +33,17 @@ The seed nodes are configured contact points for initial, automatic, join of the Note that if you are going to start the nodes on different machines you need to specify the ip-addresses or host names of the machines in ``application.conf`` instead of ``127.0.0.1`` -2. Add the following main program to your project, place it in ``src/main/scala``: +An actor that uses the cluster extension may look like this: -.. literalinclude:: ../../../akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/simple/SimpleClusterApp.scala +.. literalinclude:: ../../../akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/simple/SimpleClusterListener.scala :language: scala +The actor registers itself as subscriber of certain cluster events. It gets notified with a snapshot event, ``CurrentClusterState`` +that holds full state information of the cluster. After that it receives events for changes that happen in the cluster. -3. Start the first seed node. Open a sbt session in one terminal window and run:: - - run-main sample.cluster.simple.SimpleClusterApp 2551 - -2551 corresponds to the port of the first seed-nodes element in the configuration. -In the log output you see that the cluster node has been started and changed status to 'Up'. - -4. Start the second seed node. Open a sbt session in another terminal window and run:: - - run-main sample.cluster.simple.SimpleClusterApp 2552 - - -2552 corresponds to the port of the second seed-nodes element in the configuration. -In the log output you see that the cluster node has been started and joins the other seed node -and becomes a member of the cluster. Its status changed to 'Up'. - -Switch over to the first terminal window and see in the log output that the member joined. - -5. Start another node. Open a sbt session in yet another terminal window and run:: - - run-main sample.cluster.simple.SimpleClusterApp - -Now you don't need to specify the port number, and it will use a random available port. -It joins one of the configured seed nodes. Look at the log output in the different terminal -windows. - -Start even more nodes in the same way, if you like. - -6. Shut down one of the nodes by pressing 'ctrl-c' in one of the terminal windows. -The other nodes will detect the failure after a while, which you can see in the log -output in the other terminals. - -Look at the source code of the program again. What it does is to create an actor -and register it as subscriber of certain cluster events. It gets notified with -an snapshot event, ``CurrentClusterState`` that holds full state information of -the cluster. After that it receives events for changes that happen in the cluster. +The easiest way to run this example yourself is to download `Typesafe Activator `_ +and open the tutorial named `Akka Cluster Samples with Scala `_. +It contains instructions of how to run the SimpleClusterApp. Joining to Seed Nodes ^^^^^^^^^^^^^^^^^^^^^ @@ -230,17 +196,13 @@ backend workers, which performs the transformation job, and sends the result bac the original client. New backend nodes, as well as new frontend nodes, can be added or removed to the cluster dynamically. -In this example the following imports are used: - -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/transformation/TransformationSample.scala#imports - Messages: -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/transformation/TransformationSample.scala#messages +.. includecode:: ../../../akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/transformation/TransformationMessages.scala#messages The backend worker that performs the transformation job: -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/transformation/TransformationSample.scala#backend +.. includecode:: ../../../akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/transformation/TransformationBackend.scala#backend Note that the ``TransformationBackend`` actor subscribes to cluster events to detect new, potential, frontend nodes, and send them a registration message so that they know @@ -248,31 +210,17 @@ that they can use the backend worker. The frontend that receives user jobs and delegates to one of the registered backend workers: -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/transformation/TransformationSample.scala#frontend +.. includecode:: ../../../akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/transformation/TransformationFrontend.scala#frontend Note that the ``TransformationFrontend`` actor watch the registered backend -to be able to remove it from its list of availble backend workers. +to be able to remove it from its list of available backend workers. Death watch uses the cluster failure detector for nodes in the cluster, i.e. it detects network failures and JVM crashes, in addition to graceful termination of watched actor. -This example is included in ``akka-samples/akka-sample-cluster`` -and you can try by starting nodes in different terminal windows. For example, starting 2 -frontend nodes and 3 backend nodes:: - - sbt - - project akka-sample-cluster - - run-main sample.cluster.transformation.TransformationFrontend 2551 - - run-main sample.cluster.transformation.TransformationBackend 2552 - - run-main sample.cluster.transformation.TransformationBackend - - run-main sample.cluster.transformation.TransformationBackend - - run-main sample.cluster.transformation.TransformationFrontend +The `Typesafe Activator `_ tutorial named +`Akka Cluster Samples with Scala `_. +contains the full source code and instructions of how to run the **Worker Dial-in Example**. Node Roles ^^^^^^^^^^ @@ -295,18 +243,18 @@ members have joined, and the cluster has reached a certain size. With a configuration option you can define required number of members before the leader changes member status of 'Joining' members to 'Up'. -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/resources/factorial.conf#min-nr-of-members +.. includecode:: ../../../akka-samples/akka-sample-cluster-scala/src/main/resources/factorial.conf#min-nr-of-members In a similar way you can define required number of members of a certain role before the leader changes member status of 'Joining' members to 'Up'. -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/resources/factorial.conf#role-min-nr-of-members +.. includecode:: ../../../akka-samples/akka-sample-cluster-scala/src/main/resources/factorial.conf#role-min-nr-of-members You can start the actors in a ``registerOnMemberUp`` callback, which will be invoked when the current member status is changed tp 'Up', i.e. the cluster has at least the defined number of members. -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/factorial/FactorialSample.scala#registerOnUp +.. includecode:: ../../../akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/factorial/FactorialFrontend.scala#registerOnUp This callback can be used for other things than starting actors. @@ -439,7 +387,7 @@ Router with Group of Routees When using a ``Group`` you must start the routee actors on the cluster member nodes. That is not done by the router. The configuration for a group looks like this: -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSpec.scala#router-lookup-config +.. includecode:: ../../../akka-samples/akka-sample-cluster-scala/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSpec.scala#router-lookup-config .. note:: @@ -457,7 +405,7 @@ to a high value will result in new routees added to the router when nodes join t The same type of router could also have been defined in code: -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/stats/StatsSample.scala#router-lookup-in-code +.. includecode:: ../../../akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/stats/Extra.scala#router-lookup-in-code See :ref:`cluster_configuration_scala` section for further descriptions of the settings. @@ -473,21 +421,17 @@ to count number of characters in each word to a separate worker, a routee of a r The character count for each word is sent back to an aggregator that calculates the average number of characters per word when all results have been collected. -In this example we use the following imports: - -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/stats/StatsSample.scala#imports - Messages: -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/stats/StatsSample.scala#messages +.. includecode:: ../../../akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/stats/StatsMessages.scala#messages The worker that counts number of characters in each word: -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/stats/StatsSample.scala#worker +.. includecode:: ../../../akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/stats/StatsWorker.scala#worker The service that receives text from users and splits it up into words, delegates to workers and aggregates: -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/stats/StatsSample.scala#service +.. includecode:: ../../../akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/stats/StatsService.scala#service Note, nothing cluster specific so far, just plain actors. @@ -495,27 +439,14 @@ Note, nothing cluster specific so far, just plain actors. All nodes start ``StatsService`` and ``StatsWorker`` actors. Remember, routees are the workers in this case. The router is configured with ``routees.paths``: -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/resources/application.conf#config-router-lookup +.. includecode:: ../../../akka-samples/akka-sample-cluster-scala/src/main/resources/stats1.conf#config-router-lookup This means that user requests can be sent to ``StatsService`` on any node and it will use -``StatsWorker`` on all nodes. There can only be one worker per node, but that worker could easily -fan out to local children if more parallelism is needed. +``StatsWorker`` on all nodes. -This example is included in ``akka-samples/akka-sample-cluster`` -and you can try by starting nodes in different terminal windows. For example, starting 3 -service nodes and 1 client:: - - sbt - - project akka-sample-cluster - - run-main sample.cluster.stats.StatsSample 2551 - - run-main sample.cluster.stats.StatsSample 2552 - - run-main sample.cluster.stats.StatsSampleClient - - run-main sample.cluster.stats.StatsSample +The `Typesafe Activator `_ tutorial named +`Akka Cluster Samples with Scala `_. +contains the full source code and instructions of how to run the **Router Example with Group of Routees**. Router with Pool of Remote Deployed Routees ------------------------------------------- @@ -523,7 +454,7 @@ Router with Pool of Remote Deployed Routees When using a ``Pool`` with routees created and deployed on the cluster member nodes the configuration for a router looks like this: -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSingleMasterSpec.scala#router-deploy-config +.. includecode:: ../../../akka-samples/akka-sample-cluster-scala/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSingleMasterSpec.scala#router-deploy-config It is possible to limit the deployment of routees to member nodes tagged with a certain role by specifying ``use-role``. @@ -535,7 +466,7 @@ the cluster. The same type of router could also have been defined in code: -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/stats/StatsSample.scala#router-deploy-in-code +.. includecode:: ../../../akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/stats/Extra.scala#router-deploy-in-code See :ref:`cluster_configuration_scala` section for further descriptions of the settings. @@ -546,35 +477,23 @@ Let's take a look at how to use a cluster aware router on single master node tha and deploys workers. To keep track of a single master we use the :ref:`cluster-singleton` in the contrib module. The ``ClusterSingletonManager`` is started on each node. -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/stats/StatsSample.scala#create-singleton-manager +.. includecode:: ../../../akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/stats/StatsSampleOneMaster.scala#create-singleton-manager We also need an actor on each node that keeps track of where current single master exists and delegates jobs to the ``StatsService``. -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/stats/StatsSample.scala#facade +.. includecode:: ../../../akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/stats/StatsFacade.scala#facade The ``StatsFacade`` receives text from users and delegates to the current ``StatsService``, the single master. It listens to cluster events to lookup the ``StatsService`` on the oldest node. All nodes start ``StatsFacade`` and the ``ClusterSingletonManager``. The router is now configured like this: -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/resources/application.conf#config-router-deploy - - -This example is included in ``akka-samples/akka-sample-cluster`` -and you can try by starting nodes in different terminal windows. For example, starting 3 -service nodes and 1 client:: - - run-main sample.cluster.stats.StatsSampleOneMaster 2551 - - run-main sample.cluster.stats.StatsSampleOneMaster 2552 - - run-main sample.cluster.stats.StatsSampleOneMasterClient - - run-main sample.cluster.stats.StatsSampleOneMaster - -.. note:: The above example will be simplified when the cluster handles automatic actor partitioning. +.. includecode:: ../../../akka-samples/akka-sample-cluster-scala/src/main/resources/stats2.conf#config-router-deploy +The `Typesafe Activator `_ tutorial named +`Akka Cluster Samples with Scala `_. +contains the full source code and instructions of how to run the **Router Example with Pool of Remote Deployed Routees**. Cluster Metrics ^^^^^^^^^^^^^^^ @@ -609,57 +528,40 @@ It can be configured to use a specific MetricsSelector to produce the probabilit The collected metrics values are smoothed with `exponential weighted moving average `_. In the :ref:`cluster_configuration_scala` you can adjust how quickly past data is decayed compared to new data. -Let's take a look at this router in action. - -In this example the following imports are used: - -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/factorial/FactorialSample.scala#imports +Let's take a look at this router in action. What can be more demanding than calculating factorials? The backend worker that performs the factorial calculation: -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/factorial/FactorialSample.scala#backend +.. includecode:: ../../../akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/factorial/FactorialBackend.scala#backend The frontend that receives user jobs and delegates to the backends via the router: -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/factorial/FactorialSample.scala#frontend +.. includecode:: ../../../akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/factorial/FactorialFrontend.scala#frontend As you can see, the router is defined in the same way as other routers, and in this case it is configured as follows: -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/resources/application.conf#adaptive-router +.. includecode:: ../../../akka-samples/akka-sample-cluster-scala/src/main/resources/factorial.conf#adaptive-router It is only router type ``adaptive`` and the ``metrics-selector`` that is specific to this router, other things work in the same way as other routers. The same type of router could also have been defined in code: -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/factorial/FactorialSample.scala#router-lookup-in-code +.. includecode:: ../../../akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/factorial/Extra.scala#router-lookup-in-code -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/factorial/FactorialSample.scala#router-deploy-in-code +.. includecode:: ../../../akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/factorial/Extra.scala#router-deploy-in-code -This example is included in ``akka-samples/akka-sample-cluster`` -and you can try by starting nodes in different terminal windows. For example, starting 3 backend nodes and one frontend:: - - sbt - - project akka-sample-cluster - - run-main sample.cluster.factorial.FactorialBackend 2551 - - run-main sample.cluster.factorial.FactorialBackend 2552 - - run-main sample.cluster.factorial.FactorialBackend - - run-main sample.cluster.factorial.FactorialFrontend - -Press ctrl-c in the terminal window of the frontend to stop the factorial calculations. +The `Typesafe Activator `_ tutorial named +`Akka Cluster Samples with Scala `_. +contains the full source code and instructions of how to run the **Adaptive Load Balancing** sample. Subscribe to Metrics Events --------------------------- It is possible to subscribe to the metrics events directly to implement other functionality. -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/factorial/FactorialSample.scala#metrics-listener +.. includecode:: ../../../akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/factorial/MetricsListener.scala#metrics-listener Custom Metrics Collector ------------------------ @@ -679,14 +581,14 @@ add the ``sbt-multi-jvm`` plugin and the dependency to ``akka-multi-node-testkit First, as described in :ref:`multi-node-testing`, we need some scaffolding to configure the ``MultiNodeSpec``. Define the participating roles and their :ref:`cluster_configuration_scala` in an object extending ``MultiNodeConfig``: -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSpec.scala +.. includecode:: ../../../akka-samples/akka-sample-cluster-scala/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSpec.scala :include: MultiNodeConfig :exclude: router-lookup-config Define one concrete test class for each role/node. These will be instantiated on the different nodes (JVMs). They can be implemented differently, but often they are the same and extend an abstract test class, as illustrated here. -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSpec.scala#concrete-tests +.. includecode:: ../../../akka-samples/akka-sample-cluster-scala/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSpec.scala#concrete-tests Note the naming convention of these classes. The name of the classes must end with ``MultiJvmNode1``, ``MultiJvmNode2`` and so on. It is possible to define another suffix to be used by the ``sbt-multi-jvm``, but the default should be @@ -694,18 +596,18 @@ fine in most cases. Then the abstract ``MultiNodeSpec``, which takes the ``MultiNodeConfig`` as constructor parameter. -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSpec.scala#abstract-test +.. includecode:: ../../../akka-samples/akka-sample-cluster-scala/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSpec.scala#abstract-test Most of this can of course be extracted to a separate trait to avoid repeating this in all your tests. Typically you begin your test by starting up the cluster and let the members join, and create some actors. That can be done like this: -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSpec.scala#startup-cluster +.. includecode:: ../../../akka-samples/akka-sample-cluster-scala/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSpec.scala#startup-cluster From the test you interact with the cluster using the ``Cluster`` extension, e.g. ``join``. -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSpec.scala#join +.. includecode:: ../../../akka-samples/akka-sample-cluster-scala/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSpec.scala#join Notice how the `testActor` from :ref:`testkit ` is added as :ref:`subscriber ` to cluster changes and then waiting for certain events, such as in this case all members becoming 'Up'. @@ -713,7 +615,7 @@ to cluster changes and then waiting for certain events, such as in this case all The above code was running for all roles (JVMs). ``runOn`` is a convenient utility to declare that a certain block of code should only run for a specific role. -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSpec.scala#test-statsService +.. includecode:: ../../../akka-samples/akka-sample-cluster-scala/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSpec.scala#test-statsService Once again we take advantage of the facilities in :ref:`testkit ` to verify expected behavior. Here using ``testActor`` as sender (via ``ImplicitSender``) and verifing the reply with ``expectMsgPF``. @@ -721,7 +623,7 @@ Here using ``testActor`` as sender (via ``ImplicitSender``) and verifing the rep In the above code you can see ``node(third)``, which is useful facility to get the root actor reference of the actor system for a specific role. This can also be used to grab the ``akka.actor.Address`` of that node. -.. includecode:: ../../../akka-samples/akka-sample-cluster/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSpec.scala#addresses +.. includecode:: ../../../akka-samples/akka-sample-cluster-scala/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSpec.scala#addresses .. _cluster_jmx_scala: diff --git a/akka-samples/akka-sample-cluster-java/.gitignore b/akka-samples/akka-sample-cluster-java/.gitignore new file mode 100644 index 0000000000..660c959e44 --- /dev/null +++ b/akka-samples/akka-sample-cluster-java/.gitignore @@ -0,0 +1,17 @@ +*# +*.iml +*.ipr +*.iws +*.pyc +*.tm.epoch +*.vim +*-shim.sbt +.idea/ +/project/plugins/project +project/boot +target/ +/logs +.cache +.classpath +.project +.settings \ No newline at end of file diff --git a/akka-samples/akka-sample-cluster-java/LICENSE b/akka-samples/akka-sample-cluster-java/LICENSE new file mode 100644 index 0000000000..a02154466b --- /dev/null +++ b/akka-samples/akka-sample-cluster-java/LICENSE @@ -0,0 +1,13 @@ +Copyright 2013 Typesafe, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/akka-samples/akka-sample-cluster-java/activator.properties b/akka-samples/akka-sample-cluster-java/activator.properties new file mode 100644 index 0000000000..54232a778b --- /dev/null +++ b/akka-samples/akka-sample-cluster-java/activator.properties @@ -0,0 +1,4 @@ +name=akka-sample-cluster-java +title=Akka Cluster Samples with Java +description=Akka Cluster Samples with Java +tags=akka,cluster,java,sample diff --git a/akka-samples/akka-sample-cluster-java/project/Build.scala b/akka-samples/akka-sample-cluster-java/project/Build.scala new file mode 100644 index 0000000000..2508e85041 --- /dev/null +++ b/akka-samples/akka-sample-cluster-java/project/Build.scala @@ -0,0 +1,32 @@ +import sbt._ +import sbt.Keys._ +import com.typesafe.sbt.SbtMultiJvm +import com.typesafe.sbt.SbtMultiJvm.MultiJvmKeys.MultiJvm + +object AkkaSampleClusterBuild extends Build { + + val akkaVersion = "2.3-SNAPSHOT" + + lazy val akkaSampleCluster = Project( + id = "akka-sample-cluster-java", + base = file("."), + settings = Project.defaultSettings ++ SbtMultiJvm.multiJvmSettings ++ Seq( + name := "akka-sample-cluster-java", + version := "1.0", + scalaVersion := "2.10.3", + scalacOptions in Compile ++= Seq("-encoding", "UTF-8", "-target:jvm-1.6", "-deprecation", "-feature", "-unchecked", "-Xlog-reflective-calls", "-Xlint"), + javacOptions in Compile ++= Seq("-source", "1.6", "-target", "1.6", "-Xlint:unchecked", "-Xlint:deprecation"), + libraryDependencies ++= Seq( + "com.typesafe.akka" %% "akka-cluster" % akkaVersion, + "com.typesafe.akka" %% "akka-contrib" % akkaVersion, + "com.typesafe.akka" %% "akka-multi-node-testkit" % akkaVersion, + "org.scalatest" %% "scalatest" % "2.0" % "test", + "org.fusesource" % "sigar" % "1.6.4"), + javaOptions in run ++= Seq( + "-Djava.library.path=./sigar", + "-Xms128m", "-Xmx1024m"), + Keys.fork in run := true, + mainClass in (Compile, run) := Some("sample.cluster.simple.SimpleClusterApp") + ) + ) configs (MultiJvm) +} diff --git a/akka-samples/akka-sample-cluster-java/project/build.properties b/akka-samples/akka-sample-cluster-java/project/build.properties new file mode 100644 index 0000000000..0974fce44d --- /dev/null +++ b/akka-samples/akka-sample-cluster-java/project/build.properties @@ -0,0 +1 @@ +sbt.version=0.13.0 diff --git a/akka-samples/akka-sample-cluster-java/project/plugins.sbt b/akka-samples/akka-sample-cluster-java/project/plugins.sbt new file mode 100644 index 0000000000..c3e7d797de --- /dev/null +++ b/akka-samples/akka-sample-cluster-java/project/plugins.sbt @@ -0,0 +1,4 @@ + +resolvers += Classpaths.typesafeResolver + +addSbtPlugin("com.typesafe.sbt" % "sbt-multi-jvm" % "0.3.8") diff --git a/akka-samples/akka-sample-cluster/sigar/libsigar-amd64-freebsd-6.so b/akka-samples/akka-sample-cluster-java/sigar/libsigar-amd64-freebsd-6.so similarity index 100% rename from akka-samples/akka-sample-cluster/sigar/libsigar-amd64-freebsd-6.so rename to akka-samples/akka-sample-cluster-java/sigar/libsigar-amd64-freebsd-6.so diff --git a/akka-samples/akka-sample-cluster/sigar/libsigar-amd64-linux.so b/akka-samples/akka-sample-cluster-java/sigar/libsigar-amd64-linux.so similarity index 100% rename from akka-samples/akka-sample-cluster/sigar/libsigar-amd64-linux.so rename to akka-samples/akka-sample-cluster-java/sigar/libsigar-amd64-linux.so diff --git a/akka-samples/akka-sample-cluster/sigar/libsigar-amd64-solaris.so b/akka-samples/akka-sample-cluster-java/sigar/libsigar-amd64-solaris.so similarity index 100% rename from akka-samples/akka-sample-cluster/sigar/libsigar-amd64-solaris.so rename to akka-samples/akka-sample-cluster-java/sigar/libsigar-amd64-solaris.so diff --git a/akka-samples/akka-sample-cluster/sigar/libsigar-ia64-hpux-11.sl b/akka-samples/akka-sample-cluster-java/sigar/libsigar-ia64-hpux-11.sl similarity index 100% rename from akka-samples/akka-sample-cluster/sigar/libsigar-ia64-hpux-11.sl rename to akka-samples/akka-sample-cluster-java/sigar/libsigar-ia64-hpux-11.sl diff --git a/akka-samples/akka-sample-cluster/sigar/libsigar-ia64-linux.so b/akka-samples/akka-sample-cluster-java/sigar/libsigar-ia64-linux.so similarity index 100% rename from akka-samples/akka-sample-cluster/sigar/libsigar-ia64-linux.so rename to akka-samples/akka-sample-cluster-java/sigar/libsigar-ia64-linux.so diff --git a/akka-samples/akka-sample-cluster/sigar/libsigar-pa-hpux-11.sl b/akka-samples/akka-sample-cluster-java/sigar/libsigar-pa-hpux-11.sl similarity index 100% rename from akka-samples/akka-sample-cluster/sigar/libsigar-pa-hpux-11.sl rename to akka-samples/akka-sample-cluster-java/sigar/libsigar-pa-hpux-11.sl diff --git a/akka-samples/akka-sample-cluster/sigar/libsigar-ppc-aix-5.so b/akka-samples/akka-sample-cluster-java/sigar/libsigar-ppc-aix-5.so similarity index 100% rename from akka-samples/akka-sample-cluster/sigar/libsigar-ppc-aix-5.so rename to akka-samples/akka-sample-cluster-java/sigar/libsigar-ppc-aix-5.so diff --git a/akka-samples/akka-sample-cluster/sigar/libsigar-ppc-linux.so b/akka-samples/akka-sample-cluster-java/sigar/libsigar-ppc-linux.so similarity index 100% rename from akka-samples/akka-sample-cluster/sigar/libsigar-ppc-linux.so rename to akka-samples/akka-sample-cluster-java/sigar/libsigar-ppc-linux.so diff --git a/akka-samples/akka-sample-cluster/sigar/libsigar-ppc64-aix-5.so b/akka-samples/akka-sample-cluster-java/sigar/libsigar-ppc64-aix-5.so similarity index 100% rename from akka-samples/akka-sample-cluster/sigar/libsigar-ppc64-aix-5.so rename to akka-samples/akka-sample-cluster-java/sigar/libsigar-ppc64-aix-5.so diff --git a/akka-samples/akka-sample-cluster/sigar/libsigar-ppc64-linux.so b/akka-samples/akka-sample-cluster-java/sigar/libsigar-ppc64-linux.so similarity index 100% rename from akka-samples/akka-sample-cluster/sigar/libsigar-ppc64-linux.so rename to akka-samples/akka-sample-cluster-java/sigar/libsigar-ppc64-linux.so diff --git a/akka-samples/akka-sample-cluster/sigar/libsigar-s390x-linux.so b/akka-samples/akka-sample-cluster-java/sigar/libsigar-s390x-linux.so similarity index 100% rename from akka-samples/akka-sample-cluster/sigar/libsigar-s390x-linux.so rename to akka-samples/akka-sample-cluster-java/sigar/libsigar-s390x-linux.so diff --git a/akka-samples/akka-sample-cluster/sigar/libsigar-sparc-solaris.so b/akka-samples/akka-sample-cluster-java/sigar/libsigar-sparc-solaris.so similarity index 100% rename from akka-samples/akka-sample-cluster/sigar/libsigar-sparc-solaris.so rename to akka-samples/akka-sample-cluster-java/sigar/libsigar-sparc-solaris.so diff --git a/akka-samples/akka-sample-cluster/sigar/libsigar-sparc64-solaris.so b/akka-samples/akka-sample-cluster-java/sigar/libsigar-sparc64-solaris.so similarity index 100% rename from akka-samples/akka-sample-cluster/sigar/libsigar-sparc64-solaris.so rename to akka-samples/akka-sample-cluster-java/sigar/libsigar-sparc64-solaris.so diff --git a/akka-samples/akka-sample-cluster/sigar/libsigar-universal-macosx.dylib b/akka-samples/akka-sample-cluster-java/sigar/libsigar-universal-macosx.dylib similarity index 100% rename from akka-samples/akka-sample-cluster/sigar/libsigar-universal-macosx.dylib rename to akka-samples/akka-sample-cluster-java/sigar/libsigar-universal-macosx.dylib diff --git a/akka-samples/akka-sample-cluster/sigar/libsigar-universal64-macosx.dylib b/akka-samples/akka-sample-cluster-java/sigar/libsigar-universal64-macosx.dylib similarity index 100% rename from akka-samples/akka-sample-cluster/sigar/libsigar-universal64-macosx.dylib rename to akka-samples/akka-sample-cluster-java/sigar/libsigar-universal64-macosx.dylib diff --git a/akka-samples/akka-sample-cluster/sigar/libsigar-x86-freebsd-5.so b/akka-samples/akka-sample-cluster-java/sigar/libsigar-x86-freebsd-5.so similarity index 100% rename from akka-samples/akka-sample-cluster/sigar/libsigar-x86-freebsd-5.so rename to akka-samples/akka-sample-cluster-java/sigar/libsigar-x86-freebsd-5.so diff --git a/akka-samples/akka-sample-cluster/sigar/libsigar-x86-freebsd-6.so b/akka-samples/akka-sample-cluster-java/sigar/libsigar-x86-freebsd-6.so similarity index 100% rename from akka-samples/akka-sample-cluster/sigar/libsigar-x86-freebsd-6.so rename to akka-samples/akka-sample-cluster-java/sigar/libsigar-x86-freebsd-6.so diff --git a/akka-samples/akka-sample-cluster/sigar/libsigar-x86-linux.so b/akka-samples/akka-sample-cluster-java/sigar/libsigar-x86-linux.so similarity index 100% rename from akka-samples/akka-sample-cluster/sigar/libsigar-x86-linux.so rename to akka-samples/akka-sample-cluster-java/sigar/libsigar-x86-linux.so diff --git a/akka-samples/akka-sample-cluster/sigar/libsigar-x86-solaris.so b/akka-samples/akka-sample-cluster-java/sigar/libsigar-x86-solaris.so similarity index 100% rename from akka-samples/akka-sample-cluster/sigar/libsigar-x86-solaris.so rename to akka-samples/akka-sample-cluster-java/sigar/libsigar-x86-solaris.so diff --git a/akka-samples/akka-sample-cluster/sigar/sigar-amd64-winnt.dll b/akka-samples/akka-sample-cluster-java/sigar/sigar-amd64-winnt.dll similarity index 100% rename from akka-samples/akka-sample-cluster/sigar/sigar-amd64-winnt.dll rename to akka-samples/akka-sample-cluster-java/sigar/sigar-amd64-winnt.dll diff --git a/akka-samples/akka-sample-cluster/sigar/sigar-x86-winnt.dll b/akka-samples/akka-sample-cluster-java/sigar/sigar-x86-winnt.dll similarity index 100% rename from akka-samples/akka-sample-cluster/sigar/sigar-x86-winnt.dll rename to akka-samples/akka-sample-cluster-java/sigar/sigar-x86-winnt.dll diff --git a/akka-samples/akka-sample-cluster/sigar/sigar-x86-winnt.lib b/akka-samples/akka-sample-cluster-java/sigar/sigar-x86-winnt.lib similarity index 100% rename from akka-samples/akka-sample-cluster/sigar/sigar-x86-winnt.lib rename to akka-samples/akka-sample-cluster-java/sigar/sigar-x86-winnt.lib diff --git a/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/factorial/Extra.java b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/factorial/Extra.java new file mode 100644 index 0000000000..1a1bfc1640 --- /dev/null +++ b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/factorial/Extra.java @@ -0,0 +1,50 @@ +/** + * Copyright (C) 2011 Typesafe + */ +package sample.cluster.factorial; + +import java.util.Arrays; +import java.util.Collections; + +import akka.actor.ActorRef; +import akka.actor.Props; +import akka.actor.UntypedActor; +import akka.cluster.routing.AdaptiveLoadBalancingGroup; +import akka.cluster.routing.AdaptiveLoadBalancingPool; +import akka.cluster.routing.ClusterRouterGroup; +import akka.cluster.routing.ClusterRouterGroupSettings; +import akka.cluster.routing.ClusterRouterPool; +import akka.cluster.routing.ClusterRouterPoolSettings; +import akka.cluster.routing.HeapMetricsSelector; +import akka.cluster.routing.SystemLoadAverageMetricsSelector; + +//not used, only for documentation +abstract class FactorialFrontend2 extends UntypedActor { + //#router-lookup-in-code + int totalInstances = 100; + Iterable routeesPaths = Arrays.asList("/user/factorialBackend", ""); + boolean allowLocalRoutees = true; + String useRole = "backend"; + ActorRef backend = getContext().actorOf( + new ClusterRouterGroup(new AdaptiveLoadBalancingGroup( + HeapMetricsSelector.getInstance(), Collections. emptyList()), + new ClusterRouterGroupSettings(totalInstances, routeesPaths, + allowLocalRoutees, useRole)).props(), "factorialBackendRouter2"); + //#router-lookup-in-code +} + +//not used, only for documentation +abstract class FactorialFrontend3 extends UntypedActor { + //#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 +} diff --git a/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/factorial/FactorialApp.java b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/factorial/FactorialApp.java new file mode 100644 index 0000000000..9c8c6ddeef --- /dev/null +++ b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/factorial/FactorialApp.java @@ -0,0 +1,12 @@ +package sample.cluster.factorial; + +public class FactorialApp { + + public static void main(String[] args) { + // starting 3 backend nodes and 1 frontend node + FactorialBackendMain.main(new String[] { "2551" }); + FactorialBackendMain.main(new String[] { "2552" }); + FactorialBackendMain.main(new String[0]); + FactorialFrontendMain.main(new String[0]); + } +} diff --git a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/factorial/japi/FactorialBackend.java b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/factorial/FactorialBackend.java similarity index 77% rename from akka-samples/akka-sample-cluster/src/main/java/sample/cluster/factorial/japi/FactorialBackend.java rename to akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/factorial/FactorialBackend.java index b1f813f684..028de5879e 100644 --- a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/factorial/japi/FactorialBackend.java +++ b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/factorial/FactorialBackend.java @@ -1,6 +1,5 @@ -package sample.cluster.factorial.japi; +package sample.cluster.factorial; -//#imports import java.math.BigInteger; import java.util.concurrent.Callable; import scala.concurrent.Future; @@ -8,7 +7,6 @@ import akka.actor.UntypedActor; import akka.dispatch.Mapper; import static akka.dispatch.Futures.future; import static akka.pattern.Patterns.pipe; -//#imports //#backend public class FactorialBackend extends UntypedActor { @@ -24,11 +22,11 @@ public class FactorialBackend extends UntypedActor { }, getContext().dispatcher()); Future result = f.map( - new Mapper() { - public FactorialResult apply(BigInteger factorial) { - return new FactorialResult(n, factorial); - } - }, getContext().dispatcher()); + new Mapper() { + public FactorialResult apply(BigInteger factorial) { + return new FactorialResult(n, factorial); + } + }, getContext().dispatcher()); pipe(result, getContext().dispatcher()).to(getSender()); diff --git a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/factorial/japi/FactorialBackendMain.java b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/factorial/FactorialBackendMain.java similarity index 53% rename from akka-samples/akka-sample-cluster/src/main/java/sample/cluster/factorial/japi/FactorialBackendMain.java rename to akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/factorial/FactorialBackendMain.java index 73351f2317..6c322a6aa1 100644 --- a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/factorial/japi/FactorialBackendMain.java +++ b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/factorial/FactorialBackendMain.java @@ -1,4 +1,4 @@ -package sample.cluster.factorial.japi; +package sample.cluster.factorial; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; @@ -7,14 +7,12 @@ import akka.actor.Props; public class FactorialBackendMain { - public static void main(String[] args) throws Exception { + public static void main(String[] args) { // Override the configuration of the port when specified as program argument - final Config config = - (args.length > 0 ? - ConfigFactory.parseString(String.format("akka.remote.netty.tcp.port=%s", args[0])) : - ConfigFactory.empty()). - withFallback(ConfigFactory.parseString("akka.cluster.roles = [backend]")). - withFallback(ConfigFactory.load("factorial")); + final String port = args.length > 0 ? args[0] : "0"; + final Config config = ConfigFactory.parseString("akka.remote.netty.tcp.port=" + port). + withFallback(ConfigFactory.parseString("akka.cluster.roles = [backend]")). + withFallback(ConfigFactory.load("factorial")); ActorSystem system = ActorSystem.create("ClusterSystem", config); diff --git a/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/factorial/FactorialFrontend.java b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/factorial/FactorialFrontend.java new file mode 100644 index 0000000000..b1848a964f --- /dev/null +++ b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/factorial/FactorialFrontend.java @@ -0,0 +1,64 @@ +package sample.cluster.factorial; + +import java.util.concurrent.TimeUnit; +import scala.concurrent.duration.Duration; +import akka.actor.ActorRef; +import akka.actor.ReceiveTimeout; +import akka.actor.UntypedActor; +import akka.event.Logging; +import akka.event.LoggingAdapter; +import akka.routing.FromConfig; + +//#frontend +public class FactorialFrontend extends UntypedActor { + final int upToN; + final boolean repeat; + + LoggingAdapter log = Logging.getLogger(getContext().system(), 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 void onReceive(Object message) { + if (message instanceof FactorialResult) { + FactorialResult result = (FactorialResult) message; + if (result.n == upToN) { + log.debug("{}! = {}", result.n, result.factorial); + if (repeat) + sendJobs(); + else + getContext().stop(getSelf()); + } + + } else if (message instanceof ReceiveTimeout) { + log.info("Timeout"); + sendJobs(); + + } else { + unhandled(message); + } + } + + void sendJobs() { + log.info("Starting batch of factorials up to [{}]", upToN); + for (int n = 1; n <= upToN; n++) { + backend.tell(n, getSelf()); + } + } + +} + +//#frontend + diff --git a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/factorial/japi/FactorialFrontendMain.java b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/factorial/FactorialFrontendMain.java similarity index 53% rename from akka-samples/akka-sample-cluster/src/main/java/sample/cluster/factorial/japi/FactorialFrontendMain.java rename to akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/factorial/FactorialFrontendMain.java index 216c58bf10..492dbc5c95 100644 --- a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/factorial/japi/FactorialFrontendMain.java +++ b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/factorial/FactorialFrontendMain.java @@ -1,4 +1,4 @@ -package sample.cluster.factorial.japi; +package sample.cluster.factorial; import com.typesafe.config.Config; import com.typesafe.config.ConfigFactory; @@ -8,20 +8,22 @@ import akka.cluster.Cluster; public class FactorialFrontendMain { - public static void main(String[] args) throws Exception { - final int upToN = (args.length == 0 ? 200 : Integer.valueOf(args[0])); + public static void main(String[] args) { + final int upToN = 200; - final Config config = ConfigFactory.parseString("akka.cluster.roles = [frontend]"). - withFallback(ConfigFactory.load("factorial")); + 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."); + 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"); + system.actorOf(Props.create(FactorialFrontend.class, upToN, true), + "factorialFrontend"); } }); //#registerOnUp diff --git a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/factorial/japi/FactorialResult.java b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/factorial/FactorialResult.java similarity index 87% rename from akka-samples/akka-sample-cluster/src/main/java/sample/cluster/factorial/japi/FactorialResult.java rename to akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/factorial/FactorialResult.java index 0cb74b6b54..8a80071a2e 100644 --- a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/factorial/japi/FactorialResult.java +++ b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/factorial/FactorialResult.java @@ -1,4 +1,4 @@ -package sample.cluster.factorial.japi; +package sample.cluster.factorial; import java.math.BigInteger; import java.io.Serializable; diff --git a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/factorial/japi/MetricsListener.java b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/factorial/MetricsListener.java similarity index 97% rename from akka-samples/akka-sample-cluster/src/main/java/sample/cluster/factorial/japi/MetricsListener.java rename to akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/factorial/MetricsListener.java index 3acbf3e4c0..08ac1eb120 100644 --- a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/factorial/japi/MetricsListener.java +++ b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/factorial/MetricsListener.java @@ -1,4 +1,4 @@ -package sample.cluster.factorial.japi; +package sample.cluster.factorial; //#metrics-listener import akka.actor.UntypedActor; diff --git a/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/simple/SimpleClusterApp.java b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/simple/SimpleClusterApp.java new file mode 100644 index 0000000000..c13041a513 --- /dev/null +++ b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/simple/SimpleClusterApp.java @@ -0,0 +1,34 @@ +package sample.cluster.simple; + +import akka.actor.ActorSystem; +import akka.actor.Props; + +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; + +public class SimpleClusterApp { + + public static void main(String[] args) { + if (args.length == 0) + startup(new String[] { "2551", "2552", "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.load()); + + // Create an Akka system + ActorSystem system = ActorSystem.create("ClusterSystem", config); + + // Create an actor that handles cluster domain events + system.actorOf(Props.create(SimpleClusterListener.class), + "clusterListener"); + + } + } +} diff --git a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/simple/japi/SimpleClusterListener.java b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/simple/SimpleClusterListener.java similarity index 78% rename from akka-samples/akka-sample-cluster/src/main/java/sample/cluster/simple/japi/SimpleClusterListener.java rename to akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/simple/SimpleClusterListener.java index c5dc955303..755a73d749 100644 --- a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/simple/japi/SimpleClusterListener.java +++ b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/simple/SimpleClusterListener.java @@ -1,6 +1,7 @@ -package sample.cluster.simple.japi; +package sample.cluster.simple; import akka.actor.UntypedActor; +import akka.cluster.Cluster; import akka.cluster.ClusterEvent.ClusterDomainEvent; import akka.cluster.ClusterEvent.CurrentClusterState; import akka.cluster.ClusterEvent.MemberUp; @@ -11,6 +12,19 @@ import akka.event.LoggingAdapter; public class SimpleClusterListener extends UntypedActor { LoggingAdapter log = Logging.getLogger(getContext().system(), this); + Cluster cluster = Cluster.get(getContext().system()); + + //subscribe to cluster changes, MemberUp + @Override + public void preStart() { + cluster.subscribe(getSelf(), ClusterDomainEvent.class); + } + + //re-subscribe when restart + @Override + public void postStop() { + cluster.unsubscribe(getSelf()); + } @Override public void onReceive(Object message) { diff --git a/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/Extra.java b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/Extra.java new file mode 100644 index 0000000000..a7de074c2d --- /dev/null +++ b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/Extra.java @@ -0,0 +1,43 @@ +package sample.cluster.stats; + +import java.util.Collections; + +import akka.actor.ActorRef; +import akka.actor.Props; +import akka.actor.UntypedActor; +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; + +//not used, only for documentation +abstract class StatsService2 extends UntypedActor { + //#router-lookup-in-code + int totalInstances = 100; + Iterable 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 UntypedActor { + //#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 +} diff --git a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsAggregator.java b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/StatsAggregator.java similarity index 90% rename from akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsAggregator.java rename to akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/StatsAggregator.java index 0716cc38ec..f4fb49703b 100644 --- a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsAggregator.java +++ b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/StatsAggregator.java @@ -1,11 +1,11 @@ -package sample.cluster.stats.japi; +package sample.cluster.stats; import java.util.ArrayList; import java.util.List; import java.util.concurrent.TimeUnit; -import sample.cluster.stats.japi.StatsMessages.JobFailed; -import sample.cluster.stats.japi.StatsMessages.StatsResult; +import sample.cluster.stats.StatsMessages.JobFailed; +import sample.cluster.stats.StatsMessages.StatsResult; import scala.concurrent.duration.Duration; import akka.actor.ActorRef; import akka.actor.ReceiveTimeout; diff --git a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsFacade.java b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/StatsFacade.java similarity index 77% rename from akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsFacade.java rename to akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/StatsFacade.java index c62e48d8e6..24a8403640 100644 --- a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsFacade.java +++ b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/StatsFacade.java @@ -1,4 +1,4 @@ -package sample.cluster.stats.japi; +package sample.cluster.stats; import java.util.ArrayList; import java.util.Comparator; @@ -6,8 +6,8 @@ import java.util.List; import java.util.SortedSet; import java.util.TreeSet; -import sample.cluster.stats.japi.StatsMessages.JobFailed; -import sample.cluster.stats.japi.StatsMessages.StatsJob; +import sample.cluster.stats.StatsMessages.JobFailed; +import sample.cluster.stats.StatsMessages.StatsJob; import akka.actor.ActorSelection; import akka.actor.UntypedActor; import akka.cluster.Cluster; @@ -19,7 +19,6 @@ import akka.cluster.Member; import akka.event.Logging; import akka.event.LoggingAdapter; - //#facade public class StatsFacade extends UntypedActor { @@ -28,14 +27,16 @@ public class StatsFacade extends UntypedActor { final Comparator ageComparator = new Comparator() { public int compare(Member a, Member b) { - if (a.isOlderThan(b)) return -1; - else if (b.isOlderThan(a)) return 1; - else return 0; + if (a.isOlderThan(b)) + return -1; + else if (b.isOlderThan(a)) + return 1; + else + return 0; } }; final SortedSet membersByAge = new TreeSet(ageComparator); - //subscribe to cluster changes @Override public void preStart() { @@ -61,18 +62,21 @@ public class StatsFacade extends UntypedActor { CurrentClusterState state = (CurrentClusterState) message; List members = new ArrayList(); for (Member m : state.getMembers()) { - if (m.hasRole("compute")) members.add(m); + if (m.hasRole("compute")) + members.add(m); } membersByAge.clear(); membersByAge.addAll(members); } else if (message instanceof MemberUp) { Member m = ((MemberUp) message).member(); - if (m.hasRole("compute")) membersByAge.add(m); + if (m.hasRole("compute")) + membersByAge.add(m); } else if (message instanceof MemberRemoved) { - Member m = ((MemberUp) message).member(); - if (m.hasRole("compute")) membersByAge.remove(m); + Member m = ((MemberRemoved) message).member(); + if (m.hasRole("compute")) + membersByAge.remove(m); } else if (message instanceof MemberEvent) { // not interesting @@ -83,8 +87,8 @@ public class StatsFacade extends UntypedActor { } ActorSelection currentMaster() { - return getContext().actorSelection(membersByAge.first().address() + - "/user/singleton/statsService"); + return getContext().actorSelection( + membersByAge.first().address() + "/user/singleton/statsService"); } } diff --git a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsMessages.java b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/StatsMessages.java similarity index 96% rename from akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsMessages.java rename to akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/StatsMessages.java index 538024efa6..52d8c61ae7 100644 --- a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsMessages.java +++ b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/StatsMessages.java @@ -1,4 +1,4 @@ -package sample.cluster.stats.japi; +package sample.cluster.stats; import java.io.Serializable; diff --git a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsSampleClient.java b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/StatsSampleClient.java similarity index 93% rename from akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsSampleClient.java rename to akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/StatsSampleClient.java index 079c9f2988..acfaebee71 100644 --- a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsSampleClient.java +++ b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/StatsSampleClient.java @@ -1,4 +1,4 @@ -package sample.cluster.stats.japi; +package sample.cluster.stats; import java.util.ArrayList; import java.util.HashSet; @@ -6,9 +6,9 @@ import java.util.List; import java.util.Set; import java.util.concurrent.TimeUnit; -import sample.cluster.stats.japi.StatsMessages.JobFailed; -import sample.cluster.stats.japi.StatsMessages.StatsJob; -import sample.cluster.stats.japi.StatsMessages.StatsResult; +import sample.cluster.stats.StatsMessages.JobFailed; +import sample.cluster.stats.StatsMessages.StatsJob; +import sample.cluster.stats.StatsMessages.StatsResult; import scala.concurrent.forkjoin.ThreadLocalRandom; import scala.concurrent.duration.Duration; import scala.concurrent.duration.FiniteDuration; diff --git a/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/StatsSampleClientMain.java b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/StatsSampleClientMain.java new file mode 100644 index 0000000000..ecc255aa77 --- /dev/null +++ b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/StatsSampleClientMain.java @@ -0,0 +1,17 @@ +package sample.cluster.stats; + +import akka.actor.ActorSystem; +import akka.actor.Props; + +import com.typesafe.config.ConfigFactory; + +public class StatsSampleClientMain { + + public static void main(String[] args) { + // note that client is not a compute node, role not defined + ActorSystem system = ActorSystem.create("ClusterSystem", + ConfigFactory.load("stats1")); + system.actorOf(Props.create(StatsSampleClient.class, "/user/statsService"), + "client"); + } +} diff --git a/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/StatsSampleMain.java b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/StatsSampleMain.java new file mode 100644 index 0000000000..5cb5ffbdc0 --- /dev/null +++ b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/StatsSampleMain.java @@ -0,0 +1,36 @@ +package sample.cluster.stats; + +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; + +import akka.actor.ActorSystem; +import akka.actor.Props; + +public class StatsSampleMain { + + public static void main(String[] args) { + if (args.length == 0) { + startup(new String[] { "2551", "2552", "0" }); + StatsSampleClientMain.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("stats1")); + + ActorSystem system = ActorSystem.create("ClusterSystem", config); + + system.actorOf(Props.create(StatsWorker.class), "statsWorker"); + system.actorOf(Props.create(StatsService.class), "statsService"); + } + + } +} diff --git a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsSampleOneMasterClientMain.java b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/StatsSampleOneMasterClientMain.java similarity index 52% rename from akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsSampleOneMasterClientMain.java rename to akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/StatsSampleOneMasterClientMain.java index 96d8924ca9..a68238ce0b 100644 --- a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsSampleOneMasterClientMain.java +++ b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/StatsSampleOneMasterClientMain.java @@ -1,15 +1,18 @@ -package sample.cluster.stats.japi; +package sample.cluster.stats; + +import com.typesafe.config.ConfigFactory; import akka.actor.ActorSystem; import akka.actor.Props; public class StatsSampleOneMasterClientMain { - public static void main(String[] args) throws Exception { + public static void main(String[] args) { // note that client is not a compute node, role not defined - ActorSystem system = ActorSystem.create("ClusterSystem"); + ActorSystem system = ActorSystem.create("ClusterSystem", + ConfigFactory.load("stats2")); system.actorOf(Props.create(StatsSampleClient.class, "/user/statsFacade"), - "client"); + "client"); } diff --git a/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/StatsSampleOneMasterMain.java b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/StatsSampleOneMasterMain.java new file mode 100644 index 0000000000..e549d47704 --- /dev/null +++ b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/StatsSampleOneMasterMain.java @@ -0,0 +1,43 @@ +package sample.cluster.stats; + +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; + +import akka.actor.ActorSystem; +import akka.actor.PoisonPill; +import akka.actor.Props; +import akka.contrib.pattern.ClusterSingletonManager; + +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 + system.actorOf(ClusterSingletonManager.defaultProps( + Props.create(StatsService.class), "statsService", + PoisonPill.getInstance(), "compute"), "singleton"); + //#create-singleton-manager + + system.actorOf(Props.create(StatsFacade.class), "statsFacade"); + } + + } +} diff --git a/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/StatsService.java b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/StatsService.java new file mode 100644 index 0000000000..a20be6ade3 --- /dev/null +++ b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/StatsService.java @@ -0,0 +1,48 @@ +package sample.cluster.stats; + +import sample.cluster.stats.StatsMessages.StatsJob; +import akka.actor.ActorRef; +import akka.actor.Props; +import akka.actor.UntypedActor; +import akka.routing.ConsistentHashingRouter.ConsistentHashableEnvelope; +import akka.routing.FromConfig; + +//#service +public class StatsService extends UntypedActor { + + // 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 void onReceive(Object message) { + if (message instanceof StatsJob) { + StatsJob job = (StatsJob) message; + if (job.getText().equals("")) { + unhandled(message); + } else { + final String[] words = job.getText().split(" "); + final 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); + } + } + + } else { + unhandled(message); + } + } +} + +//#service + diff --git a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsWorker.java b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/StatsWorker.java similarity index 94% rename from akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsWorker.java rename to akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/StatsWorker.java index ca0af86af6..2d0c97cd19 100644 --- a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsWorker.java +++ b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/stats/StatsWorker.java @@ -1,4 +1,4 @@ -package sample.cluster.stats.japi; +package sample.cluster.stats; import java.util.HashMap; import java.util.Map; diff --git a/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/transformation/TransformationApp.java b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/transformation/TransformationApp.java new file mode 100644 index 0000000000..123c39f309 --- /dev/null +++ b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/transformation/TransformationApp.java @@ -0,0 +1,12 @@ +package sample.cluster.transformation; + +public class TransformationApp { + + public static void main(String[] args) { + // starting 2 frontend nodes and 3 backend nodes + TransformationBackendMain.main(new String[] { "2551" }); + TransformationBackendMain.main(new String[] { "2552" }); + TransformationBackendMain.main(new String[0]); + TransformationFrontendMain.main(new String[0]); + } +} diff --git a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/transformation/japi/TransformationBackend.java b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/transformation/TransformationBackend.java similarity index 73% rename from akka-samples/akka-sample-cluster/src/main/java/sample/cluster/transformation/japi/TransformationBackend.java rename to akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/transformation/TransformationBackend.java index c76b6fed57..2bca33d71a 100644 --- a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/transformation/japi/TransformationBackend.java +++ b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/transformation/TransformationBackend.java @@ -1,16 +1,14 @@ -package sample.cluster.transformation.japi; +package sample.cluster.transformation; -import static sample.cluster.transformation.japi.TransformationMessages.BACKEND_REGISTRATION; -import sample.cluster.transformation.japi.TransformationMessages.TransformationJob; -import sample.cluster.transformation.japi.TransformationMessages.TransformationResult; -//#imports +import static sample.cluster.transformation.TransformationMessages.BACKEND_REGISTRATION; +import sample.cluster.transformation.TransformationMessages.TransformationJob; +import sample.cluster.transformation.TransformationMessages.TransformationResult; import akka.actor.UntypedActor; import akka.cluster.Cluster; import akka.cluster.ClusterEvent.CurrentClusterState; import akka.cluster.ClusterEvent.MemberUp; import akka.cluster.Member; import akka.cluster.MemberStatus; -//#imports //#backend public class TransformationBackend extends UntypedActor { @@ -33,9 +31,8 @@ public class TransformationBackend extends UntypedActor { public void onReceive(Object message) { if (message instanceof TransformationJob) { TransformationJob job = (TransformationJob) message; - getSender() - .tell(new TransformationResult(job.getText().toUpperCase()), - getSelf()); + getSender().tell(new TransformationResult(job.getText().toUpperCase()), + getSelf()); } else if (message instanceof CurrentClusterState) { CurrentClusterState state = (CurrentClusterState) message; @@ -57,7 +54,7 @@ public class TransformationBackend extends UntypedActor { void register(Member member) { if (member.hasRole("frontend")) getContext().actorSelection(member.address() + "/user/frontend").tell( - BACKEND_REGISTRATION, getSelf()); + BACKEND_REGISTRATION, getSelf()); } } //#backend diff --git a/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/transformation/TransformationBackendMain.java b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/transformation/TransformationBackendMain.java new file mode 100644 index 0000000000..dd042b8d5b --- /dev/null +++ b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/transformation/TransformationBackendMain.java @@ -0,0 +1,24 @@ +package sample.cluster.transformation; + +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; + +import akka.actor.ActorSystem; +import akka.actor.Props; + +public class TransformationBackendMain { + + public static void main(String[] args) { + // Override the configuration of the port when specified as program argument + final String port = args.length > 0 ? args[0] : "0"; + final Config config = ConfigFactory.parseString("akka.remote.netty.tcp.port=" + port). + withFallback(ConfigFactory.parseString("akka.cluster.roles = [backend]")). + withFallback(ConfigFactory.load()); + + ActorSystem system = ActorSystem.create("ClusterSystem", config); + + system.actorOf(Props.create(TransformationBackend.class), "backend"); + + } + +} diff --git a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/transformation/japi/TransformationFrontend.java b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/transformation/TransformationFrontend.java similarity index 79% rename from akka-samples/akka-sample-cluster/src/main/java/sample/cluster/transformation/japi/TransformationFrontend.java rename to akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/transformation/TransformationFrontend.java index c1fe0f9038..4fab1cd559 100644 --- a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/transformation/japi/TransformationFrontend.java +++ b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/transformation/TransformationFrontend.java @@ -1,12 +1,12 @@ -package sample.cluster.transformation.japi; +package sample.cluster.transformation; -import static sample.cluster.transformation.japi.TransformationMessages.BACKEND_REGISTRATION; +import static sample.cluster.transformation.TransformationMessages.BACKEND_REGISTRATION; import java.util.ArrayList; import java.util.List; -import sample.cluster.transformation.japi.TransformationMessages.JobFailed; -import sample.cluster.transformation.japi.TransformationMessages.TransformationJob; +import sample.cluster.transformation.TransformationMessages.JobFailed; +import sample.cluster.transformation.TransformationMessages.TransformationJob; import akka.actor.ActorRef; import akka.actor.Terminated; import akka.actor.UntypedActor; diff --git a/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/transformation/TransformationFrontendMain.java b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/transformation/TransformationFrontendMain.java new file mode 100644 index 0000000000..19ac9da177 --- /dev/null +++ b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/transformation/TransformationFrontendMain.java @@ -0,0 +1,51 @@ +package sample.cluster.transformation; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +import com.typesafe.config.Config; +import com.typesafe.config.ConfigFactory; + +import sample.cluster.transformation.TransformationMessages.TransformationJob; +import scala.concurrent.ExecutionContext; +import scala.concurrent.duration.Duration; +import scala.concurrent.duration.FiniteDuration; +import akka.actor.ActorRef; +import akka.actor.ActorSystem; +import akka.actor.Props; +import akka.dispatch.OnSuccess; +import akka.util.Timeout; +import static akka.pattern.Patterns.ask; + +public class TransformationFrontendMain { + + public static void main(String[] args) { + // Override the configuration of the port when specified as program argument + final String port = args.length > 0 ? args[0] : "0"; + final Config config = ConfigFactory.parseString("akka.remote.netty.tcp.port=" + port). + withFallback(ConfigFactory.parseString("akka.cluster.roles = [frontend]")). + withFallback(ConfigFactory.load()); + + ActorSystem system = ActorSystem.create("ClusterSystem", config); + + final ActorRef frontend = system.actorOf( + Props.create(TransformationFrontend.class), "frontend"); + final FiniteDuration interval = Duration.create(2, TimeUnit.SECONDS); + final Timeout timeout = new Timeout(Duration.create(5, TimeUnit.SECONDS)); + final ExecutionContext ec = system.dispatcher(); + final AtomicInteger counter = new AtomicInteger(); + system.scheduler().schedule(interval, interval, new Runnable() { + public void run() { + ask(frontend, + new TransformationJob("hello-" + counter.incrementAndGet()), + timeout).onSuccess(new OnSuccess() { + public void onSuccess(Object result) { + System.out.println(result); + } + }, ec); + } + + }, ec); + + } +} diff --git a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/transformation/japi/TransformationMessages.java b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/transformation/TransformationMessages.java similarity index 96% rename from akka-samples/akka-sample-cluster/src/main/java/sample/cluster/transformation/japi/TransformationMessages.java rename to akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/transformation/TransformationMessages.java index 133ebb09d2..1942122002 100644 --- a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/transformation/japi/TransformationMessages.java +++ b/akka-samples/akka-sample-cluster-java/src/main/java/sample/cluster/transformation/TransformationMessages.java @@ -1,4 +1,4 @@ -package sample.cluster.transformation.japi; +package sample.cluster.transformation; import java.io.Serializable; diff --git a/akka-samples/akka-sample-cluster-java/src/main/resources/application.conf b/akka-samples/akka-sample-cluster-java/src/main/resources/application.conf new file mode 100644 index 0000000000..41281e5485 --- /dev/null +++ b/akka-samples/akka-sample-cluster-java/src/main/resources/application.conf @@ -0,0 +1,20 @@ +akka { + actor { + provider = "akka.cluster.ClusterActorRefProvider" + } + remote { + log-remote-lifecycle-events = off + netty.tcp { + hostname = "127.0.0.1" + port = 0 + } + } + + cluster { + seed-nodes = [ + "akka.tcp://ClusterSystem@127.0.0.1:2551", + "akka.tcp://ClusterSystem@127.0.0.1:2552"] + + auto-down-unreachable-after = 10s + } +} diff --git a/akka-samples/akka-sample-cluster-java/src/main/resources/factorial.conf b/akka-samples/akka-sample-cluster-java/src/main/resources/factorial.conf new file mode 100644 index 0000000000..def18c1e51 --- /dev/null +++ b/akka-samples/akka-sample-cluster-java/src/main/resources/factorial.conf @@ -0,0 +1,31 @@ +include "application" + +# //#min-nr-of-members +akka.cluster.min-nr-of-members = 3 +# //#min-nr-of-members + +# //#role-min-nr-of-members +akka.cluster.role { + frontend.min-nr-of-members = 1 + backend.min-nr-of-members = 2 +} +# //#role-min-nr-of-members + +# //#adaptive-router +akka.actor.deployment { + /factorialFrontend/factorialBackendRouter = { + router = adaptive-group + # metrics-selector = heap + # metrics-selector = load + # metrics-selector = cpu + metrics-selector = mix + nr-of-instances = 100 + routees.paths = ["/user/factorialBackend"] + cluster { + enabled = on + use-role = backend + allow-local-routees = off + } + } +} +# //#adaptive-router diff --git a/akka-samples/akka-sample-cluster-java/src/main/resources/stats1.conf b/akka-samples/akka-sample-cluster-java/src/main/resources/stats1.conf new file mode 100644 index 0000000000..9f376acb04 --- /dev/null +++ b/akka-samples/akka-sample-cluster-java/src/main/resources/stats1.conf @@ -0,0 +1,16 @@ +include "application" + +# //#config-router-lookup +akka.actor.deployment { + /statsService/workerRouter { + router = consistent-hashing-group + nr-of-instances = 100 + routees.paths = ["/user/statsWorker"] + cluster { + enabled = on + allow-local-routees = on + use-role = compute + } + } +} +# //#config-router-lookup diff --git a/akka-samples/akka-sample-cluster-java/src/main/resources/stats2.conf b/akka-samples/akka-sample-cluster-java/src/main/resources/stats2.conf new file mode 100644 index 0000000000..1eee48fd52 --- /dev/null +++ b/akka-samples/akka-sample-cluster-java/src/main/resources/stats2.conf @@ -0,0 +1,17 @@ +include "application" + +# //#config-router-deploy +akka.actor.deployment { + /singleton/statsService/workerRouter { + router = consistent-hashing-pool + nr-of-instances = 100 + cluster { + enabled = on + max-nr-of-instances-per-node = 3 + allow-local-routees = on + use-role = compute + } + } +} +# //#config-router-deploy + diff --git a/akka-samples/akka-sample-cluster/src/multi-jvm/scala/sample/cluster/stats/japi/StatsSampleSingleMasterJapiSpec.scala b/akka-samples/akka-sample-cluster-java/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSingleMasterSpec.scala similarity index 83% rename from akka-samples/akka-sample-cluster/src/multi-jvm/scala/sample/cluster/stats/japi/StatsSampleSingleMasterJapiSpec.scala rename to akka-samples/akka-sample-cluster-java/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSingleMasterSpec.scala index e24c35172d..0bca5c5c85 100644 --- a/akka-samples/akka-sample-cluster/src/multi-jvm/scala/sample/cluster/stats/japi/StatsSampleSingleMasterJapiSpec.scala +++ b/akka-samples/akka-sample-cluster-java/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSingleMasterSpec.scala @@ -1,4 +1,4 @@ -package sample.cluster.stats.japi +package sample.cluster.stats import language.postfixOps import scala.concurrent.duration._ @@ -18,9 +18,9 @@ import akka.contrib.pattern.ClusterSingletonManager import akka.remote.testkit.MultiNodeConfig import akka.remote.testkit.MultiNodeSpec import akka.testkit.ImplicitSender -import sample.cluster.stats.japi.StatsMessages._ +import sample.cluster.stats.StatsMessages._ -object StatsSampleSingleMasterJapiSpecConfig extends MultiNodeConfig { +object StatsSampleSingleMasterSpecConfig extends MultiNodeConfig { // register the named roles (nodes) of the test val first = role("first") val second = role("second") @@ -35,6 +35,7 @@ object StatsSampleSingleMasterJapiSpecConfig extends MultiNodeConfig { akka.cluster.roles = [compute] # don't use sigar for tests, native lib not in path akka.cluster.metrics.collector-class = akka.cluster.JmxMetricsCollector + #//#router-deploy-config akka.actor.deployment { /singleton/statsService/workerRouter { router = consistent-hashing-pool @@ -42,24 +43,25 @@ object StatsSampleSingleMasterJapiSpecConfig extends MultiNodeConfig { cluster { enabled = on max-nr-of-instances-per-node = 3 - allow-local-routees = off + allow-local-routees = on use-role = compute } } } + #//#router-deploy-config """)) } // need one concrete test class per node -class StatsSampleSingleMasterJapiSpecMultiJvmNode1 extends StatsSampleSingleMasterJapiSpec -class StatsSampleSingleMasterJapiSpecMultiJvmNode2 extends StatsSampleSingleMasterJapiSpec -class StatsSampleSingleMasterJapiSpecMultiJvmNode3 extends StatsSampleSingleMasterJapiSpec +class StatsSampleSingleMasterSpecMultiJvmNode1 extends StatsSampleSingleMasterSpec +class StatsSampleSingleMasterSpecMultiJvmNode2 extends StatsSampleSingleMasterSpec +class StatsSampleSingleMasterSpecMultiJvmNode3 extends StatsSampleSingleMasterSpec -abstract class StatsSampleSingleMasterJapiSpec extends MultiNodeSpec(StatsSampleSingleMasterJapiSpecConfig) +abstract class StatsSampleSingleMasterSpec extends MultiNodeSpec(StatsSampleSingleMasterSpecConfig) with WordSpecLike with MustMatchers with BeforeAndAfterAll with ImplicitSender { - import StatsSampleSingleMasterJapiSpecConfig._ + import StatsSampleSingleMasterSpecConfig._ override def initialParticipants = roles.size diff --git a/akka-samples/akka-sample-cluster/src/multi-jvm/scala/sample/cluster/stats/japi/StatsSampleJapiSpec.scala b/akka-samples/akka-sample-cluster-java/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSpec.scala similarity index 87% rename from akka-samples/akka-sample-cluster/src/multi-jvm/scala/sample/cluster/stats/japi/StatsSampleJapiSpec.scala rename to akka-samples/akka-sample-cluster-java/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSpec.scala index 97905b9344..d5fd9a2c68 100644 --- a/akka-samples/akka-sample-cluster/src/multi-jvm/scala/sample/cluster/stats/japi/StatsSampleJapiSpec.scala +++ b/akka-samples/akka-sample-cluster-java/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSpec.scala @@ -1,4 +1,4 @@ -package sample.cluster.stats.japi +package sample.cluster.stats import language.postfixOps import scala.concurrent.duration._ @@ -10,7 +10,7 @@ import akka.cluster.Member import akka.cluster.MemberStatus import akka.cluster.ClusterEvent.CurrentClusterState import akka.cluster.ClusterEvent.MemberUp -import sample.cluster.stats.japi.StatsMessages._ +import sample.cluster.stats.StatsMessages._ import akka.remote.testkit.MultiNodeConfig import com.typesafe.config.ConfigFactory import org.scalatest.BeforeAndAfterAll @@ -19,7 +19,7 @@ import org.scalatest.matchers.MustMatchers import akka.remote.testkit.MultiNodeSpec import akka.testkit.ImplicitSender -object StatsSampleJapiSpecConfig extends MultiNodeConfig { +object StatsSampleSpecConfig extends MultiNodeConfig { // register the named roles (nodes) of the test val first = role("first") val second = role("second") @@ -33,6 +33,7 @@ object StatsSampleJapiSpecConfig extends MultiNodeConfig { akka.cluster.roles = [compute] # don't use sigar for tests, native lib not in path akka.cluster.metrics.collector-class = akka.cluster.JmxMetricsCollector + #//#router-lookup-config akka.actor.deployment { /statsService/workerRouter { router = consistent-hashing-group @@ -45,20 +46,21 @@ object StatsSampleJapiSpecConfig extends MultiNodeConfig { } } } + #//#router-lookup-config """)) } // need one concrete test class per node -class StatsSampleJapiSpecMultiJvmNode1 extends StatsSampleJapiSpec -class StatsSampleJapiSpecMultiJvmNode2 extends StatsSampleJapiSpec -class StatsSampleJapiSpecMultiJvmNode3 extends StatsSampleJapiSpec +class StatsSampleSpecMultiJvmNode1 extends StatsSampleSpec +class StatsSampleSpecMultiJvmNode2 extends StatsSampleSpec +class StatsSampleSpecMultiJvmNode3 extends StatsSampleSpec -abstract class StatsSampleJapiSpec extends MultiNodeSpec(StatsSampleJapiSpecConfig) +abstract class StatsSampleSpec extends MultiNodeSpec(StatsSampleSpecConfig) with WordSpecLike with MustMatchers with BeforeAndAfterAll with ImplicitSender { - import StatsSampleJapiSpecConfig._ + import StatsSampleSpecConfig._ override def initialParticipants = roles.size diff --git a/akka-samples/akka-sample-cluster/src/multi-jvm/scala/sample/cluster/transformation/japi/TransformationSampleJapiSpec.scala b/akka-samples/akka-sample-cluster-java/src/multi-jvm/scala/sample/cluster/transformation/TransformationSampleSpec.scala similarity index 82% rename from akka-samples/akka-sample-cluster/src/multi-jvm/scala/sample/cluster/transformation/japi/TransformationSampleJapiSpec.scala rename to akka-samples/akka-sample-cluster-java/src/multi-jvm/scala/sample/cluster/transformation/TransformationSampleSpec.scala index fafde81f18..bfa24c2845 100644 --- a/akka-samples/akka-sample-cluster/src/multi-jvm/scala/sample/cluster/transformation/japi/TransformationSampleJapiSpec.scala +++ b/akka-samples/akka-sample-cluster-java/src/multi-jvm/scala/sample/cluster/transformation/TransformationSampleSpec.scala @@ -1,4 +1,4 @@ -package sample.cluster.transformation.japi +package sample.cluster.transformation import language.postfixOps import scala.concurrent.duration._ @@ -14,9 +14,9 @@ import akka.cluster.Cluster import akka.remote.testkit.MultiNodeConfig import akka.remote.testkit.MultiNodeSpec import akka.testkit.ImplicitSender -import sample.cluster.transformation.japi.TransformationMessages._ +import sample.cluster.transformation.TransformationMessages._ -object TransformationSampleJapiSpecConfig extends MultiNodeConfig { +object TransformationSampleSpecConfig extends MultiNodeConfig { // register the named roles (nodes) of the test val frontend1 = role("frontend1") val frontend2 = role("frontend2") @@ -42,16 +42,16 @@ object TransformationSampleJapiSpecConfig extends MultiNodeConfig { } // need one concrete test class per node -class TransformationSampleJapiSpecMultiJvmNode1 extends TransformationSampleJapiSpec -class TransformationSampleJapiSpecMultiJvmNode2 extends TransformationSampleJapiSpec -class TransformationSampleJapiSpecMultiJvmNode3 extends TransformationSampleJapiSpec -class TransformationSampleJapiSpecMultiJvmNode4 extends TransformationSampleJapiSpec -class TransformationSampleJapiSpecMultiJvmNode5 extends TransformationSampleJapiSpec +class TransformationSampleSpecMultiJvmNode1 extends TransformationSampleSpec +class TransformationSampleSpecMultiJvmNode2 extends TransformationSampleSpec +class TransformationSampleSpecMultiJvmNode3 extends TransformationSampleSpec +class TransformationSampleSpecMultiJvmNode4 extends TransformationSampleSpec +class TransformationSampleSpecMultiJvmNode5 extends TransformationSampleSpec -abstract class TransformationSampleJapiSpec extends MultiNodeSpec(TransformationSampleJapiSpecConfig) +abstract class TransformationSampleSpec extends MultiNodeSpec(TransformationSampleSpecConfig) with WordSpecLike with MustMatchers with BeforeAndAfterAll with ImplicitSender { - import TransformationSampleJapiSpecConfig._ + import TransformationSampleSpecConfig._ override def initialParticipants = roles.size diff --git a/akka-samples/akka-sample-cluster-java/tutorial/index.html b/akka-samples/akka-sample-cluster-java/tutorial/index.html new file mode 100644 index 0000000000..2f6650b23e --- /dev/null +++ b/akka-samples/akka-sample-cluster-java/tutorial/index.html @@ -0,0 +1,482 @@ + + +Akka Cluster Samples with Java + + + + +
+

+This tutorial contains 4 samples illustrating different +Akka cluster features. +

+
    +
  • Subscribe to cluster membership events
  • +
  • Sending messages to actors running on nodes in the cluster
  • +
  • Cluster aware routers
  • +
  • Cluster metrics
  • +
+
+ +
+

A Simple Cluster Example

+ +

+Open application.conf +

+ +

+To enable cluster capabilities in your Akka project you should, at a minimum, add the remote settings, +and use akka.cluster.ClusterActorRefProvider. The akka.cluster.seed-nodes should +normally also be added to your application.conf file. +

+ +

+The seed nodes are configured contact points which newly started nodes will try to connect with in order to join the cluster. +

+ +

+Note that if you are going to start the nodes on different machines you need to specify the +ip-addresses or host names of the machines in application.conf instead of 127.0.0.1. +

+ +

+Open SimpleClusterApp.java. +

+ +

+The small program together with its configuration starts an ActorSystem with the Cluster enabled. +It joins the cluster and starts an actor that logs some membership events. +Take a look at the +SimpleClusterListener.java +actor. +

+ +

+You can read more about the cluster concepts in the +documentation. +

+ +

+To run this sample, go to the Run +tab, and start the application main class sample.cluster.simple.SimpleClusterApp +if it is not already started. +

+ +

+SimpleClusterApp starts three actor systems (cluster members) in the same JVM process. It can be more +interesting to run them in separate processes. Stop the application in the +Run tab and then open three terminal windows. +

+ +

+In the first terminal window, start the first seed node with the following command (on one line): +

+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.simple.SimpleClusterApp 2551"		
+
+ +

+2551 corresponds to the port of the first seed-nodes element in the configuration. In the log +output you see that the cluster node has been started and changed status to 'Up'. +

+ +

+In the second terminal window, start the second seed node with the following command (on one line): +

+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.simple.SimpleClusterApp 2552"		
+
+ +

+2552 corresponds to the port of the second seed-nodes element in the configuration. In the +log output you see that the cluster node has been started and joins the other seed node and +becomes a member of the cluster. Its status changed to 'Up'. +

+ +

+Switch over to the first terminal window and see in the log output that the member joined. +

+ +

+Start another node in the third terminal window with the following command (on one line): +

+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.simple.SimpleClusterApp 0"		
+
+ +

+Now you don't need to specify the port number, 0 means that it will use a random available port. +It joins one of the configured seed nodes. Look at the log output in the different terminal +windows. +

+ +

+Start even more nodes in the same way, if you like. +

+ +

+Shut down one of the nodes by pressing 'ctrl-c' in one of the terminal windows. +The other nodes will detect the failure after a while, which you can see in the log +output in the other terminals. +

+ +

+Look at the +source code +of the actor again. It registers itself as subscriber of certain cluster events. It gets notified with an snapshot event, +CurrentClusterState that holds full state information of the cluster. After that it receives events for changes +that happen in the cluster. +

+ +
+ +
+

Worker Dial-in Example

+ +

+In the previous sample we saw how to subscribe to cluster membership events. +You can read more about it in the +documentation. +How can cluster membership events be used? +

+ +

+Let's take a look at an example that illustrates how workers, here named backend, +can detect and register to new master nodes, here named frontend. +

+ +

+The example application provides a service to transform text. When some text +is sent to one of the frontend services, it will be delegated to one of the +backend workers, which performs the transformation job, and sends the result back to +the original client. New backend nodes, as well as new frontend nodes, can be +added or removed to the cluster dynamically. +

+ +

+Open TransformationMessages.java. +It defines the messages that are sent between the actors. +

+ +

+The backend worker that performs the transformation job is defined in +TransformationBackend.java +

+ +

+Note that the TransformationBackend actor subscribes to cluster events to detect new, +potential, frontend nodes, and send them a registration message so that they know +that they can use the backend worker. +

+ +

+The frontend that receives user jobs and delegates to one of the registered backend workers is defined in +TransformationFrontend.java +

+ +

+Note that the TransformationFrontend actor watch the registered backend +to be able to remove it from its list of available backend workers. +Death watch uses the cluster failure detector for nodes in the cluster, i.e. it detects +network failures and JVM crashes, in addition to graceful termination of watched +actor. +

+ +

+To run this sample, go to the Run +tab, and start the application main class sample.cluster.transformation.TransformationApp +if it is not already started. +

+ +

+TransformationApp starts +5 actor systems (cluster members) in the same JVM process. It can be more +interesting to run them in separate processes. Stop the application in the +Run tab and run the following commands in separate terminal windows. +

+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.transformation.TransformationFrontendMain 2551"		
+
+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.transformation.TransformationBackendMain 2552"		
+
+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.transformation.TransformationBackendMain 0"		
+
+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.transformation.TransformationBackendMain 0"		
+
+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.transformation.TransformationFrontendMain 0"		
+
+ +
+ +
+

Cluster Aware Routers

+ +

+All routers +can be made aware of member nodes in the cluster, i.e. deploying new routees or looking up routees +on nodes in the cluster. +When a node becomes unreachable or leaves the cluster the routees of that node are +automatically unregistered from the router. When new nodes join the cluster additional +routees are added to the router, according to the configuration. Routees are also added +when a node becomes reachable again, after having been unreachable. +

+ +

+You can read more about cluster aware routers in the +documentation. +

+ +

+Let's take a look at a few samples that make use of cluster aware routers. +

+ +
+ +
+

Router Example with Group of Routees

+ +

+Let's take a look at how to use a cluster aware router with a group of routees, +i.e. a router which does not create its routees but instead forwards incoming messages to a given +set of actors created elsewhere. +

+ +

+The example application provides a service to calculate statistics for a text. +When some text is sent to the service it splits it into words, and delegates the task +to count number of characters in each word to a separate worker, a routee of a router. +The character count for each word is sent back to an aggregator that calculates +the average number of characters per word when all results have been collected. +

+ +

+Open StatsMessages.java. +It defines the messages that are sent between the actors. +

+ +

+The worker that counts number of characters in each word is defined in +StatsWorker.java. +

+ +

+The service that receives text from users and splits it up into words, delegates to workers and aggregates +is defined in StatsService.java +and StatsAggregator.java. +

+ +

+Note, nothing cluster specific so far, just plain actors. +

+ +

+All nodes start StatsService and StatsWorker actors. Remember, routees are the workers in this case. +

+ +

+Open stats1.conf +The router is configured with routees.paths. +This means that user requests can be sent to StatsService on any node and it will use +StatsWorker on all nodes. +

+ +

+To run this sample, go to the Run +tab, and start the application main class sample.cluster.stats.StatsSampleMain +if it is not already started. +

+ +

+StatsSampleMain starts +4 actor systems (cluster members) in the same JVM process. It can be more +interesting to run them in separate processes. Stop the application in the +Run tab and run the following commands in separate terminal windows. +

+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.stats.StatsSampleMain 2551"		
+
+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.stats.StatsSampleMain 2552"		
+
+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.stats.StatsSampleClientMain"		
+
+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.stats.StatsSampleMain 0"		
+
+ +
+ +
+

Router Example with Pool of Remote Deployed Routees

+ +

+Let's take a look at how to use a cluster aware router on single master node that creates +and deploys workers instead of looking them up. +

+ +

+Open StatsSampleOneMasterMain.java. +To keep track of a single master we use the Cluster Singleton +in the contrib module. The ClusterSingletonManager is started on each node. +

+ +

+We also need an actor on each node that keeps track of where current single master exists and +delegates jobs to the StatsService. That is handled by the +StatsFacade.java +

+ +

+The StatsFacade receives text from users and delegates to the current StatsService, the single +master. It listens to cluster events to lookup the StatsService on the oldest node. +

+ +

+All nodes start StatsFacade and the ClusterSingletonManager. The router is now configured in +stats2.conf +

+ +

+To run this sample, go to the Run +tab, and start the application main class sample.cluster.stats.StatsSampleOneMasterMain +if it is not already started. +

+ +

+StatsSampleOneMasterMain starts +4 actor systems (cluster members) in the same JVM process. It can be more +interesting to run them in separate processes. Stop the application in the +Run tab and run the following commands in separate terminal windows. +

+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.stats.StatsSampleOneMasterMain 2551"		
+
+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.stats.StatsSampleOneMasterMain 2552"		
+
+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.stats.StatsSampleOneMasterClientMain"		
+
+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.stats.StatsSampleOneMasterMain 0"		
+
+ +
+ +
+

Adaptive Load Balancing

+ +

+The member nodes of the cluster collects system health metrics and publishes that to other nodes and to +registered subscribers. This information is primarily used for load-balancing routers, such as +the AdaptiveLoadBalancingPool and AdaptiveLoadBalancingGroup routers. +

+ +

+You can read more about cluster metrics in the +documentation. +

+ +

+Let's take a look at this router in action. What can be more demanding than calculating factorials? +

+ +

+The backend worker that performs the factorial calculation: +FactorialBackend +

+ +

+The frontend that receives user jobs and delegates to the backends via the router: +FactorialFrontend +

+ +

+As you can see, the router is defined in the same way as other routers, and in this case it is configured in: +factorial.conf +

+ +

+It is only router type adaptive and the metrics-selector that is specific to this router, +other things work in the same way as other routers. +

+ +

+To run this sample, go to the Run +tab, and start the application main class sample.cluster.factorial.FactorialApp +if it is not already started. +

+ +

+FactorialApp starts +4 actor systems (cluster members) in the same JVM process. It can be more +interesting to run them in separate processes. Stop the application in the +Run tab and run the following commands in separate terminal windows. +

+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.factorial.FactorialBackendMain 2551"		
+
+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.factorial.FactorialBackendMain 2552"		
+
+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.factorial.FactorialBackendMain 0"		
+
+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.factorial.FactorialFrontendMain 0"		
+
+ +

+Press ctrl-c in the terminal window of the frontend to stop the factorial calculations. +

+ +
+ + + diff --git a/akka-samples/akka-sample-cluster-scala/.gitignore b/akka-samples/akka-sample-cluster-scala/.gitignore new file mode 100644 index 0000000000..660c959e44 --- /dev/null +++ b/akka-samples/akka-sample-cluster-scala/.gitignore @@ -0,0 +1,17 @@ +*# +*.iml +*.ipr +*.iws +*.pyc +*.tm.epoch +*.vim +*-shim.sbt +.idea/ +/project/plugins/project +project/boot +target/ +/logs +.cache +.classpath +.project +.settings \ No newline at end of file diff --git a/akka-samples/akka-sample-cluster-scala/LICENSE b/akka-samples/akka-sample-cluster-scala/LICENSE new file mode 100644 index 0000000000..a02154466b --- /dev/null +++ b/akka-samples/akka-sample-cluster-scala/LICENSE @@ -0,0 +1,13 @@ +Copyright 2013 Typesafe, Inc. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + +http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. diff --git a/akka-samples/akka-sample-cluster-scala/activator.properties b/akka-samples/akka-sample-cluster-scala/activator.properties new file mode 100644 index 0000000000..5a68144e0d --- /dev/null +++ b/akka-samples/akka-sample-cluster-scala/activator.properties @@ -0,0 +1,4 @@ +name=akka-sample-cluster-scala +title=Akka Cluster Samples with Scala +description=Akka Cluster Samples with Scala +tags=akka,cluster,scala,sample diff --git a/akka-samples/akka-sample-cluster-scala/project/Build.scala b/akka-samples/akka-sample-cluster-scala/project/Build.scala new file mode 100644 index 0000000000..9a5a3d8043 --- /dev/null +++ b/akka-samples/akka-sample-cluster-scala/project/Build.scala @@ -0,0 +1,32 @@ +import sbt._ +import sbt.Keys._ +import com.typesafe.sbt.SbtMultiJvm +import com.typesafe.sbt.SbtMultiJvm.MultiJvmKeys.MultiJvm + +object AkkaSampleClusterBuild extends Build { + + val akkaVersion = "2.3-SNAPSHOT" + + lazy val akkaSampleCluster = Project( + id = "akka-sample-cluster-scala", + base = file("."), + settings = Project.defaultSettings ++ SbtMultiJvm.multiJvmSettings ++ Seq( + name := "akka-sample-cluster-scala", + version := "1.0", + scalaVersion := "2.10.3", + scalacOptions in Compile ++= Seq("-encoding", "UTF-8", "-target:jvm-1.6", "-deprecation", "-feature", "-unchecked", "-Xlog-reflective-calls", "-Xlint"), + javacOptions in Compile ++= Seq("-source", "1.6", "-target", "1.6", "-Xlint:unchecked", "-Xlint:deprecation"), + libraryDependencies ++= Seq( + "com.typesafe.akka" %% "akka-cluster" % akkaVersion, + "com.typesafe.akka" %% "akka-contrib" % akkaVersion, + "com.typesafe.akka" %% "akka-multi-node-testkit" % akkaVersion, + "org.scalatest" %% "scalatest" % "2.0" % "test", + "org.fusesource" % "sigar" % "1.6.4"), + javaOptions in run ++= Seq( + "-Djava.library.path=./sigar", + "-Xms128m", "-Xmx1024m"), + Keys.fork in run := true, + mainClass in (Compile, run) := Some("sample.cluster.simple.SimpleClusterApp") + ) + ) configs (MultiJvm) +} diff --git a/akka-samples/akka-sample-cluster-scala/project/build.properties b/akka-samples/akka-sample-cluster-scala/project/build.properties new file mode 100644 index 0000000000..0974fce44d --- /dev/null +++ b/akka-samples/akka-sample-cluster-scala/project/build.properties @@ -0,0 +1 @@ +sbt.version=0.13.0 diff --git a/akka-samples/akka-sample-cluster-scala/project/plugins.sbt b/akka-samples/akka-sample-cluster-scala/project/plugins.sbt new file mode 100644 index 0000000000..c3e7d797de --- /dev/null +++ b/akka-samples/akka-sample-cluster-scala/project/plugins.sbt @@ -0,0 +1,4 @@ + +resolvers += Classpaths.typesafeResolver + +addSbtPlugin("com.typesafe.sbt" % "sbt-multi-jvm" % "0.3.8") diff --git a/akka-samples/akka-sample-cluster-scala/sigar/libsigar-amd64-freebsd-6.so b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-amd64-freebsd-6.so new file mode 100644 index 0000000000..3e94f0d2bf Binary files /dev/null and b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-amd64-freebsd-6.so differ diff --git a/akka-samples/akka-sample-cluster-scala/sigar/libsigar-amd64-linux.so b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-amd64-linux.so new file mode 100644 index 0000000000..5a2e4c24fe Binary files /dev/null and b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-amd64-linux.so differ diff --git a/akka-samples/akka-sample-cluster-scala/sigar/libsigar-amd64-solaris.so b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-amd64-solaris.so new file mode 100644 index 0000000000..6396482a43 Binary files /dev/null and b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-amd64-solaris.so differ diff --git a/akka-samples/akka-sample-cluster-scala/sigar/libsigar-ia64-hpux-11.sl b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-ia64-hpux-11.sl new file mode 100644 index 0000000000..d92ea4a96a Binary files /dev/null and b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-ia64-hpux-11.sl differ diff --git a/akka-samples/akka-sample-cluster-scala/sigar/libsigar-ia64-linux.so b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-ia64-linux.so new file mode 100644 index 0000000000..2bd2fc8e32 Binary files /dev/null and b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-ia64-linux.so differ diff --git a/akka-samples/akka-sample-cluster-scala/sigar/libsigar-pa-hpux-11.sl b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-pa-hpux-11.sl new file mode 100644 index 0000000000..0dfd8a1122 Binary files /dev/null and b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-pa-hpux-11.sl differ diff --git a/akka-samples/akka-sample-cluster-scala/sigar/libsigar-ppc-aix-5.so b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-ppc-aix-5.so new file mode 100644 index 0000000000..7d4b519921 Binary files /dev/null and b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-ppc-aix-5.so differ diff --git a/akka-samples/akka-sample-cluster-scala/sigar/libsigar-ppc-linux.so b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-ppc-linux.so new file mode 100644 index 0000000000..4394b1b00f Binary files /dev/null and b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-ppc-linux.so differ diff --git a/akka-samples/akka-sample-cluster-scala/sigar/libsigar-ppc64-aix-5.so b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-ppc64-aix-5.so new file mode 100644 index 0000000000..35fd828808 Binary files /dev/null and b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-ppc64-aix-5.so differ diff --git a/akka-samples/akka-sample-cluster-scala/sigar/libsigar-ppc64-linux.so b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-ppc64-linux.so new file mode 100644 index 0000000000..a1ba2529c9 Binary files /dev/null and b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-ppc64-linux.so differ diff --git a/akka-samples/akka-sample-cluster-scala/sigar/libsigar-s390x-linux.so b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-s390x-linux.so new file mode 100644 index 0000000000..c275f4ac69 Binary files /dev/null and b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-s390x-linux.so differ diff --git a/akka-samples/akka-sample-cluster-scala/sigar/libsigar-sparc-solaris.so b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-sparc-solaris.so new file mode 100644 index 0000000000..aa847d2b54 Binary files /dev/null and b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-sparc-solaris.so differ diff --git a/akka-samples/akka-sample-cluster-scala/sigar/libsigar-sparc64-solaris.so b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-sparc64-solaris.so new file mode 100644 index 0000000000..6c4fe809c5 Binary files /dev/null and b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-sparc64-solaris.so differ diff --git a/akka-samples/akka-sample-cluster-scala/sigar/libsigar-universal-macosx.dylib b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-universal-macosx.dylib new file mode 100644 index 0000000000..27ab107111 Binary files /dev/null and b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-universal-macosx.dylib differ diff --git a/akka-samples/akka-sample-cluster-scala/sigar/libsigar-universal64-macosx.dylib b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-universal64-macosx.dylib new file mode 100644 index 0000000000..0c721fecf3 Binary files /dev/null and b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-universal64-macosx.dylib differ diff --git a/akka-samples/akka-sample-cluster-scala/sigar/libsigar-x86-freebsd-5.so b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-x86-freebsd-5.so new file mode 100644 index 0000000000..8c50c6117a Binary files /dev/null and b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-x86-freebsd-5.so differ diff --git a/akka-samples/akka-sample-cluster-scala/sigar/libsigar-x86-freebsd-6.so b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-x86-freebsd-6.so new file mode 100644 index 0000000000..f0800274a6 Binary files /dev/null and b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-x86-freebsd-6.so differ diff --git a/akka-samples/akka-sample-cluster-scala/sigar/libsigar-x86-linux.so b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-x86-linux.so new file mode 100644 index 0000000000..a0b64eddb0 Binary files /dev/null and b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-x86-linux.so differ diff --git a/akka-samples/akka-sample-cluster-scala/sigar/libsigar-x86-solaris.so b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-x86-solaris.so new file mode 100644 index 0000000000..c6452e5655 Binary files /dev/null and b/akka-samples/akka-sample-cluster-scala/sigar/libsigar-x86-solaris.so differ diff --git a/akka-samples/akka-sample-cluster-scala/sigar/sigar-amd64-winnt.dll b/akka-samples/akka-sample-cluster-scala/sigar/sigar-amd64-winnt.dll new file mode 100644 index 0000000000..1ec8a0353e Binary files /dev/null and b/akka-samples/akka-sample-cluster-scala/sigar/sigar-amd64-winnt.dll differ diff --git a/akka-samples/akka-sample-cluster-scala/sigar/sigar-x86-winnt.dll b/akka-samples/akka-sample-cluster-scala/sigar/sigar-x86-winnt.dll new file mode 100644 index 0000000000..6afdc0166c Binary files /dev/null and b/akka-samples/akka-sample-cluster-scala/sigar/sigar-x86-winnt.dll differ diff --git a/akka-samples/akka-sample-cluster-scala/sigar/sigar-x86-winnt.lib b/akka-samples/akka-sample-cluster-scala/sigar/sigar-x86-winnt.lib new file mode 100644 index 0000000000..04924a1fc1 Binary files /dev/null and b/akka-samples/akka-sample-cluster-scala/sigar/sigar-x86-winnt.lib differ diff --git a/akka-samples/akka-sample-cluster-scala/src/main/resources/application.conf b/akka-samples/akka-sample-cluster-scala/src/main/resources/application.conf new file mode 100644 index 0000000000..41281e5485 --- /dev/null +++ b/akka-samples/akka-sample-cluster-scala/src/main/resources/application.conf @@ -0,0 +1,20 @@ +akka { + actor { + provider = "akka.cluster.ClusterActorRefProvider" + } + remote { + log-remote-lifecycle-events = off + netty.tcp { + hostname = "127.0.0.1" + port = 0 + } + } + + cluster { + seed-nodes = [ + "akka.tcp://ClusterSystem@127.0.0.1:2551", + "akka.tcp://ClusterSystem@127.0.0.1:2552"] + + auto-down-unreachable-after = 10s + } +} diff --git a/akka-samples/akka-sample-cluster-scala/src/main/resources/factorial.conf b/akka-samples/akka-sample-cluster-scala/src/main/resources/factorial.conf new file mode 100644 index 0000000000..def18c1e51 --- /dev/null +++ b/akka-samples/akka-sample-cluster-scala/src/main/resources/factorial.conf @@ -0,0 +1,31 @@ +include "application" + +# //#min-nr-of-members +akka.cluster.min-nr-of-members = 3 +# //#min-nr-of-members + +# //#role-min-nr-of-members +akka.cluster.role { + frontend.min-nr-of-members = 1 + backend.min-nr-of-members = 2 +} +# //#role-min-nr-of-members + +# //#adaptive-router +akka.actor.deployment { + /factorialFrontend/factorialBackendRouter = { + router = adaptive-group + # metrics-selector = heap + # metrics-selector = load + # metrics-selector = cpu + metrics-selector = mix + nr-of-instances = 100 + routees.paths = ["/user/factorialBackend"] + cluster { + enabled = on + use-role = backend + allow-local-routees = off + } + } +} +# //#adaptive-router diff --git a/akka-samples/akka-sample-cluster-scala/src/main/resources/stats1.conf b/akka-samples/akka-sample-cluster-scala/src/main/resources/stats1.conf new file mode 100644 index 0000000000..9f376acb04 --- /dev/null +++ b/akka-samples/akka-sample-cluster-scala/src/main/resources/stats1.conf @@ -0,0 +1,16 @@ +include "application" + +# //#config-router-lookup +akka.actor.deployment { + /statsService/workerRouter { + router = consistent-hashing-group + nr-of-instances = 100 + routees.paths = ["/user/statsWorker"] + cluster { + enabled = on + allow-local-routees = on + use-role = compute + } + } +} +# //#config-router-lookup diff --git a/akka-samples/akka-sample-cluster-scala/src/main/resources/stats2.conf b/akka-samples/akka-sample-cluster-scala/src/main/resources/stats2.conf new file mode 100644 index 0000000000..1eee48fd52 --- /dev/null +++ b/akka-samples/akka-sample-cluster-scala/src/main/resources/stats2.conf @@ -0,0 +1,17 @@ +include "application" + +# //#config-router-deploy +akka.actor.deployment { + /singleton/statsService/workerRouter { + router = consistent-hashing-pool + nr-of-instances = 100 + cluster { + enabled = on + max-nr-of-instances-per-node = 3 + allow-local-routees = on + use-role = compute + } + } +} +# //#config-router-deploy + diff --git a/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/factorial/Extra.scala b/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/factorial/Extra.scala new file mode 100644 index 0000000000..24149b0b85 --- /dev/null +++ b/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/factorial/Extra.scala @@ -0,0 +1,38 @@ +package sample.cluster.factorial + +import akka.actor.Props +import akka.actor.Actor + +// not used, only for documentation +abstract class FactorialFrontend2 extends Actor { + //#router-lookup-in-code + import akka.cluster.routing.ClusterRouterGroup + import akka.cluster.routing.ClusterRouterGroupSettings + import akka.cluster.routing.AdaptiveLoadBalancingGroup + import akka.cluster.routing.HeapMetricsSelector + + val backend = context.actorOf( + ClusterRouterGroup(AdaptiveLoadBalancingGroup(HeapMetricsSelector), + ClusterRouterGroupSettings( + totalInstances = 100, routeesPaths = List("/user/factorialBackend"), + allowLocalRoutees = true, useRole = Some("backend"))).props(), + name = "factorialBackendRouter2") + //#router-lookup-in-code +} + +// not used, only for documentation +abstract class FactorialFrontend3 extends Actor { + //#router-deploy-in-code + import akka.cluster.routing.ClusterRouterPool + import akka.cluster.routing.ClusterRouterPoolSettings + import akka.cluster.routing.AdaptiveLoadBalancingPool + import akka.cluster.routing.SystemLoadAverageMetricsSelector + + val backend = context.actorOf( + ClusterRouterPool(AdaptiveLoadBalancingPool( + SystemLoadAverageMetricsSelector), ClusterRouterPoolSettings( + totalInstances = 100, maxInstancesPerNode = 3, + allowLocalRoutees = false, useRole = Some("backend"))).props(Props[FactorialBackend]), + name = "factorialBackendRouter3") + //#router-deploy-in-code +} \ No newline at end of file diff --git a/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/factorial/FactorialApp.scala b/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/factorial/FactorialApp.scala new file mode 100644 index 0000000000..69fb39c4ad --- /dev/null +++ b/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/factorial/FactorialApp.scala @@ -0,0 +1,11 @@ +package sample.cluster.factorial + +object FactorialApp { + def main(args: Array[String]): Unit = { + // starting 3 backend nodes and 1 frontend node + FactorialBackend.main(Seq("2551").toArray) + FactorialBackend.main(Seq("2552").toArray) + FactorialBackend.main(Array.empty) + FactorialFrontend.main(Array.empty) + } +} \ No newline at end of file diff --git a/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/factorial/FactorialBackend.scala b/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/factorial/FactorialBackend.scala new file mode 100644 index 0000000000..d1e41c0b9e --- /dev/null +++ b/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/factorial/FactorialBackend.scala @@ -0,0 +1,46 @@ +package sample.cluster.factorial + +import scala.annotation.tailrec +import scala.concurrent.Future +import com.typesafe.config.ConfigFactory +import akka.actor.Actor +import akka.actor.ActorLogging +import akka.actor.ActorSystem +import akka.actor.Props +import akka.pattern.pipe + +//#backend +class FactorialBackend extends Actor with ActorLogging { + + import context.dispatcher + + def receive = { + case (n: Int) => + Future(factorial(n)) map { result => (n, result) } pipeTo sender + } + + def factorial(n: Int): BigInt = { + @tailrec def factorialAcc(acc: BigInt, n: Int): BigInt = { + if (n <= 1) acc + else factorialAcc(acc * n, n - 1) + } + factorialAcc(BigInt(1), n) + } + +} +//#backend + +object FactorialBackend { + def main(args: Array[String]): Unit = { + // Override the configuration of the port when specified as program argument + val port = if (args.isEmpty) "0" else args(0) + val config = ConfigFactory.parseString(s"akka.remote.netty.tcp.port=$port"). + withFallback(ConfigFactory.parseString("akka.cluster.roles = [backend]")). + withFallback(ConfigFactory.load("factorial")) + + val system = ActorSystem("ClusterSystem", config) + system.actorOf(Props[FactorialBackend], name = "factorialBackend") + + system.actorOf(Props[MetricsListener], name = "metricsListener") + } +} \ No newline at end of file diff --git a/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/factorial/FactorialFrontend.scala b/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/factorial/FactorialFrontend.scala new file mode 100644 index 0000000000..b9606d1456 --- /dev/null +++ b/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/factorial/FactorialFrontend.scala @@ -0,0 +1,61 @@ +package sample.cluster.factorial + +import scala.concurrent.duration._ +import com.typesafe.config.ConfigFactory +import akka.actor.Actor +import akka.actor.ActorLogging +import akka.actor.ActorSystem +import akka.actor.Props +import akka.cluster.Cluster +import akka.routing.FromConfig +import akka.actor.ReceiveTimeout + +//#frontend +class FactorialFrontend(upToN: Int, repeat: Boolean) extends Actor with ActorLogging { + + val backend = context.actorOf(FromConfig.props(), + name = "factorialBackendRouter") + + override def preStart(): Unit = { + sendJobs() + if (repeat) { + context.setReceiveTimeout(10.seconds) + } + } + + def receive = { + case (n: Int, factorial: BigInt) => + if (n == upToN) { + log.debug("{}! = {}", n, factorial) + if (repeat) sendJobs() + else context.stop(self) + } + case ReceiveTimeout => + log.info("Timeout") + sendJobs() + } + + def sendJobs(): Unit = { + log.info("Starting batch of factorials up to [{}]", upToN) + 1 to upToN foreach { backend ! _ } + } +} +//#frontend + +object FactorialFrontend { + def main(args: Array[String]): Unit = { + val upToN = 200 + + val config = ConfigFactory.parseString("akka.cluster.roles = [frontend]"). + withFallback(ConfigFactory.load("factorial")) + + val system = ActorSystem("ClusterSystem", config) + system.log.info("Factorials will start when 2 backend members in the cluster.") + //#registerOnUp + Cluster(system) registerOnMemberUp { + system.actorOf(Props(classOf[FactorialFrontend], upToN, true), + name = "factorialFrontend") + } + //#registerOnUp + } +} \ No newline at end of file diff --git a/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/factorial/MetricsListener.scala b/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/factorial/MetricsListener.scala new file mode 100644 index 0000000000..3d6a2a890e --- /dev/null +++ b/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/factorial/MetricsListener.scala @@ -0,0 +1,47 @@ +package sample.cluster.factorial + +import akka.actor.ActorLogging +import akka.actor.Actor + +//#metrics-listener +import akka.cluster.Cluster +import akka.cluster.ClusterEvent.ClusterMetricsChanged +import akka.cluster.ClusterEvent.CurrentClusterState +import akka.cluster.NodeMetrics +import akka.cluster.StandardMetrics.HeapMemory +import akka.cluster.StandardMetrics.Cpu + +class MetricsListener extends Actor with ActorLogging { + val selfAddress = Cluster(context.system).selfAddress + + // subscribe to ClusterMetricsChanged + // re-subscribe when restart + override def preStart(): Unit = + Cluster(context.system).subscribe(self, classOf[ClusterMetricsChanged]) + override def postStop(): Unit = + Cluster(context.system).unsubscribe(self) + + def receive = { + case ClusterMetricsChanged(clusterMetrics) => + clusterMetrics.filter(_.address == selfAddress) foreach { nodeMetrics => + logHeap(nodeMetrics) + logCpu(nodeMetrics) + } + case state: CurrentClusterState => // ignore + } + + def logHeap(nodeMetrics: NodeMetrics): Unit = nodeMetrics match { + case HeapMemory(address, timestamp, used, committed, max) => + log.info("Used heap: {} MB", used.doubleValue / 1024 / 1024) + case _ => // no heap info + } + + def logCpu(nodeMetrics: NodeMetrics): Unit = nodeMetrics match { + case Cpu(address, timestamp, Some(systemLoadAverage), cpuCombined, processors) => + log.info("Load: {} ({} processors)", systemLoadAverage, processors) + case _ => // no cpu info + } +} + +//#metrics-listener + diff --git a/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/simple/SimpleClusterApp.scala b/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/simple/SimpleClusterApp.scala new file mode 100644 index 0000000000..1e87f49d25 --- /dev/null +++ b/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/simple/SimpleClusterApp.scala @@ -0,0 +1,29 @@ +package sample.cluster.simple + +import com.typesafe.config.ConfigFactory +import akka.actor.ActorSystem +import akka.actor.Props + +object SimpleClusterApp { + def main(args: Array[String]): Unit = { + if (args.isEmpty) + startup(Seq("2551", "2552", "0")) + else + startup(args) + } + + def startup(ports: Seq[String]): Unit = { + ports foreach { port => + // Override the configuration of the port + val config = ConfigFactory.parseString("akka.remote.netty.tcp.port=" + port). + withFallback(ConfigFactory.load()) + + // Create an Akka system + val system = ActorSystem("ClusterSystem", config) + // Create an actor that handles cluster domain events + system.actorOf(Props[SimpleClusterListener], name = "clusterListener") + } + } + +} + diff --git a/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/simple/SimpleClusterListener.scala b/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/simple/SimpleClusterListener.scala new file mode 100644 index 0000000000..7f06f38ba2 --- /dev/null +++ b/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/simple/SimpleClusterListener.scala @@ -0,0 +1,28 @@ +package sample.cluster.simple + +import akka.cluster.Cluster +import akka.cluster.ClusterEvent._ +import akka.actor.ActorLogging +import akka.actor.Actor + +class SimpleClusterListener extends Actor with ActorLogging { + + val cluster = Cluster(context.system) + + // subscribe to cluster changes, re-subscribe when restart + override def preStart(): Unit = cluster.subscribe(self, classOf[ClusterDomainEvent]) + override def postStop(): Unit = cluster.unsubscribe(self) + + def receive = { + case state: CurrentClusterState => + log.info("Current members: {}", state.members.mkString(", ")) + case MemberUp(member) => + log.info("Member is Up: {}", member.address) + case UnreachableMember(member) => + log.info("Member detected as unreachable: {}", member) + case MemberRemoved(member, previousStatus) => + log.info("Member is Removed: {} after {}", + member.address, previousStatus) + case _: ClusterDomainEvent => // ignore + } +} \ No newline at end of file diff --git a/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/stats/Extra.scala b/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/stats/Extra.scala new file mode 100644 index 0000000000..f5163c84db --- /dev/null +++ b/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/stats/Extra.scala @@ -0,0 +1,34 @@ +package sample.cluster.stats + +import akka.actor.Actor +import akka.actor.Props + +// not used, only for documentation +abstract class StatsService2 extends Actor { + //#router-lookup-in-code + import akka.cluster.routing.ClusterRouterGroup + import akka.cluster.routing.ClusterRouterGroupSettings + import akka.routing.ConsistentHashingGroup + + val workerRouter = context.actorOf( + ClusterRouterGroup(ConsistentHashingGroup(Nil), ClusterRouterGroupSettings( + totalInstances = 100, routeesPaths = List("/user/statsWorker"), + allowLocalRoutees = true, useRole = Some("compute"))).props(), + name = "workerRouter2") + //#router-lookup-in-code +} + +// not used, only for documentation +abstract class StatsService3 extends Actor { + //#router-deploy-in-code + import akka.cluster.routing.ClusterRouterPool + import akka.cluster.routing.ClusterRouterPoolSettings + import akka.routing.ConsistentHashingPool + + val workerRouter = context.actorOf( + ClusterRouterPool(ConsistentHashingPool(0), ClusterRouterPoolSettings( + totalInstances = 100, maxInstancesPerNode = 3, + allowLocalRoutees = false, useRole = None)).props(Props[StatsWorker]), + name = "workerRouter3") + //#router-deploy-in-code +} diff --git a/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/stats/StatsFacade.scala b/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/stats/StatsFacade.scala new file mode 100644 index 0000000000..a0cf0fe4d2 --- /dev/null +++ b/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/stats/StatsFacade.scala @@ -0,0 +1,48 @@ +package sample.cluster.stats + +import scala.collection.immutable +import akka.actor.Actor +import akka.actor.ActorLogging +import akka.actor.ActorSelection +import akka.actor.RootActorPath +import akka.cluster.Cluster +import akka.cluster.ClusterEvent.CurrentClusterState +import akka.cluster.ClusterEvent.MemberEvent +import akka.cluster.ClusterEvent.MemberRemoved +import akka.cluster.ClusterEvent.MemberUp +import akka.cluster.Member + +//#facade +class StatsFacade extends Actor with ActorLogging { + import context.dispatcher + val cluster = Cluster(context.system) + + // sort by age, oldest first + val ageOrdering = Ordering.fromLessThan[Member] { (a, b) => a.isOlderThan(b) } + var membersByAge: immutable.SortedSet[Member] = immutable.SortedSet.empty(ageOrdering) + + // subscribe to cluster changes + // re-subscribe when restart + override def preStart(): Unit = cluster.subscribe(self, classOf[MemberEvent]) + override def postStop(): Unit = cluster.unsubscribe(self) + + def receive = { + case job: StatsJob if membersByAge.isEmpty => + sender ! JobFailed("Service unavailable, try again later") + case job: StatsJob => + currentMaster.tell(job, sender) + case state: CurrentClusterState => + membersByAge = immutable.SortedSet.empty(ageOrdering) ++ state.members.collect { + case m if m.hasRole("compute") => m + } + case MemberUp(m) => if (m.hasRole("compute")) membersByAge += m + case MemberRemoved(m, _) => if (m.hasRole("compute")) membersByAge -= m + case _: MemberEvent => // not interesting + } + + def currentMaster: ActorSelection = + context.actorSelection(RootActorPath(membersByAge.head.address) / + "user" / "singleton" / "statsService") + +} +//#facade \ No newline at end of file diff --git a/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/stats/StatsMessages.scala b/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/stats/StatsMessages.scala new file mode 100644 index 0000000000..916dfae294 --- /dev/null +++ b/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/stats/StatsMessages.scala @@ -0,0 +1,7 @@ +package sample.cluster.stats + +//#messages +case class StatsJob(text: String) +case class StatsResult(meanWordLength: Double) +case class JobFailed(reason: String) +//#messages diff --git a/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/stats/StatsSample.scala b/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/stats/StatsSample.scala new file mode 100644 index 0000000000..ef286bd497 --- /dev/null +++ b/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/stats/StatsSample.scala @@ -0,0 +1,91 @@ +package sample.cluster.stats + +import scala.concurrent.duration._ +import scala.concurrent.forkjoin.ThreadLocalRandom +import com.typesafe.config.ConfigFactory +import akka.actor.Actor +import akka.actor.ActorSystem +import akka.actor.Address +import akka.actor.PoisonPill +import akka.actor.Props +import akka.actor.RelativeActorPath +import akka.actor.RootActorPath +import akka.cluster.Cluster +import akka.cluster.ClusterEvent._ +import akka.cluster.MemberStatus + +object StatsSample { + def main(args: Array[String]): Unit = { + if (args.isEmpty) { + startup(Seq("2551", "2552", "0")) + StatsSampleClient.main(Array.empty) + } else { + startup(args) + } + } + + def startup(ports: Seq[String]): Unit = { + ports foreach { port => + // Override the configuration of the port when specified as program argument + val config = + ConfigFactory.parseString(s"akka.remote.netty.tcp.port=" + port).withFallback( + ConfigFactory.parseString("akka.cluster.roles = [compute]")). + withFallback(ConfigFactory.load("stats1")) + + val system = ActorSystem("ClusterSystem", config) + + system.actorOf(Props[StatsWorker], name = "statsWorker") + system.actorOf(Props[StatsService], name = "statsService") + } + } +} + +object StatsSampleClient { + def main(args: Array[String]): Unit = { + // note that client is not a compute node, role not defined + val system = ActorSystem("ClusterSystem") + system.actorOf(Props(classOf[StatsSampleClient], "/user/statsService"), "client") + } +} + +class StatsSampleClient(servicePath: String) extends Actor { + val cluster = Cluster(context.system) + val servicePathElements = servicePath match { + case RelativeActorPath(elements) => elements + case _ => throw new IllegalArgumentException( + "servicePath [%s] is not a valid relative actor path" format servicePath) + } + import context.dispatcher + val tickTask = context.system.scheduler.schedule(2.seconds, 2.seconds, self, "tick") + + var nodes = Set.empty[Address] + + override def preStart(): Unit = { + cluster.subscribe(self, classOf[MemberEvent]) + cluster.subscribe(self, classOf[UnreachableMember]) + } + override def postStop(): Unit = { + cluster.unsubscribe(self) + tickTask.cancel() + } + + def receive = { + case "tick" if nodes.nonEmpty => + // just pick any one + val address = nodes.toIndexedSeq(ThreadLocalRandom.current.nextInt(nodes.size)) + val service = context.actorSelection(RootActorPath(address) / servicePathElements) + service ! StatsJob("this is the text that will be analyzed") + case result: StatsResult => + println(result) + case failed: JobFailed => + println(failed) + case state: CurrentClusterState => + nodes = state.members.collect { + case m if m.hasRole("compute") && m.status == MemberStatus.Up => m.address + } + case MemberUp(m) if m.hasRole("compute") => nodes += m.address + case other: MemberEvent => nodes -= other.member.address + case UnreachableMember(m) => nodes -= m.address + } + +} diff --git a/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/stats/StatsSampleOneMaster.scala b/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/stats/StatsSampleOneMaster.scala new file mode 100644 index 0000000000..3fc7f5c8f1 --- /dev/null +++ b/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/stats/StatsSampleOneMaster.scala @@ -0,0 +1,47 @@ +package sample.cluster.stats + +import com.typesafe.config.ConfigFactory +import akka.actor.ActorSystem +import akka.actor.PoisonPill +import akka.actor.Props +import akka.contrib.pattern.ClusterSingletonManager + +object StatsSampleOneMaster { + def main(args: Array[String]): Unit = { + if (args.isEmpty) { + startup(Seq("2551", "2552", "0")) + StatsSampleOneMasterClient.main(Array.empty) + } else { + startup(args) + } + } + + def startup(ports: Seq[String]): Unit = { + ports foreach { port => + // Override the configuration of the port when specified as program argument + val config = + ConfigFactory.parseString(s"akka.remote.netty.tcp.port=" + port).withFallback( + ConfigFactory.parseString("akka.cluster.roles = [compute]")). + withFallback(ConfigFactory.load("stats2")) + + val system = ActorSystem("ClusterSystem", config) + + //#create-singleton-manager + system.actorOf(ClusterSingletonManager.props( + singletonProps = Props[StatsService], singletonName = "statsService", + terminationMessage = PoisonPill, role = Some("compute")), + name = "singleton") + //#create-singleton-manager + system.actorOf(Props[StatsFacade], name = "statsFacade") + } + } +} + +object StatsSampleOneMasterClient { + def main(args: Array[String]): Unit = { + // note that client is not a compute node, role not defined + val system = ActorSystem("ClusterSystem") + system.actorOf(Props(classOf[StatsSampleClient], "/user/statsFacade"), "client") + } +} + diff --git a/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/stats/StatsService.scala b/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/stats/StatsService.scala new file mode 100644 index 0000000000..f7d21d13bb --- /dev/null +++ b/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/stats/StatsService.scala @@ -0,0 +1,50 @@ +package sample.cluster.stats + +import scala.concurrent.duration._ +import akka.actor.Actor +import akka.actor.ActorRef +import akka.actor.Props +import akka.actor.ReceiveTimeout +import akka.routing.ConsistentHashingRouter.ConsistentHashableEnvelope +import akka.routing.FromConfig + +//#service +class StatsService extends Actor { + // 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[StatsWorker.class]. + val workerRouter = context.actorOf(FromConfig.props(Props[StatsWorker]), + name = "workerRouter") + + def receive = { + case StatsJob(text) if text != "" => + val words = text.split(" ") + val replyTo = sender // important to not close over sender + // create actor that collects replies from workers + val aggregator = context.actorOf(Props( + classOf[StatsAggregator], words.size, replyTo)) + words foreach { word => + workerRouter.tell( + ConsistentHashableEnvelope(word, word), aggregator) + } + } +} + +class StatsAggregator(expectedResults: Int, replyTo: ActorRef) extends Actor { + var results = IndexedSeq.empty[Int] + context.setReceiveTimeout(3.seconds) + + def receive = { + case wordCount: Int => + results = results :+ wordCount + if (results.size == expectedResults) { + val meanWordLength = results.sum.toDouble / results.size + replyTo ! StatsResult(meanWordLength) + context.stop(self) + } + case ReceiveTimeout => + replyTo ! JobFailed("Service unavailable, try again later") + context.stop(self) + } +} +//#service diff --git a/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/stats/StatsWorker.scala b/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/stats/StatsWorker.scala new file mode 100644 index 0000000000..dd2ebd4bfc --- /dev/null +++ b/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/stats/StatsWorker.scala @@ -0,0 +1,21 @@ +package sample.cluster.stats + +import akka.actor.Actor + +//#worker +class StatsWorker extends Actor { + var cache = Map.empty[String, Int] + def receive = { + case word: String => + val length = cache.get(word) match { + case Some(x) => x + case None => + val x = word.length + cache += (word -> x) + x + } + + sender ! length + } +} +//#worker \ No newline at end of file diff --git a/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/transformation/TransformationApp.scala b/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/transformation/TransformationApp.scala new file mode 100644 index 0000000000..e1ea6f1518 --- /dev/null +++ b/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/transformation/TransformationApp.scala @@ -0,0 +1,14 @@ +package sample.cluster.transformation + +object TransformationApp { + + def main(args: Array[String]): Unit = { + // starting 2 frontend nodes and 3 backend nodes + TransformationFrontend.main(Seq("2551").toArray) + TransformationBackend.main(Seq("2552").toArray) + TransformationBackend.main(Array.empty) + TransformationBackend.main(Array.empty) + TransformationFrontend.main(Array.empty) + } + +} \ No newline at end of file diff --git a/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/transformation/TransformationBackend.scala b/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/transformation/TransformationBackend.scala new file mode 100644 index 0000000000..6148da8850 --- /dev/null +++ b/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/transformation/TransformationBackend.scala @@ -0,0 +1,52 @@ +package sample.cluster.transformation + +import language.postfixOps +import scala.concurrent.duration._ +import akka.actor.Actor +import akka.actor.ActorRef +import akka.actor.ActorSystem +import akka.actor.Props +import akka.actor.RootActorPath +import akka.cluster.Cluster +import akka.cluster.ClusterEvent.CurrentClusterState +import akka.cluster.ClusterEvent.MemberUp +import akka.cluster.Member +import akka.cluster.MemberStatus +import com.typesafe.config.ConfigFactory + +//#backend +class TransformationBackend extends Actor { + + val cluster = Cluster(context.system) + + // subscribe to cluster changes, MemberUp + // re-subscribe when restart + override def preStart(): Unit = cluster.subscribe(self, classOf[MemberUp]) + override def postStop(): Unit = cluster.unsubscribe(self) + + def receive = { + case TransformationJob(text) => sender ! TransformationResult(text.toUpperCase) + case state: CurrentClusterState => + state.members.filter(_.status == MemberStatus.Up) foreach register + case MemberUp(m) => register(m) + } + + def register(member: Member): Unit = + if (member.hasRole("frontend")) + context.actorSelection(RootActorPath(member.address) / "user" / "frontend") ! + BackendRegistration +} +//#backend + +object TransformationBackend { + def main(args: Array[String]): Unit = { + // Override the configuration of the port when specified as program argument + val port = if (args.isEmpty) "0" else args(0) + val config = ConfigFactory.parseString(s"akka.remote.netty.tcp.port=$port"). + withFallback(ConfigFactory.parseString("akka.cluster.roles = [backend]")). + withFallback(ConfigFactory.load()) + + val system = ActorSystem("ClusterSystem", config) + system.actorOf(Props[TransformationBackend], name = "backend") + } +} \ No newline at end of file diff --git a/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/transformation/TransformationFrontend.scala b/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/transformation/TransformationFrontend.scala new file mode 100644 index 0000000000..18d5a25e86 --- /dev/null +++ b/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/transformation/TransformationFrontend.scala @@ -0,0 +1,60 @@ +package sample.cluster.transformation + +import language.postfixOps +import scala.concurrent.duration._ +import akka.actor.Actor +import akka.actor.ActorRef +import akka.actor.ActorSystem +import akka.actor.Props +import akka.actor.Terminated +import akka.pattern.ask +import akka.util.Timeout +import com.typesafe.config.ConfigFactory +import java.util.concurrent.atomic.AtomicInteger + +//#frontend +class TransformationFrontend extends Actor { + + var backends = IndexedSeq.empty[ActorRef] + var jobCounter = 0 + + def receive = { + case job: TransformationJob if backends.isEmpty => + sender ! JobFailed("Service unavailable, try again later", job) + + case job: TransformationJob => + jobCounter += 1 + backends(jobCounter % backends.size) forward job + + case BackendRegistration if !backends.contains(sender) => + context watch sender + backends = backends :+ sender + + case Terminated(a) => + backends = backends.filterNot(_ == a) + } +} +//#frontend + +object TransformationFrontend { + def main(args: Array[String]): Unit = { + // Override the configuration of the port when specified as program argument + val port = if (args.isEmpty) "0" else args(0) + val config = ConfigFactory.parseString(s"akka.remote.netty.tcp.port=$port"). + withFallback(ConfigFactory.parseString("akka.cluster.roles = [frontend]")). + withFallback(ConfigFactory.load()) + + val system = ActorSystem("ClusterSystem", config) + val frontend = system.actorOf(Props[TransformationFrontend], name = "frontend") + + val counter = new AtomicInteger + import system.dispatcher + system.scheduler.schedule(2.seconds, 2.seconds) { + implicit val timeout = Timeout(5 seconds) + (frontend ? TransformationJob("hello-" + counter.incrementAndGet())) onSuccess { + case result => println(result) + } + } + + } +} \ No newline at end of file diff --git a/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/transformation/TransformationMessages.scala b/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/transformation/TransformationMessages.scala new file mode 100644 index 0000000000..0d4ac7c02a --- /dev/null +++ b/akka-samples/akka-sample-cluster-scala/src/main/scala/sample/cluster/transformation/TransformationMessages.scala @@ -0,0 +1,8 @@ +package sample.cluster.transformation + +//#messages +case class TransformationJob(text: String) +case class TransformationResult(text: String) +case class JobFailed(reason: String, job: TransformationJob) +case object BackendRegistration +//#messages diff --git a/akka-samples/akka-sample-cluster/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSingleMasterSpec.scala b/akka-samples/akka-sample-cluster-scala/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSingleMasterSpec.scala similarity index 97% rename from akka-samples/akka-sample-cluster/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSingleMasterSpec.scala rename to akka-samples/akka-sample-cluster-scala/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSingleMasterSpec.scala index 9388a39c40..e9db531ae7 100644 --- a/akka-samples/akka-sample-cluster/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSingleMasterSpec.scala +++ b/akka-samples/akka-sample-cluster-scala/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSingleMasterSpec.scala @@ -42,7 +42,7 @@ object StatsSampleSingleMasterSpecConfig extends MultiNodeConfig { cluster { enabled = on max-nr-of-instances-per-node = 3 - allow-local-routees = off + allow-local-routees = on use-role = compute } } @@ -79,7 +79,7 @@ abstract class StatsSampleSingleMasterSpec extends MultiNodeSpec(StatsSampleSing Cluster(system) join firstAddress - receiveN(3).collect { case MemberUp(m) ⇒ m.address }.toSet must be( + receiveN(3).collect { case MemberUp(m) => m.address }.toSet must be( Set(firstAddress, secondAddress, thirdAddress)) Cluster(system).unsubscribe(testActor) diff --git a/akka-samples/akka-sample-cluster/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSpec.scala b/akka-samples/akka-sample-cluster-scala/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSpec.scala similarity index 98% rename from akka-samples/akka-sample-cluster/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSpec.scala rename to akka-samples/akka-sample-cluster-scala/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSpec.scala index 39b639c289..fae44ea1fa 100644 --- a/akka-samples/akka-sample-cluster/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSpec.scala +++ b/akka-samples/akka-sample-cluster-scala/src/multi-jvm/scala/sample/cluster/stats/StatsSampleSpec.scala @@ -96,7 +96,7 @@ abstract class StatsSampleSpec extends MultiNodeSpec(StatsSampleSpecConfig) system.actorOf(Props[StatsWorker], "statsWorker") system.actorOf(Props[StatsService], "statsService") - receiveN(3).collect { case MemberUp(m) ⇒ m.address }.toSet must be( + receiveN(3).collect { case MemberUp(m) => m.address }.toSet must be( Set(firstAddress, secondAddress, thirdAddress)) Cluster(system).unsubscribe(testActor) diff --git a/akka-samples/akka-sample-cluster/src/multi-jvm/scala/sample/cluster/transformation/TransformationSampleSpec.scala b/akka-samples/akka-sample-cluster-scala/src/multi-jvm/scala/sample/cluster/transformation/TransformationSampleSpec.scala similarity index 98% rename from akka-samples/akka-sample-cluster/src/multi-jvm/scala/sample/cluster/transformation/TransformationSampleSpec.scala rename to akka-samples/akka-sample-cluster-scala/src/multi-jvm/scala/sample/cluster/transformation/TransformationSampleSpec.scala index f68148b3b6..908ee67a1d 100644 --- a/akka-samples/akka-sample-cluster/src/multi-jvm/scala/sample/cluster/transformation/TransformationSampleSpec.scala +++ b/akka-samples/akka-sample-cluster-scala/src/multi-jvm/scala/sample/cluster/transformation/TransformationSampleSpec.scala @@ -66,7 +66,7 @@ abstract class TransformationSampleSpec extends MultiNodeSpec(TransformationSamp transformationFrontend ! TransformationJob("hello") expectMsgPF() { // no backends yet, service unavailble - case JobFailed(_, TransformationJob("hello")) ⇒ + case JobFailed(_, TransformationJob("hello")) => } } diff --git a/akka-samples/akka-sample-cluster-scala/tutorial/index.html b/akka-samples/akka-sample-cluster-scala/tutorial/index.html new file mode 100644 index 0000000000..88e60d1356 --- /dev/null +++ b/akka-samples/akka-sample-cluster-scala/tutorial/index.html @@ -0,0 +1,481 @@ + + +Akka Cluster Samples with Scala + + + + +
+

+This tutorial contains 4 samples illustrating different +Akka cluster features. +

+
    +
  • Subscribe to cluster membership events
  • +
  • Sending messages to actors running on nodes in the cluster
  • +
  • Cluster aware routers
  • +
  • Cluster metrics
  • +
+
+ +
+

A Simple Cluster Example

+ +

+Open application.conf +

+ +

+To enable cluster capabilities in your Akka project you should, at a minimum, add the remote settings, +and use akka.cluster.ClusterActorRefProvider. The akka.cluster.seed-nodes should +normally also be added to your application.conf file. +

+ +

+The seed nodes are configured contact points which newly started nodes will try to connect with in order to join the cluster. +

+ +

+Note that if you are going to start the nodes on different machines you need to specify the +ip-addresses or host names of the machines in application.conf instead of 127.0.0.1. +

+ +

+Open SimpleClusterApp.scala. +

+ +

+The small program together with its configuration starts an ActorSystem with the Cluster enabled. +It joins the cluster and starts an actor that logs some membership events. +Take a look at the +SimpleClusterListener.java +actor. +

+ +

+You can read more about the cluster concepts in the +documentation. +

+ +

+To run this sample, go to the Run +tab, and start the application main class sample.cluster.simple.SimpleClusterApp +if it is not already started. +

+ +

+SimpleClusterApp starts three actor systems (cluster members) in the same JVM process. It can be more +interesting to run them in separate processes. Stop the application in the +Run tab and then open three terminal windows. +

+ +

+In the first terminal window, start the first seed node with the following command (on one line): +

+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.simple.SimpleClusterApp 2551"		
+
+ +

+2551 corresponds to the port of the first seed-nodes element in the configuration. In the log +output you see that the cluster node has been started and changed status to 'Up'. +

+ +

+In the second terminal window, start the second seed node with the following command (on one line): +

+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.simple.SimpleClusterApp 2552"		
+
+ +

+2552 corresponds to the port of the second seed-nodes element in the configuration. In the +log output you see that the cluster node has been started and joins the other seed node and +becomes a member of the cluster. Its status changed to 'Up'. +

+ +

+Switch over to the first terminal window and see in the log output that the member joined. +

+ +

+Start another node in the third terminal window with the following command (on one line): +

+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.simple.SimpleClusterApp 0"		
+
+ +

+Now you don't need to specify the port number, 0 means that it will use a random available port. +It joins one of the configured seed nodes. Look at the log output in the different terminal +windows. +

+ +

+Start even more nodes in the same way, if you like. +

+ +

+Shut down one of the nodes by pressing 'ctrl-c' in one of the terminal windows. +The other nodes will detect the failure after a while, which you can see in the log +output in the other terminals. +

+ +

+Look at the +source code +of the actor again. It registers itself as subscriber of certain cluster events. It gets notified with an snapshot event, +CurrentClusterState that holds full state information of the cluster. After that it receives events for changes +that happen in the cluster. +

+ +
+ +
+

Worker Dial-in Example

+ +

+In the previous sample we saw how to subscribe to cluster membership events. +You can read more about it in the +documentation. +How can cluster membership events be used? +

+ +

+Let's take a look at an example that illustrates how workers, here named backend, +can detect and register to new master nodes, here named frontend. +

+ +

+The example application provides a service to transform text. When some text +is sent to one of the frontend services, it will be delegated to one of the +backend workers, which performs the transformation job, and sends the result back to +the original client. New backend nodes, as well as new frontend nodes, can be +added or removed to the cluster dynamically. +

+ +

+Open TransformationMessages.scala. +It defines the messages that are sent between the actors. +

+ +

+The backend worker that performs the transformation job is defined in +TransformationBackend.scala +

+ +

+Note that the TransformationBackend actor subscribes to cluster events to detect new, +potential, frontend nodes, and send them a registration message so that they know +that they can use the backend worker. +

+ +

+The frontend that receives user jobs and delegates to one of the registered backend workers is defined in +TransformationFrontend.scala +

+ +

+Note that the TransformationFrontend actor watch the registered backend +to be able to remove it from its list of available backend workers. +Death watch uses the cluster failure detector for nodes in the cluster, i.e. it detects +network failures and JVM crashes, in addition to graceful termination of watched +actor. +

+ +

+To run this sample, go to the Run +tab, and start the application main class sample.cluster.transformation.TransformationApp +if it is not already started. +

+ +

+TransformationApp starts +5 actor systems (cluster members) in the same JVM process. It can be more +interesting to run them in separate processes. Stop the application in the +Run tab and run the following commands in separate terminal windows. +

+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.transformation.TransformationFrontend 2551"		
+
+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.transformation.TransformationBackend 2552"		
+
+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.transformation.TransformationBackend 0"		
+
+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.transformation.TransformationBackend 0"		
+
+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.transformation.TransformationFrontend 0"		
+
+ +
+ +
+

Cluster Aware Routers

+ +

+All routers +can be made aware of member nodes in the cluster, i.e. deploying new routees or looking up routees +on nodes in the cluster. +When a node becomes unreachable or leaves the cluster the routees of that node are +automatically unregistered from the router. When new nodes join the cluster additional +routees are added to the router, according to the configuration. Routees are also added +when a node becomes reachable again, after having been unreachable. +

+ +

+You can read more about cluster aware routers in the +documentation. +

+ +

+Let's take a look at a few samples that make use of cluster aware routers. +

+ +
+ +
+

Router Example with Group of Routees

+ +

+Let's take a look at how to use a cluster aware router with a group of routees, +i.e. a router which does not create its routees but instead forwards incoming messages to a given +set of actors created elsewhere. +

+ +

+The example application provides a service to calculate statistics for a text. +When some text is sent to the service it splits it into words, and delegates the task +to count number of characters in each word to a separate worker, a routee of a router. +The character count for each word is sent back to an aggregator that calculates +the average number of characters per word when all results have been collected. +

+ +

+Open StatsMessages.scala. +It defines the messages that are sent between the actors. +

+ +

+The worker that counts number of characters in each word is defined in +StatsWorker.scala. +

+ +

+The service that receives text from users and splits it up into words, delegates to workers and aggregates +is defined in StatsService.scala. +

+ +

+Note, nothing cluster specific so far, just plain actors. +

+ +

+All nodes start StatsService and StatsWorker actors. Remember, routees are the workers in this case. +

+ +

+Open stats1.conf +The router is configured with routees.paths. +This means that user requests can be sent to StatsService on any node and it will use +StatsWorker on all nodes. +

+ +

+To run this sample, go to the Run +tab, and start the application main class sample.cluster.stats.StatsSample +if it is not already started. +

+ +

+StatsSample starts +4 actor systems (cluster members) in the same JVM process. It can be more +interesting to run them in separate processes. Stop the application in the +Run tab and run the following commands in separate terminal windows. +

+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.stats.StatsSample 2551"		
+
+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.stats.StatsSample 2552"		
+
+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.stats.StatsSampleClient"		
+
+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.stats.StatsSample 0"		
+
+ +
+ +
+

Router Example with Pool of Remote Deployed Routees

+ +

+Let's take a look at how to use a cluster aware router on single master node that creates +and deploys workers instead of looking them up. +

+ +

+Open StatsSampleOneMaster.scala. +To keep track of a single master we use the Cluster Singleton +in the contrib module. The ClusterSingletonManager is started on each node. +

+ +

+We also need an actor on each node that keeps track of where current single master exists and +delegates jobs to the StatsService. That is handled by the +StatsFacade.scala +

+ +

+The StatsFacade receives text from users and delegates to the current StatsService, the single +master. It listens to cluster events to lookup the StatsService on the oldest node. +

+ +

+All nodes start StatsFacade and the ClusterSingletonManager. The router is now configured in +stats2.conf +

+ +

+To run this sample, go to the Run +tab, and start the application main class sample.cluster.stats.StatsSampleOneMaster +if it is not already started. +

+ +

+StatsSampleOneMaster starts +4 actor systems (cluster members) in the same JVM process. It can be more +interesting to run them in separate processes. Stop the application in the +Run tab and run the following commands in separate terminal windows. +

+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.stats.StatsSampleOneMaster 2551"		
+
+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.stats.StatsSampleOneMaster 2552"		
+
+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.stats.StatsSampleOneMasterClient"		
+
+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.stats.StatsSampleOneMaster 0"		
+
+ +
+ +
+

Adaptive Load Balancing

+ +

+The member nodes of the cluster collects system health metrics and publishes that to other nodes and to +registered subscribers. This information is primarily used for load-balancing routers, such as +the AdaptiveLoadBalancingPool and AdaptiveLoadBalancingGroup routers. +

+ +

+You can read more about cluster metrics in the +documentation. +

+ +

+Let's take a look at this router in action. What can be more demanding than calculating factorials? +

+ +

+The backend worker that performs the factorial calculation: +FactorialBackend +

+ +

+The frontend that receives user jobs and delegates to the backends via the router: +FactorialFrontend +

+ +

+As you can see, the router is defined in the same way as other routers, and in this case it is configured in: +factorial.conf +

+ +

+It is only router type adaptive and the metrics-selector that is specific to this router, +other things work in the same way as other routers. +

+ +

+To run this sample, go to the Run +tab, and start the application main class sample.cluster.factorial.FactorialApp +if it is not already started. +

+ +

+FactorialApp starts +4 actor systems (cluster members) in the same JVM process. It can be more +interesting to run them in separate processes. Stop the application in the +Run tab and run the following commands in separate terminal windows. +

+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.factorial.FactorialBackend 2551"		
+
+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.factorial.FactorialBackend 2552"		
+
+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.factorial.FactorialBackend 0"		
+
+ +

+<path to activator dir>/activator 
+  "runMain sample.cluster.factorial.FactorialFrontend 0"		
+
+ +

+Press ctrl-c in the terminal window of the frontend to stop the factorial calculations. +

+ +
+ + + diff --git a/akka-samples/akka-sample-cluster/README.md b/akka-samples/akka-sample-cluster/README.md deleted file mode 100644 index 3a6daaca2a..0000000000 --- a/akka-samples/akka-sample-cluster/README.md +++ /dev/null @@ -1,20 +0,0 @@ -Cluster Sample -============== - -This sample is meant to be used by studying the code; it does not perform any -astounding functions when running it. If you want to run it, check out the akka -sources on your local hard drive, follow the [instructions for setting up Akka -with SBT](http://doc.akka.io/docs/akka/current/intro/getting-started.html). -When you start SBT within the checked-out akka source directory, you can run -this sample by typing - - akka-sample-cluster-experimental/run-main sample.cluster.simple.SimpleClusterApp 2551 - -and then from another terminal start more cluster nodes like this: - - akka-sample-cluster-experimental/run-main sample.cluster.simple.SimpleClusterApp - -Then you can start and stop cluster nodes and observe the messages printed by -the remaining ones, demonstrating cluster membership changes. - -You can read more in the [Akka docs](http://akka.io/docs). diff --git a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/factorial/japi/FactorialFrontend.java b/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/factorial/japi/FactorialFrontend.java deleted file mode 100644 index 8b3a0333b9..0000000000 --- a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/factorial/japi/FactorialFrontend.java +++ /dev/null @@ -1,98 +0,0 @@ -package sample.cluster.factorial.japi; - -import java.util.Collections; - -import akka.actor.UntypedActor; -import akka.actor.ActorRef; -import akka.actor.Props; -import akka.event.Logging; -import akka.event.LoggingAdapter; -import akka.routing.FromConfig; -import akka.cluster.routing.AdaptiveLoadBalancingPool; -import akka.cluster.routing.AdaptiveLoadBalancingGroup; -import akka.cluster.routing.ClusterRouterPool; -import akka.cluster.routing.ClusterRouterGroup; -import akka.cluster.routing.ClusterRouterGroupSettings; -import akka.cluster.routing.ClusterRouterPoolSettings; -import akka.cluster.routing.HeapMetricsSelector; -import akka.cluster.routing.SystemLoadAverageMetricsSelector; - -//#frontend -public class FactorialFrontend extends UntypedActor { - final int upToN; - final boolean repeat; - - LoggingAdapter log = Logging.getLogger(getContext().system(), 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(); - } - - @Override - public void onReceive(Object message) { - if (message instanceof FactorialResult) { - FactorialResult result = (FactorialResult) message; - if (result.n == upToN) { - log.debug("{}! = {}", result.n, result.factorial); - if (repeat) sendJobs(); - } - - } else { - unhandled(message); - } - } - - 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 UntypedActor { - //#router-lookup-in-code - int totalInstances = 100; - String routeesPath = "/user/factorialBackend"; - boolean allowLocalRoutees = true; - String useRole = "backend"; - ActorRef backend = getContext().actorOf( - new ClusterRouterGroup( - new AdaptiveLoadBalancingGroup(HeapMetricsSelector.getInstance(), Collections.emptyList()), - new ClusterRouterGroupSettings( - totalInstances, routeesPath, allowLocalRoutees, useRole)).props(), - "factorialBackendRouter2"); - //#router-lookup-in-code -} - -//not used, only for documentation -abstract class FactorialFrontend3 extends UntypedActor { - //#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 -} \ No newline at end of file diff --git a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/simple/japi/SimpleClusterApp.java b/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/simple/japi/SimpleClusterApp.java deleted file mode 100644 index 7aebbdd510..0000000000 --- a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/simple/japi/SimpleClusterApp.java +++ /dev/null @@ -1,28 +0,0 @@ -package sample.cluster.simple.japi; - -import akka.actor.ActorRef; -import akka.actor.ActorSystem; -import akka.actor.Props; -import akka.cluster.Cluster; -import akka.cluster.ClusterEvent.ClusterDomainEvent; - -public class SimpleClusterApp { - - public static void main(String[] args) { - // Override the configuration of the port - // when specified as program argument - if (args.length > 0) - System.setProperty("akka.remote.netty.tcp.port", args[0]); - - // Create an Akka system - ActorSystem system = ActorSystem.create("ClusterSystem"); - - // Create an actor that handles cluster domain events - ActorRef clusterListener = system.actorOf(Props.create( - SimpleClusterListener.class), "clusterListener"); - - // Add subscription of cluster events - Cluster.get(system).subscribe(clusterListener, - ClusterDomainEvent.class); - } -} diff --git a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsSampleClientMain.java b/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsSampleClientMain.java deleted file mode 100644 index 55152d034f..0000000000 --- a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsSampleClientMain.java +++ /dev/null @@ -1,13 +0,0 @@ -package sample.cluster.stats.japi; - -import akka.actor.ActorSystem; -import akka.actor.Props; - -public class StatsSampleClientMain { - - public static void main(String[] args) throws Exception { - // note that client is not a compute node, role not defined - ActorSystem system = ActorSystem.create("ClusterSystem"); - system.actorOf(Props.create(StatsSampleClient.class, "/user/statsService"), "client"); - } -} diff --git a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsSampleMain.java b/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsSampleMain.java deleted file mode 100644 index 7a9d6cca16..0000000000 --- a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsSampleMain.java +++ /dev/null @@ -1,25 +0,0 @@ -package sample.cluster.stats.japi; - -import com.typesafe.config.Config; -import com.typesafe.config.ConfigFactory; -import akka.actor.ActorSystem; -import akka.actor.Props; - -public class StatsSampleMain { - - public static void main(String[] args) throws Exception { - // Override the configuration of the port when specified as program argument - final Config config = - (args.length > 0 ? - ConfigFactory.parseString(String.format("akka.remote.netty.tcp.port=%s", args[0])) : - ConfigFactory.empty()). - withFallback(ConfigFactory.parseString("akka.cluster.roles = [compute]")). - withFallback(ConfigFactory.load()); - - ActorSystem system = ActorSystem.create("ClusterSystem", config); - - system.actorOf(Props.create(StatsWorker.class), "statsWorker"); - system.actorOf(Props.create(StatsService.class), "statsService"); - - } -} diff --git a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsSampleOneMasterMain.java b/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsSampleOneMasterMain.java deleted file mode 100644 index 256032fe21..0000000000 --- a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsSampleOneMasterMain.java +++ /dev/null @@ -1,33 +0,0 @@ -package sample.cluster.stats.japi; - -import com.typesafe.config.Config; -import com.typesafe.config.ConfigFactory; -import akka.actor.ActorSystem; -import akka.actor.PoisonPill; -import akka.actor.Props; -import akka.contrib.pattern.ClusterSingletonManager; - -public class StatsSampleOneMasterMain { - - public static void main(String[] args) throws Exception { - // Override the configuration of the port when specified as program argument - final Config config = - (args.length > 0 ? - ConfigFactory.parseString(String.format("akka.remote.netty.tcp.port=%s", args[0])) : - ConfigFactory.empty()). - withFallback(ConfigFactory.parseString("akka.cluster.roles = [compute]")). - withFallback(ConfigFactory.load()); - - ActorSystem system = ActorSystem.create("ClusterSystem", config); - - //#create-singleton-manager - system.actorOf(ClusterSingletonManager.defaultProps( - Props.create(StatsService.class), - "statsService", PoisonPill.getInstance(), "compute"), - "singleton"); - //#create-singleton-manager - - system.actorOf(Props.create(StatsFacade.class), "statsFacade"); - - } -} diff --git a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsService.java b/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsService.java deleted file mode 100644 index 4e490925ca..0000000000 --- a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/stats/japi/StatsService.java +++ /dev/null @@ -1,89 +0,0 @@ -package sample.cluster.stats.japi; - -import java.util.Collections; - -import sample.cluster.stats.japi.StatsMessages.StatsJob; - -//#imports -import akka.actor.ActorRef; -import akka.actor.Props; -import akka.actor.UntypedActor; -import akka.cluster.routing.ClusterRouterGroup; -import akka.cluster.routing.ClusterRouterPool; -import akka.cluster.routing.ClusterRouterGroupSettings; -import akka.cluster.routing.ClusterRouterPoolSettings; -import akka.routing.ConsistentHashingGroup; -import akka.routing.ConsistentHashingPool; -import akka.routing.ConsistentHashingRouter.ConsistentHashableEnvelope; -import akka.routing.FromConfig; -//#imports - -//#service -public class StatsService extends UntypedActor { - - // 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 void onReceive(Object message) { - if (message instanceof StatsJob) { - StatsJob job = (StatsJob) message; - if (job.getText().equals("")) { - unhandled(message); - } else { - final String[] words = job.getText().split(" "); - final 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); - } - } - - } else { - unhandled(message); - } - } -} - -//#service - -//not used, only for documentation -abstract class StatsService2 extends UntypedActor { - //#router-lookup-in-code - int totalInstances = 100; - Iterable 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 UntypedActor { - //#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 -} diff --git a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/transformation/japi/TransformationBackendMain.java b/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/transformation/japi/TransformationBackendMain.java deleted file mode 100644 index 804469e77b..0000000000 --- a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/transformation/japi/TransformationBackendMain.java +++ /dev/null @@ -1,20 +0,0 @@ -package sample.cluster.transformation.japi; - -import akka.actor.ActorSystem; -import akka.actor.Props; - -public class TransformationBackendMain { - - public static void main(String[] args) throws Exception { - // Override the configuration of the port - // when specified as program argument - if (args.length > 0) - System.setProperty("akka.remote.netty.tcp.port", args[0]); - - ActorSystem system = ActorSystem.create("ClusterSystem"); - - system.actorOf(Props.create(TransformationBackend.class), "backend"); - - } - -} diff --git a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/transformation/japi/TransformationFrontendMain.java b/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/transformation/japi/TransformationFrontendMain.java deleted file mode 100644 index 8a11f78600..0000000000 --- a/akka-samples/akka-sample-cluster/src/main/java/sample/cluster/transformation/japi/TransformationFrontendMain.java +++ /dev/null @@ -1,45 +0,0 @@ -package sample.cluster.transformation.japi; - -import java.util.concurrent.TimeUnit; - -import sample.cluster.transformation.japi.TransformationMessages.TransformationJob; -import scala.concurrent.ExecutionContext; -import scala.concurrent.duration.Duration; -import akka.actor.ActorRef; -import akka.actor.ActorSystem; -import akka.actor.Props; -import akka.dispatch.OnSuccess; -import akka.util.Timeout; -import static akka.pattern.Patterns.ask; - -public class TransformationFrontendMain { - - public static void main(String[] args) throws Exception { - // Override the configuration of the port - // when specified as program argument - if (args.length > 0) - System.setProperty("akka.remote.netty.tcp.port", args[0]); - - ActorSystem system = ActorSystem.create("ClusterSystem"); - - ActorRef frontend = system.actorOf(Props.create( - TransformationFrontend.class), "frontend"); - Timeout timeout = new Timeout(Duration.create(5, TimeUnit.SECONDS)); - final ExecutionContext ec = system.dispatcher(); - for (int n = 1; n <= 120; n++) { - ask(frontend, new TransformationJob("hello-" + n), timeout) - .onSuccess(new OnSuccess() { - public void onSuccess(Object result) { - System.out.println(result); - } - }, ec); - - // wait a while until next request, - // to avoid flooding the console with output - Thread.sleep(2000); - } - system.shutdown(); - - } - -} diff --git a/akka-samples/akka-sample-cluster/src/main/resources/application.conf b/akka-samples/akka-sample-cluster/src/main/resources/application.conf deleted file mode 100644 index 2b9eb7ae19..0000000000 --- a/akka-samples/akka-sample-cluster/src/main/resources/application.conf +++ /dev/null @@ -1,71 +0,0 @@ -# //#cluster -akka { - actor { - provider = "akka.cluster.ClusterActorRefProvider" - } - remote { - log-remote-lifecycle-events = off - netty.tcp { - hostname = "127.0.0.1" - port = 0 - } - } - - cluster { - seed-nodes = [ - "akka.tcp://ClusterSystem@127.0.0.1:2551", - "akka.tcp://ClusterSystem@127.0.0.1:2552"] - - auto-down-unreachable-after = 10s - } -} -# //#cluster - -# //#config-router-lookup -akka.actor.deployment { - /statsService/workerRouter { - router = consistent-hashing-group - nr-of-instances = 100 - routees.paths = ["/user/statsWorker"] - cluster { - enabled = on - allow-local-routees = on - use-role = compute - } - } -} -# //#config-router-lookup - -# //#config-router-deploy -akka.actor.deployment { - /singleton/statsService/workerRouter { - router = consistent-hashing-pool - nr-of-instances = 100 - cluster { - enabled = on - max-nr-of-instances-per-node = 3 - allow-local-routees = off - use-role = compute - } - } -} -# //#config-router-deploy - -# //#adaptive-router -akka.actor.deployment { - /factorialFrontend/factorialBackendRouter = { - router = adaptive-group - # metrics-selector = heap - # metrics-selector = load - # metrics-selector = cpu - metrics-selector = mix - nr-of-instances = 100 - routees.paths = ["/user/factorialBackend"] - cluster { - enabled = on - use-role = backend - allow-local-routees = off - } - } -} -# //#adaptive-router \ No newline at end of file diff --git a/akka-samples/akka-sample-cluster/src/main/resources/factorial.conf b/akka-samples/akka-sample-cluster/src/main/resources/factorial.conf deleted file mode 100644 index e0c79671b6..0000000000 --- a/akka-samples/akka-sample-cluster/src/main/resources/factorial.conf +++ /dev/null @@ -1,12 +0,0 @@ -include "application" - -# //#min-nr-of-members -akka.cluster.min-nr-of-members = 3 -# //#min-nr-of-members - -# //#role-min-nr-of-members -akka.cluster.role { - frontend.min-nr-of-members = 1 - backend.min-nr-of-members = 2 -} -# //#role-min-nr-of-members \ No newline at end of file diff --git a/akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/factorial/FactorialSample.scala b/akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/factorial/FactorialSample.scala deleted file mode 100644 index f08d37ac6e..0000000000 --- a/akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/factorial/FactorialSample.scala +++ /dev/null @@ -1,173 +0,0 @@ -package sample.cluster.factorial - -//#imports -import scala.annotation.tailrec -import scala.concurrent.Future -import com.typesafe.config.ConfigFactory -import akka.actor.Actor -import akka.actor.ActorLogging -import akka.actor.ActorRef -import akka.actor.ActorSystem -import akka.actor.Props -import akka.pattern.pipe -import akka.routing.FromConfig - -//#imports - -import akka.cluster.Cluster -import akka.cluster.ClusterEvent.CurrentClusterState -import akka.cluster.ClusterEvent.MemberUp - -object FactorialFrontend { - def main(args: Array[String]): Unit = { - val upToN = if (args.isEmpty) 200 else args(0).toInt - - val config = ConfigFactory.parseString("akka.cluster.roles = [frontend]"). - withFallback(ConfigFactory.load("factorial")) - - val system = ActorSystem("ClusterSystem", config) - system.log.info("Factorials will start when 2 backend members in the cluster.") - //#registerOnUp - Cluster(system) registerOnMemberUp { - system.actorOf(Props(classOf[FactorialFrontend], upToN, true), - name = "factorialFrontend") - } - //#registerOnUp - } -} - -//#frontend -class FactorialFrontend(upToN: Int, repeat: Boolean) extends Actor with ActorLogging { - - val backend = context.actorOf(FromConfig.props(), - name = "factorialBackendRouter") - - override def preStart(): Unit = sendJobs() - - def receive = { - case (n: Int, factorial: BigInt) ⇒ - if (n == upToN) { - log.debug("{}! = {}", n, factorial) - if (repeat) sendJobs() - } - } - - def sendJobs(): Unit = { - log.info("Starting batch of factorials up to [{}]", upToN) - 1 to upToN foreach { backend ! _ } - } -} -//#frontend - -object FactorialBackend { - def main(args: Array[String]): Unit = { - // Override the configuration of the port when specified as program argument - val config = - (if (args.nonEmpty) ConfigFactory.parseString(s"akka.remote.netty.tcp.port=${args(0)}") - else ConfigFactory.empty).withFallback( - ConfigFactory.parseString("akka.cluster.roles = [backend]")). - withFallback(ConfigFactory.load("factorial")) - - val system = ActorSystem("ClusterSystem", config) - system.actorOf(Props[FactorialBackend], name = "factorialBackend") - - system.actorOf(Props[MetricsListener], name = "metricsListener") - } -} - -//#backend -class FactorialBackend extends Actor with ActorLogging { - - import context.dispatcher - - def receive = { - case (n: Int) ⇒ - Future(factorial(n)) map { result ⇒ (n, result) } pipeTo sender - } - - def factorial(n: Int): BigInt = { - @tailrec def factorialAcc(acc: BigInt, n: Int): BigInt = { - if (n <= 1) acc - else factorialAcc(acc * n, n - 1) - } - factorialAcc(BigInt(1), n) - } - -} -//#backend - -//#metrics-listener -import akka.cluster.Cluster -import akka.cluster.ClusterEvent.ClusterMetricsChanged -import akka.cluster.ClusterEvent.CurrentClusterState -import akka.cluster.NodeMetrics -import akka.cluster.StandardMetrics.HeapMemory -import akka.cluster.StandardMetrics.Cpu - -class MetricsListener extends Actor with ActorLogging { - val selfAddress = Cluster(context.system).selfAddress - - // subscribe to ClusterMetricsChanged - // re-subscribe when restart - override def preStart(): Unit = - Cluster(context.system).subscribe(self, classOf[ClusterMetricsChanged]) - override def postStop(): Unit = - Cluster(context.system).unsubscribe(self) - - def receive = { - case ClusterMetricsChanged(clusterMetrics) ⇒ - clusterMetrics.filter(_.address == selfAddress) foreach { nodeMetrics ⇒ - logHeap(nodeMetrics) - logCpu(nodeMetrics) - } - case state: CurrentClusterState ⇒ // ignore - } - - def logHeap(nodeMetrics: NodeMetrics): Unit = nodeMetrics match { - case HeapMemory(address, timestamp, used, committed, max) ⇒ - log.info("Used heap: {} MB", used.doubleValue / 1024 / 1024) - case _ ⇒ // no heap info - } - - def logCpu(nodeMetrics: NodeMetrics): Unit = nodeMetrics match { - case Cpu(address, timestamp, Some(systemLoadAverage), cpuCombined, processors) ⇒ - log.info("Load: {} ({} processors)", systemLoadAverage, processors) - case _ ⇒ // no cpu info - } -} - -//#metrics-listener - -// not used, only for documentation -abstract class FactorialFrontend2 extends Actor { - //#router-lookup-in-code - import akka.cluster.routing.ClusterRouterGroup - import akka.cluster.routing.ClusterRouterGroupSettings - import akka.cluster.routing.AdaptiveLoadBalancingGroup - import akka.cluster.routing.HeapMetricsSelector - - val backend = context.actorOf( - ClusterRouterGroup(AdaptiveLoadBalancingGroup(HeapMetricsSelector), - ClusterRouterGroupSettings( - totalInstances = 100, routeesPaths = List("/user/factorialBackend"), - allowLocalRoutees = true, useRole = Some("backend"))).props(), - name = "factorialBackendRouter2") - //#router-lookup-in-code -} - -// not used, only for documentation -abstract class FactorialFrontend3 extends Actor { - //#router-deploy-in-code - import akka.cluster.routing.ClusterRouterPool - import akka.cluster.routing.ClusterRouterPoolSettings - import akka.cluster.routing.AdaptiveLoadBalancingPool - import akka.cluster.routing.SystemLoadAverageMetricsSelector - - val backend = context.actorOf( - ClusterRouterPool(AdaptiveLoadBalancingPool( - SystemLoadAverageMetricsSelector), ClusterRouterPoolSettings( - totalInstances = 100, maxInstancesPerNode = 3, - allowLocalRoutees = false, useRole = Some("backend"))).props(Props[FactorialBackend]), - name = "factorialBackendRouter3") - //#router-deploy-in-code -} \ No newline at end of file diff --git a/akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/simple/SimpleClusterApp.scala b/akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/simple/SimpleClusterApp.scala deleted file mode 100644 index 397ffaf3b6..0000000000 --- a/akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/simple/SimpleClusterApp.scala +++ /dev/null @@ -1,36 +0,0 @@ -package sample.cluster.simple - -import akka.actor._ -import akka.cluster.Cluster -import akka.cluster.ClusterEvent._ - -object SimpleClusterApp { - def main(args: Array[String]): Unit = { - - // Override the configuration of the port - // when specified as program argument - if (args.nonEmpty) System.setProperty("akka.remote.netty.tcp.port", args(0)) - - // Create an Akka system - val system = ActorSystem("ClusterSystem") - val clusterListener = system.actorOf(Props[SimpleClusterListener], - name = "clusterListener") - - Cluster(system).subscribe(clusterListener, classOf[ClusterDomainEvent]) - } -} - -class SimpleClusterListener extends Actor with ActorLogging { - def receive = { - case state: CurrentClusterState ⇒ - log.info("Current members: {}", state.members.mkString(", ")) - case MemberUp(member) ⇒ - log.info("Member is Up: {}", member.address) - case UnreachableMember(member) ⇒ - log.info("Member detected as unreachable: {}", member) - case MemberRemoved(member, previousStatus) ⇒ - log.info("Member is Removed: {} after {}", - member.address, previousStatus) - case _: ClusterDomainEvent ⇒ // ignore - } -} \ No newline at end of file diff --git a/akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/stats/StatsSample.scala b/akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/stats/StatsSample.scala deleted file mode 100644 index 888eec38d2..0000000000 --- a/akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/stats/StatsSample.scala +++ /dev/null @@ -1,252 +0,0 @@ -package sample.cluster.stats - -//#imports -import language.postfixOps -import scala.collection.immutable -import scala.concurrent.forkjoin.ThreadLocalRandom -import scala.concurrent.duration._ -import com.typesafe.config.ConfigFactory -import akka.actor.Actor -import akka.actor.ActorLogging -import akka.actor.ActorRef -import akka.actor.ActorSelection -import akka.actor.ActorSystem -import akka.actor.Address -import akka.actor.PoisonPill -import akka.actor.Props -import akka.actor.ReceiveTimeout -import akka.actor.RelativeActorPath -import akka.actor.RootActorPath -import akka.cluster.Cluster -import akka.cluster.ClusterEvent._ -import akka.cluster.MemberStatus -import akka.cluster.Member -import akka.contrib.pattern.ClusterSingletonManager -import akka.routing.FromConfig -import akka.routing.ConsistentHashingRouter.ConsistentHashableEnvelope -//#imports - -//#messages -case class StatsJob(text: String) -case class StatsResult(meanWordLength: Double) -case class JobFailed(reason: String) -//#messages - -//#service -class StatsService extends Actor { - // 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[StatsWorker.class]. - val workerRouter = context.actorOf(FromConfig.props(Props[StatsWorker]), - name = "workerRouter") - - def receive = { - case StatsJob(text) if text != "" ⇒ - val words = text.split(" ") - val replyTo = sender // important to not close over sender - // create actor that collects replies from workers - val aggregator = context.actorOf(Props( - classOf[StatsAggregator], words.size, replyTo)) - words foreach { word ⇒ - workerRouter.tell( - ConsistentHashableEnvelope(word, word), aggregator) - } - } -} - -class StatsAggregator(expectedResults: Int, replyTo: ActorRef) extends Actor { - var results = IndexedSeq.empty[Int] - context.setReceiveTimeout(3 seconds) - - def receive = { - case wordCount: Int ⇒ - results = results :+ wordCount - if (results.size == expectedResults) { - val meanWordLength = results.sum.toDouble / results.size - replyTo ! StatsResult(meanWordLength) - context.stop(self) - } - case ReceiveTimeout ⇒ - replyTo ! JobFailed("Service unavailable, try again later") - context.stop(self) - } -} -//#service - -//#worker -class StatsWorker extends Actor { - var cache = Map.empty[String, Int] - def receive = { - case word: String ⇒ - val length = cache.get(word) match { - case Some(x) ⇒ x - case None ⇒ - val x = word.length - cache += (word -> x) - x - } - - sender ! length - } -} -//#worker - -//#facade -class StatsFacade extends Actor with ActorLogging { - import context.dispatcher - val cluster = Cluster(context.system) - - // sort by age, oldest first - val ageOrdering = Ordering.fromLessThan[Member] { (a, b) ⇒ a.isOlderThan(b) } - var membersByAge: immutable.SortedSet[Member] = immutable.SortedSet.empty(ageOrdering) - - // subscribe to cluster changes - // re-subscribe when restart - override def preStart(): Unit = cluster.subscribe(self, classOf[MemberEvent]) - override def postStop(): Unit = cluster.unsubscribe(self) - - def receive = { - case job: StatsJob if membersByAge.isEmpty ⇒ - sender ! JobFailed("Service unavailable, try again later") - case job: StatsJob ⇒ - currentMaster.tell(job, sender) - case state: CurrentClusterState ⇒ - membersByAge = immutable.SortedSet.empty(ageOrdering) ++ state.members.collect { - case m if m.hasRole("compute") ⇒ m - } - case MemberUp(m) ⇒ if (m.hasRole("compute")) membersByAge += m - case MemberRemoved(m, _) ⇒ if (m.hasRole("compute")) membersByAge -= m - case _: MemberEvent ⇒ // not interesting - } - - def currentMaster: ActorSelection = - context.actorSelection(RootActorPath(membersByAge.head.address) / - "user" / "singleton" / "statsService") - -} -//#facade - -object StatsSample { - def main(args: Array[String]): Unit = { - // Override the configuration of the port when specified as program argument - val config = - (if (args.nonEmpty) ConfigFactory.parseString(s"akka.remote.netty.tcp.port=${args(0)}") - else ConfigFactory.empty).withFallback( - ConfigFactory.parseString("akka.cluster.roles = [compute]")). - withFallback(ConfigFactory.load()) - - val system = ActorSystem("ClusterSystem", config) - - system.actorOf(Props[StatsWorker], name = "statsWorker") - system.actorOf(Props[StatsService], name = "statsService") - } -} - -object StatsSampleOneMaster { - def main(args: Array[String]): Unit = { - // Override the configuration of the port when specified as program argument - val config = - (if (args.nonEmpty) ConfigFactory.parseString(s"akka.remote.netty.tcp.port=${args(0)}") - else ConfigFactory.empty).withFallback( - ConfigFactory.parseString("akka.cluster.roles = [compute]")). - withFallback(ConfigFactory.load()) - - val system = ActorSystem("ClusterSystem", config) - - //#create-singleton-manager - system.actorOf(ClusterSingletonManager.props( - singletonProps = Props[StatsService], singletonName = "statsService", - terminationMessage = PoisonPill, role = Some("compute")), - name = "singleton") - //#create-singleton-manager - system.actorOf(Props[StatsFacade], name = "statsFacade") - } -} - -object StatsSampleClient { - def main(args: Array[String]): Unit = { - // note that client is not a compute node, role not defined - val system = ActorSystem("ClusterSystem") - system.actorOf(Props(classOf[StatsSampleClient], "/user/statsService"), "client") - } -} - -object StatsSampleOneMasterClient { - def main(args: Array[String]): Unit = { - // note that client is not a compute node, role not defined - val system = ActorSystem("ClusterSystem") - system.actorOf(Props(classOf[StatsSampleClient], "/user/statsFacade"), "client") - } -} - -class StatsSampleClient(servicePath: String) extends Actor { - val cluster = Cluster(context.system) - val servicePathElements = servicePath match { - case RelativeActorPath(elements) ⇒ elements - case _ ⇒ throw new IllegalArgumentException( - "servicePath [%s] is not a valid relative actor path" format servicePath) - } - import context.dispatcher - val tickTask = context.system.scheduler.schedule(2 seconds, 2 seconds, self, "tick") - - var nodes = Set.empty[Address] - - override def preStart(): Unit = { - cluster.subscribe(self, classOf[MemberEvent]) - cluster.subscribe(self, classOf[UnreachableMember]) - } - override def postStop(): Unit = { - cluster.unsubscribe(self) - tickTask.cancel() - } - - def receive = { - case "tick" if nodes.nonEmpty ⇒ - // just pick any one - val address = nodes.toIndexedSeq(ThreadLocalRandom.current.nextInt(nodes.size)) - val service = context.actorSelection(RootActorPath(address) / servicePathElements) - service ! StatsJob("this is the text that will be analyzed") - case result: StatsResult ⇒ - println(result) - case failed: JobFailed ⇒ - println(failed) - case state: CurrentClusterState ⇒ - nodes = state.members.collect { - case m if m.hasRole("compute") && m.status == MemberStatus.Up ⇒ m.address - } - case MemberUp(m) if m.hasRole("compute") ⇒ nodes += m.address - case other: MemberEvent ⇒ nodes -= other.member.address - case UnreachableMember(m) ⇒ nodes -= m.address - } - -} - -// not used, only for documentation -abstract class StatsService2 extends Actor { - //#router-lookup-in-code - import akka.cluster.routing.ClusterRouterGroup - import akka.cluster.routing.ClusterRouterGroupSettings - import akka.routing.ConsistentHashingGroup - - val workerRouter = context.actorOf( - ClusterRouterGroup(ConsistentHashingGroup(Nil), ClusterRouterGroupSettings( - totalInstances = 100, routeesPaths = List("/user/statsWorker"), - allowLocalRoutees = true, useRole = Some("compute"))).props(), - name = "workerRouter2") - //#router-lookup-in-code -} - -// not used, only for documentation -abstract class StatsService3 extends Actor { - //#router-deploy-in-code - import akka.cluster.routing.ClusterRouterPool - import akka.cluster.routing.ClusterRouterPoolSettings - import akka.routing.ConsistentHashingPool - - val workerRouter = context.actorOf( - ClusterRouterPool(ConsistentHashingPool(0), ClusterRouterPoolSettings( - totalInstances = 100, maxInstancesPerNode = 3, - allowLocalRoutees = false, useRole = None)).props(Props[StatsWorker]), - name = "workerRouter3") - //#router-deploy-in-code -} diff --git a/akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/transformation/TransformationSample.scala b/akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/transformation/TransformationSample.scala deleted file mode 100644 index c05edd01d1..0000000000 --- a/akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/transformation/TransformationSample.scala +++ /dev/null @@ -1,115 +0,0 @@ -package sample.cluster.transformation - -//#imports -import language.postfixOps -import scala.concurrent.duration._ -import akka.actor.Actor -import akka.actor.ActorRef -import akka.actor.ActorSystem -import akka.actor.Props -import akka.actor.RootActorPath -import akka.actor.Terminated -import akka.cluster.Cluster -import akka.cluster.ClusterEvent.CurrentClusterState -import akka.cluster.ClusterEvent.MemberUp -import akka.cluster.Member -import akka.cluster.MemberStatus -import akka.pattern.ask -import akka.util.Timeout -import com.typesafe.config.ConfigFactory -//#imports - -//#messages -case class TransformationJob(text: String) -case class TransformationResult(text: String) -case class JobFailed(reason: String, job: TransformationJob) -case object BackendRegistration -//#messages - -object TransformationFrontend { - def main(args: Array[String]): Unit = { - // Override the configuration of the port when specified as program argument - val config = - (if (args.nonEmpty) ConfigFactory.parseString(s"akka.remote.netty.tcp.port=${args(0)}") - else ConfigFactory.empty).withFallback( - ConfigFactory.parseString("akka.cluster.roles = [frontend]")). - withFallback(ConfigFactory.load()) - - val system = ActorSystem("ClusterSystem", config) - val frontend = system.actorOf(Props[TransformationFrontend], name = "frontend") - - import system.dispatcher - implicit val timeout = Timeout(5 seconds) - for (n ← 1 to 120) { - (frontend ? TransformationJob("hello-" + n)) onSuccess { - case result ⇒ println(result) - } - // wait a while until next request, - // to avoid flooding the console with output - Thread.sleep(2000) - } - system.shutdown() - } -} - -//#frontend -class TransformationFrontend extends Actor { - - var backends = IndexedSeq.empty[ActorRef] - var jobCounter = 0 - - def receive = { - case job: TransformationJob if backends.isEmpty ⇒ - sender ! JobFailed("Service unavailable, try again later", job) - - case job: TransformationJob ⇒ - jobCounter += 1 - backends(jobCounter % backends.size) forward job - - case BackendRegistration if !backends.contains(sender) ⇒ - context watch sender - backends = backends :+ sender - - case Terminated(a) ⇒ - backends = backends.filterNot(_ == a) - } -} -//#frontend - -object TransformationBackend { - def main(args: Array[String]): Unit = { - // Override the configuration of the port when specified as program argument - val config = - (if (args.nonEmpty) ConfigFactory.parseString(s"akka.remote.netty.tcp.port=${args(0)}") - else ConfigFactory.empty).withFallback( - ConfigFactory.parseString("akka.cluster.roles = [backend]")). - withFallback(ConfigFactory.load()) - - val system = ActorSystem("ClusterSystem", config) - system.actorOf(Props[TransformationBackend], name = "backend") - } -} - -//#backend -class TransformationBackend extends Actor { - - val cluster = Cluster(context.system) - - // subscribe to cluster changes, MemberUp - // re-subscribe when restart - override def preStart(): Unit = cluster.subscribe(self, classOf[MemberUp]) - override def postStop(): Unit = cluster.unsubscribe(self) - - def receive = { - case TransformationJob(text) ⇒ sender ! TransformationResult(text.toUpperCase) - case state: CurrentClusterState ⇒ - state.members.filter(_.status == MemberStatus.Up) foreach register - case MemberUp(m) ⇒ register(m) - } - - def register(member: Member): Unit = - if (member.hasRole("frontend")) - context.actorSelection(RootActorPath(member.address) / "user" / "frontend") ! - BackendRegistration -} -//#backend \ No newline at end of file diff --git a/project/AkkaBuild.scala b/project/AkkaBuild.scala index 06541fd38c..59863198fb 100644 --- a/project/AkkaBuild.scala +++ b/project/AkkaBuild.scala @@ -446,7 +446,10 @@ object AkkaBuild extends Build { id = "akka-samples", base = file("akka-samples"), settings = parentSettings, - aggregate = Seq(camelSampleJava, camelSampleScala, fsmSample, mainSampleJava, mainSampleScala, helloKernelSample, remoteSampleJava, remoteSampleScala, persistenceSample, clusterSample, multiNodeSample, osgiDiningHakkersSample) + aggregate = Seq(camelSampleJava, camelSampleScala, mainSampleJava, mainSampleScala, + remoteSampleJava, remoteSampleScala, clusterSampleJava, clusterSampleScala, + fsmSample, persistenceSample, + multiNodeSample, helloKernelSample, osgiDiningHakkersSample) ) lazy val camelSampleJava = Project( @@ -512,9 +515,27 @@ object AkkaBuild extends Build { settings = sampleSettings ) - lazy val clusterSample = Project( - id = "akka-sample-cluster", - base = file("akka-samples/akka-sample-cluster"), + lazy val clusterSampleJava = Project( + id = "akka-sample-cluster-java", + base = file("akka-samples/akka-sample-cluster-java"), + dependencies = Seq(cluster, contrib, remoteTests % "test", testkit % "test"), + settings = sampleSettings ++ multiJvmSettings ++ Seq( + libraryDependencies ++= Dependencies.clusterSample, + javaOptions in run ++= Seq( + "-Djava.library.path=./sigar", + "-Xms128m", "-Xmx1024m"), + Keys.fork in run := true, + // disable parallel tests + parallelExecution in Test := false, + extraOptions in MultiJvm <<= (sourceDirectory in MultiJvm) { src => + (name: String) => (src ** (name + ".conf")).get.headOption.map("-Dakka.config=" + _.absolutePath).toSeq + } + ) + ) configs (MultiJvm) + + lazy val clusterSampleScala = Project( + id = "akka-sample-cluster-scala", + base = file("akka-samples/akka-sample-cluster-scala"), dependencies = Seq(cluster, contrib, remoteTests % "test", testkit % "test"), settings = sampleSettings ++ multiJvmSettings ++ Seq( libraryDependencies ++= Dependencies.clusterSample,