=doc #3689 Make activator templates for cluster samples
This commit is contained in:
parent
b82698a354
commit
37f8f2831b
135 changed files with 2650 additions and 1461 deletions
|
|
@ -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 <http://typesafe.com/platform/getstarted>`_
|
||||
and open the tutorial named `Akka Cluster Samples with Java <http://typesafe.com/activator/template/akka-sample-cluster-java>`_.
|
||||
It contains instructions of how to run the <code>SimpleClusterApp</code>.
|
||||
|
||||
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 <http://typesafe.com/platform/getstarted>`_ tutorial named
|
||||
`Akka Cluster Samples with Java <http://typesafe.com/activator/template/akka-sample-cluster-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 <http://typesafe.com/platform/getstarted>`_ tutorial named
|
||||
`Akka Cluster Samples with Java <http://typesafe.com/activator/template/akka-sample-cluster-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 <http://typesafe.com/platform/getstarted>`_ tutorial named
|
||||
`Akka Cluster Samples with Java <http://typesafe.com/activator/template/akka-sample-cluster-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 <http://en.wikipedia.org/wiki/Moving_average#Exponential_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 <http://typesafe.com/platform/getstarted>`_ tutorial named
|
||||
`Akka Cluster Samples with Java <http://typesafe.com/activator/template/akka-sample-cluster-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
|
||||
------------------------
|
||||
|
|
|
|||
|
|
@ -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 <http://typesafe.com/platform/getstarted>`_
|
||||
and open the tutorial named `Akka Cluster Samples with Scala <http://typesafe.com/activator/template/akka-sample-cluster-scala>`_.
|
||||
It contains instructions of how to run the <code>SimpleClusterApp</code>.
|
||||
|
||||
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 <http://typesafe.com/platform/getstarted>`_ tutorial named
|
||||
`Akka Cluster Samples with Scala <http://typesafe.com/activator/template/akka-sample-cluster-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 <http://typesafe.com/platform/getstarted>`_ tutorial named
|
||||
`Akka Cluster Samples with Scala <http://typesafe.com/activator/template/akka-sample-cluster-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 <http://typesafe.com/platform/getstarted>`_ tutorial named
|
||||
`Akka Cluster Samples with Scala <http://typesafe.com/activator/template/akka-sample-cluster-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 <http://en.wikipedia.org/wiki/Moving_average#Exponential_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 <http://typesafe.com/platform/getstarted>`_ tutorial named
|
||||
`Akka Cluster Samples with Scala <http://typesafe.com/activator/template/akka-sample-cluster-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 <akka-testkit>` is added as :ref:`subscriber <cluster_subscriber_scala>`
|
||||
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 <akka-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:
|
||||
|
|
|
|||
17
akka-samples/akka-sample-cluster-java/.gitignore
vendored
Normal file
17
akka-samples/akka-sample-cluster-java/.gitignore
vendored
Normal file
|
|
@ -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
|
||||
13
akka-samples/akka-sample-cluster-java/LICENSE
Normal file
13
akka-samples/akka-sample-cluster-java/LICENSE
Normal file
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
32
akka-samples/akka-sample-cluster-java/project/Build.scala
Normal file
32
akka-samples/akka-sample-cluster-java/project/Build.scala
Normal file
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
sbt.version=0.13.0
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
resolvers += Classpaths.typesafeResolver
|
||||
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-multi-jvm" % "0.3.8")
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
/**
|
||||
* Copyright (C) 2011 Typesafe <http://typesafe.com/>
|
||||
*/
|
||||
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<String> routeesPaths = Arrays.asList("/user/factorialBackend", "");
|
||||
boolean allowLocalRoutees = true;
|
||||
String useRole = "backend";
|
||||
ActorRef backend = getContext().actorOf(
|
||||
new ClusterRouterGroup(new AdaptiveLoadBalancingGroup(
|
||||
HeapMetricsSelector.getInstance(), Collections.<String> emptyList()),
|
||||
new ClusterRouterGroupSettings(totalInstances, routeesPaths,
|
||||
allowLocalRoutees, useRole)).props(), "factorialBackendRouter2");
|
||||
//#router-lookup-in-code
|
||||
}
|
||||
|
||||
//not used, only for documentation
|
||||
abstract class FactorialFrontend3 extends 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
|
||||
}
|
||||
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
|
@ -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 {
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package sample.cluster.factorial.japi;
|
||||
package sample.cluster.factorial;
|
||||
|
||||
import com.typesafe.config.Config;
|
||||
import com.typesafe.config.ConfigFactory;
|
||||
|
|
@ -7,12 +7,10 @@ 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()).
|
||||
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"));
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package sample.cluster.factorial.japi;
|
||||
package sample.cluster.factorial;
|
||||
|
||||
import com.typesafe.config.Config;
|
||||
import com.typesafe.config.ConfigFactory;
|
||||
|
|
@ -8,14 +8,16 @@ 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
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package sample.cluster.factorial.japi;
|
||||
package sample.cluster.factorial;
|
||||
|
||||
import java.math.BigInteger;
|
||||
import java.io.Serializable;
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package sample.cluster.factorial.japi;
|
||||
package sample.cluster.factorial;
|
||||
|
||||
//#metrics-listener
|
||||
import akka.actor.UntypedActor;
|
||||
|
|
@ -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");
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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) {
|
||||
|
|
@ -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<String> routeesPaths = Collections
|
||||
.singletonList("/user/statsWorker");
|
||||
boolean allowLocalRoutees = true;
|
||||
String useRole = "compute";
|
||||
ActorRef workerRouter = getContext().actorOf(
|
||||
new ClusterRouterGroup(new ConsistentHashingGroup(routeesPaths),
|
||||
new ClusterRouterGroupSettings(totalInstances, routeesPaths,
|
||||
allowLocalRoutees, useRole)).props(), "workerRouter2");
|
||||
//#router-lookup-in-code
|
||||
}
|
||||
|
||||
//not used, only for documentation
|
||||
abstract class StatsService3 extends 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
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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<Member> ageComparator = new Comparator<Member>() {
|
||||
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<Member> membersByAge = new TreeSet<Member>(ageComparator);
|
||||
|
||||
|
||||
//subscribe to cluster changes
|
||||
@Override
|
||||
public void preStart() {
|
||||
|
|
@ -61,18 +62,21 @@ public class StatsFacade extends UntypedActor {
|
|||
CurrentClusterState state = (CurrentClusterState) message;
|
||||
List<Member> members = new ArrayList<Member>();
|
||||
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");
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package sample.cluster.stats.japi;
|
||||
package sample.cluster.stats;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
|
|
@ -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;
|
||||
|
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,13 +1,16 @@
|
|||
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");
|
||||
|
||||
|
|
@ -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");
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package sample.cluster.stats.japi;
|
||||
package sample.cluster.stats;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
|
@ -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]);
|
||||
}
|
||||
}
|
||||
|
|
@ -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,8 +31,7 @@ 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()),
|
||||
getSender().tell(new TransformationResult(job.getText().toUpperCase()),
|
||||
getSelf());
|
||||
|
||||
} else if (message instanceof CurrentClusterState) {
|
||||
|
|
@ -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");
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -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;
|
||||
|
|
@ -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<Object>() {
|
||||
public void onSuccess(Object result) {
|
||||
System.out.println(result);
|
||||
}
|
||||
}, ec);
|
||||
}
|
||||
|
||||
}, ec);
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package sample.cluster.transformation.japi;
|
||||
package sample.cluster.transformation;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
|
||||
482
akka-samples/akka-sample-cluster-java/tutorial/index.html
Normal file
482
akka-samples/akka-sample-cluster-java/tutorial/index.html
Normal file
|
|
@ -0,0 +1,482 @@
|
|||
<!-- <html> -->
|
||||
<head>
|
||||
<title>Akka Cluster Samples with Java</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
|
||||
<div>
|
||||
<p>
|
||||
This tutorial contains 4 samples illustrating different
|
||||
<a href="http://doc.akka.io/docs/akka/2.3-SNAPSHOT/java/cluster-usage.html" target="_blank">Akka cluster</a> features.
|
||||
</p>
|
||||
<ul>
|
||||
<li>Subscribe to cluster membership events</li>
|
||||
<li>Sending messages to actors running on nodes in the cluster</li>
|
||||
<li>Cluster aware routers</li>
|
||||
<li>Cluster metrics</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>A Simple Cluster Example</h2>
|
||||
|
||||
<p>
|
||||
Open <a href="#code/src/main/resources/application.conf" class="shortcut">application.conf</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
To enable cluster capabilities in your Akka project you should, at a minimum, add the remote settings,
|
||||
and use <code>akka.cluster.ClusterActorRefProvider</code>. The <code>akka.cluster.seed-nodes</code> should
|
||||
normally also be added to your application.conf file.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The seed nodes are configured contact points which newly started nodes will try to connect with in order to join the cluster.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
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 <code>application.conf</code> instead of <code>127.0.0.1</code>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Open <a href="#code/src/main/java/sample/cluster/simple/SimpleClusterApp.java" class="shortcut">SimpleClusterApp.java</a>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
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
|
||||
<a href="#code/src/main/java/sample/cluster/simple/SimpleClusterListener.java" class="shortcut">SimpleClusterListener.java</a>
|
||||
actor.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
You can read more about the cluster concepts in the
|
||||
<a href="http://doc.akka.io/docs/akka/2.3-SNAPSHOT/java/cluster-usage.html" target="_blank">documentation</a>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
To run this sample, go to the <a href="#run" class="shortcut">Run</a>
|
||||
tab, and start the application main class <b><code>sample.cluster.simple.SimpleClusterApp</code></b>
|
||||
if it is not already started.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<code>SimpleClusterApp</code> 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
|
||||
<a href="#run" class="shortcut">Run</a> tab and then open three terminal windows.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
In the first terminal window, start the first seed node with the following command (on one line):
|
||||
</p>
|
||||
|
||||
<pre><code>
|
||||
<path to activator dir>/activator
|
||||
"runMain sample.cluster.simple.SimpleClusterApp 2551"
|
||||
</code></pre>
|
||||
|
||||
<p>
|
||||
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'.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
In the second terminal window, start the second seed node with the following command (on one line):
|
||||
</p>
|
||||
|
||||
<pre><code>
|
||||
<path to activator dir>/activator
|
||||
"runMain sample.cluster.simple.SimpleClusterApp 2552"
|
||||
</code></pre>
|
||||
|
||||
<p>
|
||||
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'.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Switch over to the first terminal window and see in the log output that the member joined.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Start another node in the third terminal window with the following command (on one line):
|
||||
</p>
|
||||
|
||||
<pre><code>
|
||||
<path to activator dir>/activator
|
||||
"runMain sample.cluster.simple.SimpleClusterApp 0"
|
||||
</code></pre>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Start even more nodes in the same way, if you like.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Look at the
|
||||
<a href="#code/src/main/java/sample/cluster/simple/SimpleClusterListener.java" class="shortcut">source code</a>
|
||||
of the actor again. It registers itself as subscriber of certain cluster events. It gets notified with an snapshot event,
|
||||
<code>CurrentClusterState</code> that holds full state information of the cluster. After that it receives events for changes
|
||||
that happen in the cluster.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>Worker Dial-in Example</h2>
|
||||
|
||||
<p>
|
||||
In the previous sample we saw how to subscribe to cluster membership events.
|
||||
You can read more about it in the
|
||||
<a href="http://doc.akka.io/docs/akka/2.3-SNAPSHOT/java/cluster-usage.html#Subscribe_to_Cluster_Events" target="_blank">documentation</a>.
|
||||
How can cluster membership events be used?
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Let's take a look at an example that illustrates how workers, here named <b>backend</b>,
|
||||
can detect and register to new master nodes, here named <b>frontend</b>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Open <a href="#code/src/main/java/sample/cluster/transformation/TransformationMessages.java" class="shortcut">TransformationMessages.java</a>.
|
||||
It defines the messages that are sent between the actors.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The backend worker that performs the transformation job is defined in
|
||||
<a href="#code/src/main/java/sample/cluster/transformation/TransformationBackend.java" class="shortcut">TransformationBackend.java</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Note that the <code>TransformationBackend</code> 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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The frontend that receives user jobs and delegates to one of the registered backend workers is defined in
|
||||
<a href="#code/src/main/java/sample/cluster/transformation/TransformationFrontend.java" class="shortcut">TransformationFrontend.java</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Note that the <code>TransformationFrontend</code> 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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
To run this sample, go to the <a href="#run" class="shortcut">Run</a>
|
||||
tab, and start the application main class <b><code>sample.cluster.transformation.TransformationApp</code></b>
|
||||
if it is not already started.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="#code/src/main/java/sample/cluster/transformation/TransformationApp.java" class="shortcut">TransformationApp</a> 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
|
||||
<a href="#run" class="shortcut">Run</a> tab and run the following commands in separate terminal windows.
|
||||
</p>
|
||||
|
||||
<pre><code>
|
||||
<path to activator dir>/activator
|
||||
"runMain sample.cluster.transformation.TransformationFrontendMain 2551"
|
||||
</code></pre>
|
||||
|
||||
<pre><code>
|
||||
<path to activator dir>/activator
|
||||
"runMain sample.cluster.transformation.TransformationBackendMain 2552"
|
||||
</code></pre>
|
||||
|
||||
<pre><code>
|
||||
<path to activator dir>/activator
|
||||
"runMain sample.cluster.transformation.TransformationBackendMain 0"
|
||||
</code></pre>
|
||||
|
||||
<pre><code>
|
||||
<path to activator dir>/activator
|
||||
"runMain sample.cluster.transformation.TransformationBackendMain 0"
|
||||
</code></pre>
|
||||
|
||||
<pre><code>
|
||||
<path to activator dir>/activator
|
||||
"runMain sample.cluster.transformation.TransformationFrontendMain 0"
|
||||
</code></pre>
|
||||
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>Cluster Aware Routers</h2>
|
||||
|
||||
<p>
|
||||
All <a href="http://doc.akka.io/docs/akka/2.3-SNAPSHOT/java/routing.html" target="_blank">routers</a>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
You can read more about cluster aware routers in the
|
||||
<a href="http://doc.akka.io/docs/akka/2.3-SNAPSHOT/java/cluster-usage.html#Cluster_Aware_Routers" target="_blank">documentation</a>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Let's take a look at a few samples that make use of cluster aware routers.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>Router Example with Group of Routees</h2>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Open <a href="#code/src/main/java/sample/cluster/stats/StatsMessages.java" class="shortcut">StatsMessages.java</a>.
|
||||
It defines the messages that are sent between the actors.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The worker that counts number of characters in each word is defined in
|
||||
<a href="#code/src/main/java/sample/cluster/stats/StatsWorker.java" class="shortcut">StatsWorker.java</a>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The service that receives text from users and splits it up into words, delegates to workers and aggregates
|
||||
is defined in <a href="#code/src/main/java/sample/cluster/stats/StatsService.java" class="shortcut">StatsService.java</a>
|
||||
and <a href="#code/src/main/java/sample/cluster/stats/StatsAggregator.java" class="shortcut">StatsAggregator.java</a>.
|
||||
<p>
|
||||
|
||||
<p>
|
||||
Note, nothing cluster specific so far, just plain actors.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
All nodes start <code>StatsService</code> and <code>StatsWorker</code> actors. Remember, routees are the workers in this case.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Open <a href="#code/src/main/resources/stats1.conf" class="shortcut">stats1.conf</a>
|
||||
The router is configured with <code>routees.paths</code>.
|
||||
This means that user requests can be sent to <code>StatsService</code> on any node and it will use
|
||||
<code>StatsWorker</code> on all nodes.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
To run this sample, go to the <a href="#run" class="shortcut">Run</a>
|
||||
tab, and start the application main class <b><code>sample.cluster.stats.StatsSampleMain</code></b>
|
||||
if it is not already started.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="#code/src/main/java/sample/cluster/stats/StatsSampleMain.java" class="shortcut">StatsSampleMain</a> 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
|
||||
<a href="#run" class="shortcut">Run</a> tab and run the following commands in separate terminal windows.
|
||||
</p>
|
||||
|
||||
<pre><code>
|
||||
<path to activator dir>/activator
|
||||
"runMain sample.cluster.stats.StatsSampleMain 2551"
|
||||
</code></pre>
|
||||
|
||||
<pre><code>
|
||||
<path to activator dir>/activator
|
||||
"runMain sample.cluster.stats.StatsSampleMain 2552"
|
||||
</code></pre>
|
||||
|
||||
<pre><code>
|
||||
<path to activator dir>/activator
|
||||
"runMain sample.cluster.stats.StatsSampleClientMain"
|
||||
</code></pre>
|
||||
|
||||
<pre><code>
|
||||
<path to activator dir>/activator
|
||||
"runMain sample.cluster.stats.StatsSampleMain 0"
|
||||
</code></pre>
|
||||
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>Router Example with Pool of Remote Deployed Routees</h2>
|
||||
|
||||
<p>
|
||||
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.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Open <a href="#code/src/main/java/sample/cluster/stats/StatsSampleOneMasterMain.java" class="shortcut">StatsSampleOneMasterMain.java</a>.
|
||||
To keep track of a single master we use the <a href="http://doc.akka.io/docs/akka/2.3-SNAPSHOT/contrib/cluster-singleton.html" target="_blank">Cluster Singleton</a>
|
||||
in the contrib module. The <code>ClusterSingletonManager</code> is started on each node.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
We also need an actor on each node that keeps track of where current single master exists and
|
||||
delegates jobs to the <code>StatsService</code>. That is handled by the
|
||||
<a href="#code/src/main/java/sample/cluster/stats/StatsFacade.java" class="shortcut">StatsFacade.java</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The <code>StatsFacade</code> receives text from users and delegates to the current <code>StatsService</code>, the single
|
||||
master. It listens to cluster events to lookup the <code>StatsService</code> on the oldest node.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
All nodes start <code>StatsFacade</code> and the <code>ClusterSingletonManager</code>. The router is now configured in
|
||||
<a href="#code/src/main/resources/stats2.conf" class="shortcut">stats2.conf</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
To run this sample, go to the <a href="#run" class="shortcut">Run</a>
|
||||
tab, and start the application main class <b><code>sample.cluster.stats.StatsSampleOneMasterMain</code></b>
|
||||
if it is not already started.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="#code/src/main/java/sample/cluster/stats/StatsSampleOneMasterMain.java" class="shortcut">StatsSampleOneMasterMain</a> 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
|
||||
<a href="#run" class="shortcut">Run</a> tab and run the following commands in separate terminal windows.
|
||||
</p>
|
||||
|
||||
<pre><code>
|
||||
<path to activator dir>/activator
|
||||
"runMain sample.cluster.stats.StatsSampleOneMasterMain 2551"
|
||||
</code></pre>
|
||||
|
||||
<pre><code>
|
||||
<path to activator dir>/activator
|
||||
"runMain sample.cluster.stats.StatsSampleOneMasterMain 2552"
|
||||
</code></pre>
|
||||
|
||||
<pre><code>
|
||||
<path to activator dir>/activator
|
||||
"runMain sample.cluster.stats.StatsSampleOneMasterClientMain"
|
||||
</code></pre>
|
||||
|
||||
<pre><code>
|
||||
<path to activator dir>/activator
|
||||
"runMain sample.cluster.stats.StatsSampleOneMasterMain 0"
|
||||
</code></pre>
|
||||
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<h2>Adaptive Load Balancing</h2>
|
||||
|
||||
<p>
|
||||
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 <code>AdaptiveLoadBalancingPool</code> and <code>AdaptiveLoadBalancingGroup</code> routers.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
You can read more about cluster metrics in the
|
||||
<a href="http://doc.akka.io/docs/akka/2.3-SNAPSHOT/java/cluster-usage.html#Cluster_Metrics" target="_blank">documentation</a>.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
Let's take a look at this router in action. What can be more demanding than calculating factorials?
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The backend worker that performs the factorial calculation:
|
||||
<a href="#code/src/main/java/sample/cluster/factorial/FactorialBackend.java" class="shortcut">FactorialBackend</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
The frontend that receives user jobs and delegates to the backends via the router:
|
||||
<a href="#code/src/main/java/sample/cluster/factorial/FactorialFrontend.java" class="shortcut">FactorialFrontend</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
As you can see, the router is defined in the same way as other routers, and in this case it is configured in:
|
||||
<a href="#code/src/main/resources/factorial.conf" class="shortcut">factorial.conf</a>
|
||||
</p>
|
||||
|
||||
<p>
|
||||
It is only router type <code>adaptive</code> and the <code>metrics-selector</code> that is specific to this router,
|
||||
other things work in the same way as other routers.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
To run this sample, go to the <a href="#run" class="shortcut">Run</a>
|
||||
tab, and start the application main class <b><code>sample.cluster.factorial.FactorialApp</code></b>
|
||||
if it is not already started.
|
||||
</p>
|
||||
|
||||
<p>
|
||||
<a href="#code/src/main/java/sample/cluster/factorial/FactorialApp.java" class="shortcut">FactorialApp</a> 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
|
||||
<a href="#run" class="shortcut">Run</a> tab and run the following commands in separate terminal windows.
|
||||
</p>
|
||||
|
||||
<pre><code>
|
||||
<path to activator dir>/activator
|
||||
"runMain sample.cluster.factorial.FactorialBackendMain 2551"
|
||||
</code></pre>
|
||||
|
||||
<pre><code>
|
||||
<path to activator dir>/activator
|
||||
"runMain sample.cluster.factorial.FactorialBackendMain 2552"
|
||||
</code></pre>
|
||||
|
||||
<pre><code>
|
||||
<path to activator dir>/activator
|
||||
"runMain sample.cluster.factorial.FactorialBackendMain 0"
|
||||
</code></pre>
|
||||
|
||||
<pre><code>
|
||||
<path to activator dir>/activator
|
||||
"runMain sample.cluster.factorial.FactorialFrontendMain 0"
|
||||
</code></pre>
|
||||
|
||||
<p>
|
||||
Press ctrl-c in the terminal window of the frontend to stop the factorial calculations.
|
||||
</p>
|
||||
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
17
akka-samples/akka-sample-cluster-scala/.gitignore
vendored
Normal file
17
akka-samples/akka-sample-cluster-scala/.gitignore
vendored
Normal file
|
|
@ -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
|
||||
13
akka-samples/akka-sample-cluster-scala/LICENSE
Normal file
13
akka-samples/akka-sample-cluster-scala/LICENSE
Normal file
|
|
@ -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.
|
||||
|
|
@ -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
|
||||
32
akka-samples/akka-sample-cluster-scala/project/Build.scala
Normal file
32
akka-samples/akka-sample-cluster-scala/project/Build.scala
Normal file
|
|
@ -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)
|
||||
}
|
||||
|
|
@ -0,0 +1 @@
|
|||
sbt.version=0.13.0
|
||||
|
|
@ -0,0 +1,4 @@
|
|||
|
||||
resolvers += Classpaths.typesafeResolver
|
||||
|
||||
addSbtPlugin("com.typesafe.sbt" % "sbt-multi-jvm" % "0.3.8")
|
||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
akka-samples/akka-sample-cluster-scala/sigar/sigar-x86-winnt.dll
Normal file
BIN
akka-samples/akka-sample-cluster-scala/sigar/sigar-x86-winnt.dll
Normal file
Binary file not shown.
BIN
akka-samples/akka-sample-cluster-scala/sigar/sigar-x86-winnt.lib
Normal file
BIN
akka-samples/akka-sample-cluster-scala/sigar/sigar-x86-winnt.lib
Normal file
Binary file not shown.
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
|
@ -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
|
||||
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Add a link
Reference in a new issue