From a237058b64f39dd3514d16608090176acd5369de Mon Sep 17 00:00:00 2001 From: Patrik Nordwall Date: Mon, 27 Aug 2012 07:47:34 +0200 Subject: [PATCH] First part of usage doc for cluster, see #1916 * Sample app, akka-sample-cluster * Preparing Your Project for Clustering * A Simple Cluster Example * Automatic vs. Manual Joining * Automatic vs. Manual Downing * Configuration --- akka-docs/cluster/cluster-usage.rst | 196 ++++++++++++++++++ akka-docs/cluster/index.rst | 1 + .../src/main/resources/application.conf | 4 +- .../scala/sample/cluster/ClusterApp.scala | 14 +- 4 files changed, 208 insertions(+), 7 deletions(-) create mode 100644 akka-docs/cluster/cluster-usage.rst diff --git a/akka-docs/cluster/cluster-usage.rst b/akka-docs/cluster/cluster-usage.rst new file mode 100644 index 0000000000..3f54778817 --- /dev/null +++ b/akka-docs/cluster/cluster-usage.rst @@ -0,0 +1,196 @@ + +.. _cluster_usage: + +############### + Cluster Usage +############### + +.. note:: This document describes how to use the features implemented so far of the + new clustering coming in Akka Coltrane and is not available in the latest stable release. + The API might change before it is released. + +For introduction to the Akka Cluster concepts please see :ref:`cluster`. + +Preparing Your Project for Clustering +^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +The Akka cluster is a separate jar file. Make sure that you have the following dependency in your project:: + + "com.typesafe.akka" % "akka-cluster" % "2.1-SNAPSHOT" + +Instead of 2.1-SNAPSHOT you should pick a timestamped Akka version from ``_. + +The following sbt build file illustrates what to use with Scala 2.10.0-M7 and Akka 2.1-SNAPSHOT + + import sbt._ + import sbt.Keys._ + + object ProjectBuild extends Build { + + lazy val root = Project( + id = "root", + base = file("."), + settings = Project.defaultSettings ++ Seq( + name := "Akka Cluster Example", + organization := "org.test", + version := "0.1-SNAPSHOT", + scalaVersion := "2.10.0-M7", + + resolvers += "Sonatype Releases Repo" at "https://oss.sonatype.org/content/repositories/releases/", + resolvers += "Sonatype Snapshot Repo" at "https://oss.sonatype.org/content/repositories/snapshots/", + resolvers += "Typesafe Releases" at "http://repo.typesafe.com/typesafe/releases", + resolvers += "Typesafe Snapshots" at "http://repo.typesafe.com/typesafe/snapshots/", + + + libraryDependencies ++= Seq( + "com.typesafe.akka" % "akka-cluster" % "2.1-20120822-000942") + ) + ) + } + +Pick a timestamped Akka version from ``_. + + +A Simple Cluster Example +^^^^^^^^^^^^^^^^^^^^^^^^ + +The following small program together with its configuration starts an ``ActorSystem`` +with the Cluster extension enabled. It joins the cluster and logs some membership events. + +Try it out: + +1. Add the following ``application.conf`` in your project, place it in ``src/main/resources``: + + +.. literalinclude:: ../../akka-samples/akka-sample-cluster/src/main/resources/application.conf + :language: none + +To enable cluster capabilities in your Akka project you should, at a minimum, add the :ref:`remoting-scala` +settings and the ``akka.cluster.seed-nodes`` to your ``application.conf`` file: + +.. literalinclude:: ../../akka-samples/akka-sample-remote/src/main/resources/common.conf + :language: none + +The seed nodes are configured contact points for initial, automatic, join of the cluster. + +Note that if you are going to starting 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``: + +.. literalinclude:: ../../akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/ClusterApp.scala + :language: scala + + +3. Start the first seed node. Open a sbt session in one terminal window and run:: + + run-main sample.cluster.ClusterApp 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.ClusterApp 2552 + + +2552 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 joins the other seed node +and becomes a member of the cluster. It's 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.ClusterApp + +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. + +Automatic vs. Manual Joining +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +You may decide if joining to the cluster should be done automatically or manually. +By default it is automatic and you need to define the seed nodes in configuration +so that a new node has an initial contact point. When a new node is started it +sends a message to all seed nodes and then sends join command to the one that +answers first. If no one of the seed nodes replied (might not be started yet) +it retries this procedure until successful or shutdown. + +There is one thing to be aware of regarding the seed node configured as the +first element in the ``seed-nodes`` configuration list. +The seed nodes can be started in any order and it is not necessary to have all +seed nodes running, but the first seed node must be started when initially +starting a cluster, otherwise the other seed-nodes will not become initialized +and no other node can join the cluster. Once more than two seed nodes have been +started it is no problem to shut down the first seed node. If it goes down it +must be manually joined to the cluster again. +Automatic joining of the first seed node is not possible, it would only join +itself. It is only the first seed node that has this restriction. + +You can disable automatic joining with configuration: + + akka.cluster.auto-join = off + +Then you need to join manually, using JMX or the provided script. +You can join to any node in the cluster. It doesn't have to be configured as +seed node. If you are not using auto-join there is no need to configure +seed nodes at all. + +Joining can also be performed programatically with ``Cluster(system).join``. + + +Automatic vs. Manual Downing +^^^^^^^^^^^^^^^^^^^^^^^^^^^^ + +When a member is considered by the failure detector to be unreachable the +leader is not allowed to perform its duties, such as changing status of +new joining members to Up. The status of the unreachable member must be +changed to Down. This can be performed automatically or manually. By +default it must be done manually, using using JMX or the provided script. + +It can also be performed programatically with ``Cluster(system).down``. + +You can enable automatic downing with configuration: + + akka.cluster.auto-down = on + +Be aware of that using auto-down implies that two separate clusters will +automatically be formed in case of network partition. That might be +desired by some applications but not by others. + +Configuration +^^^^^^^^^^^^^ + +There are several configuration properties for the cluster. We refer to the following +reference file for more information: + + +.. literalinclude:: ../../akka-cluster/src/main/resources/reference.conf + :language: none + +It is recommended that you change the ``tick-duration`` to 33 ms or less +of the default scheduler when using cluster, if you don't need to have it +configured to a longer duration for other reasons. If you don't do this +a dedicated scheduler will be used for periodic tasks of the cluster, which +introduce the extra overhead of another thread. + +:: + + # shorter tick-duration of default scheduler when using cluster + akka.scheduler.tick-duration.tick-duration = 33ms + + + diff --git a/akka-docs/cluster/index.rst b/akka-docs/cluster/index.rst index 35c4b2250a..dac3a558d9 100644 --- a/akka-docs/cluster/index.rst +++ b/akka-docs/cluster/index.rst @@ -5,3 +5,4 @@ Cluster :maxdepth: 2 cluster + cluster-usage diff --git a/akka-samples/akka-sample-cluster/src/main/resources/application.conf b/akka-samples/akka-sample-cluster/src/main/resources/application.conf index 779b3825cd..ee403ff23d 100644 --- a/akka-samples/akka-sample-cluster/src/main/resources/application.conf +++ b/akka-samples/akka-sample-cluster/src/main/resources/application.conf @@ -13,6 +13,8 @@ akka { extensions = ["akka.cluster.Cluster$"] cluster { - seed-nodes = ["akka://ClusterSystem@127.0.0.1:2551", "akka://ClusterSystem@127.0.0.1:2552"] + seed-nodes = [ + "akka://ClusterSystem@127.0.0.1:2551", + "akka://ClusterSystem@127.0.0.1:2552"] } } \ No newline at end of file diff --git a/akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/ClusterApp.scala b/akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/ClusterApp.scala index 521176d142..0fd396784d 100644 --- a/akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/ClusterApp.scala +++ b/akka-samples/akka-sample-cluster/src/main/scala/sample/cluster/ClusterApp.scala @@ -8,24 +8,26 @@ object ClusterApp { def main(args: Array[String]): Unit = { + // Override the configuration of the port + // when specified as program argument if (args.nonEmpty) System.setProperty("akka.remote.netty.port", args(0)) // Create an Akka system val system = ActorSystem("ClusterSystem") - val clusterListener = system.actorOf(Props(new Actor { + val clusterListener = system.actorOf(Props(new Actor with ActorLogging { def receive = { case state: CurrentClusterState ⇒ - println("Current members: " + state.members) + log.info("Current members: {}", state.members) case MemberJoined(member) ⇒ - println("Member joined: " + member) + log.info("Member joined: {}", member) case MemberUp(member) ⇒ - println("Member is Up: " + member) + log.info("Member is Up: {}", member) case MemberUnreachable(member) ⇒ - println("Member detected as unreachable: " + member) + log.info("Member detected as unreachable: {}", member) case _ ⇒ // ignore } - })) + }), name = "clusterListener") Cluster(system).subscribe(clusterListener, classOf[ClusterDomainEvent]) }