Merge paradox/scala/cluster-usage.md and java/cluster-usage.md (#23154)

* Identical contents in scala/cluster-usage.md and java/cluster-usage.md for easier review

* Replace java/cluster-usage.md with a symlink to scala/cluster-usage.md
This commit is contained in:
Richard Imaoka 2017-06-14 17:52:01 +09:00 committed by Arnout Engelen
parent 17e07c0ae2
commit 48c8c1ab91
4 changed files with 160 additions and 876 deletions

View file

@ -6,11 +6,23 @@ For introduction to the Akka Cluster concepts please see @ref:[Cluster Specifica
The Akka cluster is a separate jar file. Make sure that you have the following dependency in your project:
@@@vars
```
"com.typesafe.akka" %% "akka-cluster" % "$akka.version$"
```
@@@
Scala
: @@@vars
```
"com.typesafe.akka" %% "akka-cluster" % "$akka.version$"
```
@@@
Java
: @@@vars
```
<dependency>
<groupId>com.typesafe.akka</groupId>
<artifactId>akka-cluster_$scala.binary_version$</artifactId>
<version>$akka.version$</version>
</dependency>
```
@@@
## A Simple Cluster Example
@ -73,15 +85,21 @@ ip-addresses or host names of the machines in `application.conf` instead of `127
An actor that uses the cluster extension may look like this:
@@snip [SimpleClusterListener.scala]($code$/scala/docs/cluster/SimpleClusterListener.scala) { type=scala }
Scala
: @@snip [SimpleClusterListener.scala]($code$/scala/docs/cluster/SimpleClusterListener.scala) { type=scala }
Java
: @@snip [SimpleClusterListener.java]($code$/java/jdocs/cluster/SimpleClusterListener.java) { type=java }
The actor registers itself as subscriber of certain cluster events. It receives events corresponding to the current state
of the cluster when the subscription starts and then it receives events for changes that happen in the cluster.
The easiest way to run this example yourself is to download the ready to run
@extref[Akka Cluster Sample with Scala](ecs:akka-samples-cluster-scala)
@scala[@extref[Akka Cluster Sample with Scala](ecs:akka-samples-cluster-scala)]
@java[@extref[Akka Cluster Sample with Java](ecs:akka-samples-cluster-java)]
together with the tutorial. It contains instructions on how to run the `SimpleClusterApp`.
The source code of this sample can be found in the @extref[Akka Samples Repository](samples:akka-sample-cluster-scala).
The source code of this sample can be found in the
@scala[@extref[Akka Samples Repository](samples:akka-sample-cluster-scala)]@java[@extref[Akka Samples Repository](samples:akka-sample-cluster-java)].
## Joining to Seed Nodes
@ -123,7 +141,7 @@ seed nodes in the existing cluster.
If you don't configure seed nodes you need to join the cluster programmatically or manually.
Manual joining can be performed by using [JMX](#cluster-jmx) or [HTTP](#cluster-http).
Joining programmatically can be performed with `Cluster(system).join`. Unsuccessful join attempts are
Joining programmatically can be performed with @scala[`Cluster(system).join`]@java[`Cluster.get(system).join`]. Unsuccessful join attempts are
automatically retried after the time period defined in configuration property `retry-unsuccessful-join-after`.
Retries can be disabled by setting the property to `off`.
@ -131,7 +149,7 @@ You can join to any node in the cluster. It does not have to be configured as a
Note that you can only join to an existing cluster member, which means that for bootstrapping some
node must join itself,and then the following nodes could join them to make up a cluster.
You may also use `Cluster(system).joinSeedNodes` to join programmatically,
You may also use @scala[`Cluster(system).joinSeedNodes`]@java[`Cluster.get(system).joinSeedNodes`] to join programmatically,
which is attractive when dynamically discovering other nodes at startup by using some external tool or API.
When using `joinSeedNodes` you should not include the node itself except for the node that is
supposed to be the first seed node, and that should be placed first in parameter to
@ -146,7 +164,7 @@ configured `seed-node-timeout`.
An actor system can only join a cluster once. Additional attempts will be ignored.
When it has successfully joined it must be restarted to be able to join another
cluster or to join the same cluster again.It can use the same host name and port
cluster or to join the same cluster again. It can use the same host name and port
after the restart, when it come up as new incarnation of existing member in the cluster,
trying to join in, then the existing one will be removed from the cluster and then it will
be allowed to join.
@ -168,7 +186,7 @@ status of the unreachable member must be changed to 'Down'. Changing status to '
can be performed automatically or manually. By default it must be done manually, using
[JMX](#cluster-jmx) or [HTTP](#cluster-http).
It can also be performed programmatically with `Cluster(system).down(address)`.
It can also be performed programmatically with @scala[`Cluster(system).down(address)`]@java[`Cluster.get(system).down(address)`].
A pre-packaged solution for the downing problem is provided by
[Split Brain Resolver](http://developer.lightbend.com/docs/akka-commercial-addons/current/split-brain-resolver.html),
@ -217,7 +235,11 @@ A more graceful exit can be performed if you tell the cluster that a node shall
This can be performed using [JMX](#cluster-jmx) or [HTTP](#cluster-http).
It can also be performed programmatically with:
@@snip [ClusterDocSpec.scala]($code$/scala/docs/cluster/ClusterDocSpec.scala) { #leave }
Scala
: @@snip [ClusterDocSpec.scala]($code$/scala/docs/cluster/ClusterDocSpec.scala) { #leave }
Java
: @@snip [ClusterDocTest.java]($code$/java/jdocs/cluster/ClusterDocTest.java) { #leave }
Note that this command can be issued to any member in the cluster, not necessarily the
one that is leaving.
@ -257,9 +279,13 @@ have no knowledge about the existence of the new members. You should for example
## Subscribe to Cluster Events
You can subscribe to change notifications of the cluster membership by using
`Cluster(system).subscribe`.
@scala[`Cluster(system).subscribe`]@java[`Cluster.get(system).subscribe`].
@@snip [SimpleClusterListener2.scala]($code$/scala/docs/cluster/SimpleClusterListener2.scala) { #subscribe }
Scala
: @@snip [SimpleClusterListener2.scala]($code$/scala/docs/cluster/SimpleClusterListener2.scala) { #subscribe }
Java
: @@snip [SimpleClusterListener2.java]($code$/java/jdocs/cluster/SimpleClusterListener2.java) { #subscribe }
A snapshot of the full state, `akka.cluster.ClusterEvent.CurrentClusterState`, is sent to the subscriber
as the first message, followed by events for incremental updates.
@ -270,19 +296,23 @@ This is expected behavior. When the node has been accepted in the cluster you wi
receive `MemberUp` for that node, and other nodes.
If you find it inconvenient to handle the `CurrentClusterState` you can use
`ClusterEvent.InitialStateAsEvents` as parameter to `subscribe`.
@scala[`ClusterEvent.InitialStateAsEvents`] @java[`ClusterEvent.initialStateAsEvents()`] as parameter to `subscribe`.
That means that instead of receiving `CurrentClusterState` as the first message you will receive
the events corresponding to the current state to mimic what you would have seen if you were
listening to the events when they occurred in the past. Note that those initial events only correspond
to the current state and it is not the full history of all changes that actually has occurred in the cluster.
@@snip [SimpleClusterListener.scala]($code$/scala/docs/cluster/SimpleClusterListener.scala) { #subscribe }
Scala
: @@snip [SimpleClusterListener.scala]($code$/scala/docs/cluster/SimpleClusterListener.scala) { #subscribe }
Java
: @@snip [SimpleClusterListener.java]($code$/java/jdocs/cluster/SimpleClusterListener.java) { #subscribe }
The events to track the life-cycle of members are:
* `ClusterEvent.MemberJoined` - A new member has joined the cluster and its status has been changed to `Joining`.
* `ClusterEvent.MemberUp` - A new member has joined the cluster and its status has been changed to `Up`.
* `ClusterEvent.MemberExited` - A member is leaving the cluster and its status has been changed to `Exiting`.
* `ClusterEvent.MemberJoined` - A new member has joined the cluster and its status has been changed to `Joining`
* `ClusterEvent.MemberUp` - A new member has joined the cluster and its status has been changed to `Up`
* `ClusterEvent.MemberExited` - A member is leaving the cluster and its status has been changed to `Exiting`
Note that the node might already have been shutdown when this event is published on another node.
* `ClusterEvent.MemberRemoved` - Member completely removed from the cluster.
* `ClusterEvent.UnreachableMember` - A member is considered as unreachable, detected by the failure detector
@ -295,7 +325,7 @@ of classes that extends `akka.cluster.ClusterEvent.ClusterDomainEvent`
for details about the events.
Instead of subscribing to cluster events it can sometimes be convenient to only get the full membership state with
`Cluster(system).state`. Note that this state is not necessarily in sync with the events published to a
@scala[`Cluster(system).state`]@java[`Cluster.get(system).state()`]. Note that this state is not necessarily in sync with the events published to a
cluster subscription.
### Worker Dial-in Example
@ -311,11 +341,19 @@ added or removed to the cluster dynamically.
Messages:
@@snip [TransformationMessages.scala]($code$/scala/docs/cluster/TransformationMessages.scala) { #messages }
Scala
: @@snip [TransformationMessages.scala]($code$/scala/docs/cluster/TransformationMessages.scala) { #messages }
Java
: @@snip [TransformationMessages.java]($code$/java/jdocs/cluster/TransformationMessages.java) { #messages }
The backend worker that performs the transformation job:
@@snip [TransformationBackend.scala]($code$/scala/docs/cluster/TransformationBackend.scala) { #backend }
Scala
: @@snip [TransformationBackend.scala]($code$/scala/docs/cluster/TransformationBackend.scala) { #backend }
Java
: @@snip [TransformationBackend.java]($code$/java/jdocs/cluster/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
@ -323,7 +361,11 @@ that they can use the backend worker.
The frontend that receives user jobs and delegates to one of the registered backend workers:
@@snip [TransformationFrontend.scala]($code$/scala/docs/cluster/TransformationFrontend.scala) { #frontend }
Scala
: @@snip [TransformationFrontend.scala]($code$/scala/docs/cluster/TransformationFrontend.scala) { #frontend }
Java
: @@snip [TransformationFrontend.java]($code$/java/jdocs/cluster/TransformationFrontend.java) { #frontend }
Note that the `TransformationFrontend` actor watch the registered backend
to be able to remove it from its list of available backend workers.
@ -333,9 +375,11 @@ actor. Death watch generates the `Terminated` message to the watching actor when
unreachable cluster node has been downed and removed.
The easiest way to run **Worker Dial-in Example** example yourself is to download the ready to run
@extref[Akka Cluster Sample with Scala](ecs:akka-samples-cluster-scala)
@scala[@extref[Akka Cluster Sample with Scala](ecs:akka-samples-cluster-scala)]
@java[@extref[Akka Cluster Sample with Java](ecs:akka-samples-cluster-java)]
together with the tutorial. It contains instructions on how to run the **Worker Dial-in Example** sample.
The source code of this sample can be found in the @extref[Akka Samples Repository](samples:akka-sample-cluster-scala).
The source code of this sample can be found in the
@scala[@extref[Akka Samples Repository](samples:akka-sample-cluster-scala)]@java[@extref[Akka Samples Repository](samples:akka-sample-cluster-java)].
## Node Roles
@ -375,7 +419,11 @@ You can start the actors in a `registerOnMemberUp` callback, which will
be invoked when the current member status is changed to 'Up', i.e. the cluster
has at least the defined number of members.
@@snip [FactorialFrontend.scala]($code$/scala/docs/cluster/FactorialFrontend.scala) { #registerOnUp }
Scala
: @@snip [FactorialFrontend.scala]($code$/scala/docs/cluster/FactorialFrontend.scala) { #registerOnUp }
Java
: @@snip [FactorialFrontendMain.java]($code$/java/jdocs/cluster/FactorialFrontendMain.java) { #registerOnUp }
This callback can be used for other things than starting actors.
@ -410,7 +458,7 @@ Distributes actors across several nodes in the cluster and supports interaction
with the actors using their logical identifier, but without having to care about
their physical location in the cluster.
See @ref:[Cluster Sharding](cluster-sharding.md)
See @ref:[Cluster Sharding](cluster-sharding.md).
## Distributed Publish Subscribe
@ -575,7 +623,11 @@ Set it to a lower value if you want to limit total number of routees.
The same type of router could also have been defined in code:
@@snip [StatsService.scala]($akka$/akka-cluster-metrics/src/multi-jvm/scala/akka/cluster/metrics/sample/StatsService.scala) { #router-lookup-in-code }
Scala
: @@snip [StatsService.scala]($akka$/akka-cluster-metrics/src/multi-jvm/scala/akka/cluster/metrics/sample/StatsService.scala) { #router-lookup-in-code }
Java
: @@snip [StatsService.java]($code$/java/jdocs/cluster/StatsService.java) { #router-lookup-in-code }
See [configuration](#cluster-configuration) section for further descriptions of the settings.
@ -592,16 +644,35 @@ the average number of characters per word when all results have been collected.
Messages:
@@snip [StatsMessages.scala]($akka$/akka-cluster-metrics/src/multi-jvm/scala/akka/cluster/metrics/sample/StatsMessages.scala) { #messages }
Scala
: @@snip [StatsMessages.scala]($akka$/akka-cluster-metrics/src/multi-jvm/scala/akka/cluster/metrics/sample/StatsMessages.scala) { #messages }
Java
: @@snip [StatsMessages.java]($code$/java/jdocs/cluster/StatsMessages.java) { #messages }
The worker that counts number of characters in each word:
@@snip [StatsWorker.scala]($akka$/akka-cluster-metrics/src/multi-jvm/scala/akka/cluster/metrics/sample/StatsWorker.scala) { #worker }
Scala
: @@snip [StatsWorker.scala]($akka$/akka-cluster-metrics/src/multi-jvm/scala/akka/cluster/metrics/sample/StatsWorker.scala) { #worker }
Java
: @@snip [StatsWorker.java]($code$/java/jdocs/cluster/StatsWorker.java) { #worker }
The service that receives text from users and splits it up into words, delegates to workers and aggregates:
@@@ div { .group-scala }
@@snip [StatsService.scala]($akka$/akka-cluster-metrics/src/multi-jvm/scala/akka/cluster/metrics/sample/StatsService.scala) { #service }
@@@
@@@ div { .group-java }
@@snip [StatsService.java]($code$/java/jdocs/cluster/StatsService.java) { #service }
@@snip [StatsAggregator.java]($code$/java/jdocs/cluster/StatsAggregator.java) { #aggregator }
@@@
Note, nothing cluster specific so far, just plain actors.
All nodes start `StatsService` and `StatsWorker` actors. Remember, routees are the workers in this case.
@ -625,9 +696,11 @@ This means that user requests can be sent to `StatsService` on any node and it w
`StatsWorker` on all nodes.
The easiest way to run **Router Example with Group of Routees** example yourself is to download the ready to run
@extref[Akka Cluster Sample with Scala](ecs:akka-samples-cluster-scala)
@scala[@extref[Akka Cluster Sample with Scala](ecs:akka-samples-cluster-scala)]
@java[@extref[Akka Cluster Sample with Java](ecs:akka-samples-cluster-java)]
together with the tutorial. It contains instructions on how to run the **Router Example with Group of Routees** sample.
The source code of this sample can be found in the @extref[Akka Samples Repository](samples:akka-sample-cluster-scala).
The source code of this sample can be found in the
@scala[@extref[Akka Samples Repository](samples:akka-sample-cluster-scala)]@java[@extref[Akka Samples Repository](samples:akka-sample-cluster-java)].
### Router with Pool of Remote Deployed Routees
@ -658,7 +731,11 @@ Set it to a lower value if you want to limit total number of routees.
The same type of router could also have been defined in code:
@@snip [StatsService.scala]($akka$/akka-cluster-metrics/src/multi-jvm/scala/akka/cluster/metrics/sample/StatsService.scala) { #router-deploy-in-code }
Scala
: @@snip [StatsService.scala]($akka$/akka-cluster-metrics/src/multi-jvm/scala/akka/cluster/metrics/sample/StatsService.scala) { #router-deploy-in-code }
Java
: @@snip [StatsService.java]($code$/java/jdocs/cluster/StatsService.java) { #router-deploy-in-code }
See [configuration](#cluster-configuration) section for further descriptions of the settings.
@ -666,27 +743,39 @@ See [configuration](#cluster-configuration) section for further descriptions of
Let's take a look at how to use a cluster aware router on single master node that creates
and deploys workers. To keep track of a single master we use the @ref:[Cluster Singleton](cluster-singleton.md)
in the cluster-tools module. The `ClusterSingletonManager` is started on each node.:
in the cluster-tools module. The `ClusterSingletonManager` is started on each node:
```
system.actorOf(
ClusterSingletonManager.props(
singletonProps = Props[StatsService],
terminationMessage = PoisonPill,
settings = ClusterSingletonManagerSettings(system).withRole("compute")),
name = "statsService")
```
Scala
: @@@vars
```
system.actorOf(
ClusterSingletonManager.props(
singletonProps = Props[StatsService],
terminationMessage = PoisonPill,
settings = ClusterSingletonManagerSettings(system).withRole("compute")),
name = "statsService")
```
@@@
Java
: @@snip [StatsSampleOneMasterMain.java]($code$/java/jdocs/cluster/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`. That is provided by the `ClusterSingletonProxy`.:
delegates jobs to the `StatsService`. That is provided by the `ClusterSingletonProxy`:
```
system.actorOf(
ClusterSingletonProxy.props(
singletonManagerPath = "/user/statsService",
settings = ClusterSingletonProxySettings(system).withRole("compute")),
name = "statsServiceProxy")
```
Scala
: @@@vars
```
system.actorOf(
ClusterSingletonProxy.props(
singletonManagerPath = "/user/statsService",
settings = ClusterSingletonProxySettings(system).withRole("compute")),
name = "statsServiceProxy")
```
@@@
Java
: @@snip [StatsSampleOneMasterMain.java]($code$/java/jdocs/cluster/StatsSampleOneMasterMain.java) { #singleton-proxy }
The `ClusterSingletonProxy` 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.
@ -708,15 +797,19 @@ akka.actor.deployment {
```
The easiest way to run **Router Example with Pool of Remote Deployed Routees** example yourself is to download the ready to run
@extref[Akka Cluster Sample with Scala](ecs:akka-samples-cluster-scala)
@scala[@extref[Akka Cluster Sample with Scala](ecs:akka-samples-cluster-scala)]
@java[@extref[Akka Cluster Sample with Java](ecs:akka-samples-cluster-java)]
together with the tutorial. It contains instructions on how to run the **Router Example with Pool of Remote Deployed Routees** sample.
The source code of this sample can be found in the @extref[Akka Samples Repository](samples:akka-sample-cluster-scala).
The source code of this sample can be found in the
@scala[@extref[Akka Samples Repository](samples:akka-sample-cluster-scala)]@java[@extref[Akka Samples Repository](samples:akka-sample-cluster-java)].
## Cluster Metrics
The member nodes of the cluster can collect system health metrics and publish that to other cluster nodes
and to the registered subscribers on the system event bus with the help of `cluster-metrics`.
@@@ div { .group-scala }
## How to Test
@ref:[Multi Node Testing](multi-node-testing.md) is useful for testing cluster applications.
@ -769,6 +862,17 @@ the actor system for a specific role. This can also be used to grab the `akka.ac
@@snip [StatsSampleSpec.scala]($akka$/akka-cluster-metrics/src/multi-jvm/scala/akka/cluster/metrics/sample/StatsSampleSpec.scala) { #addresses }
@@@
@@@ div { .group-java }
## How to Test
Currently testing with the `sbt-multi-jvm` plugin is only documented for Scala.
Go to the corresponding @ref[Scala page](../scala/cluster-usage.md#how-to-test) for details.
@@@
## Management
<a id="cluster-http"></a>