Akka

3 篇文章 0 订阅

转载自 http://www.gtan.com/akka_doc/index.html


一、Akka是什么?

1、可扩展的实时事务处理

我们相信编写出正确的具有容错性和可扩展性的并发程序太困难了。这多数是因为我们使用了错误的工具和错误的抽象级别。Akka就是为了改变这种状况而生的。通过使用Actor模型我们提升了抽象级别,为构建正确的可扩展并发应用提供了一个更好的平台。在容错性方面我们采取了“let it crash”(让它崩溃)模型,人们已经将这种模型用在了电信行业,构建出“自愈合”的应用和永不停机的系统,取得了巨大成功。Actor还为透明的分布式系统以及真正的可扩展高容错应用的基础进行了抽象。

Akka是开源的,可以通过Apache 2许可获得。

从 http://akka.io/downloads/ 下载

2、Akka实现了独特的混合模型

Actors

Actors为你提供:

  • 对并发/并行程序的简单的、高级别的抽象。
  • 异步、非阻塞、高性能的事件驱动编程模型。
  • 非常轻量的事件驱动处理(1G内存可容纳约270万个actors)。

参阅 Actors (Scala) 和 Actors (Java)

容错性

使用“let-it-crash”语义和监管者树形结构来实现容错。非常适合编写永不停机、自愈合的高容错系统。监管者树形结构可以跨多个JVM来提供真正的高容错系统。

参阅 容错性 (Scala) 和 容错性 (Java)

位置透明性

Akka的所有元素都为分布式环境而设计:所有actor都仅通过发送消息进行互操作,所有操作都是异步的。

了解远程调用请参阅 事务透明性

事务性actors

事务性Actor是actor与STM(Software Transactional Memory)的组合。它使你能够使用自动重试和回滚来组合出原子消息流。

参阅 事务性actor (Scala) 和 事务性actor (Java)

3、Scala 和 Java APIs

Akka同时提供 Scala API 和 Java API.

4、Akka可以以两种不同的方式来使用

  • 以库的形式:在web应用中使用,放到 WEB-INF/lib 中或者作为一个普通的Jar包放进classpath。
  • 以微内核的形式:你可以将应用放进一个独立的内核。

参阅 用例与部署场景 了解细节。

5、Cloudy Akka 如何了?

Akka的商业支持早先被叫作Cloudy Akka. 它包括两部分:

  • Akka的集群支持
  • 监控和管理(早先称为Atmos)

Cloudy Akka已经停止了。集群支持已经被移进了Akka的开源版本中(即将到来的Akka 2.1),而监控和管理(Atmos)现在被重新命名为Typesafe控制台,是Typesafe Stack(详见下文)商业合约的一部分。

6、Typesafe Stack

Akka现在是 Typesafe Stack 的一部分。

Typesafe stack是一个让开发者更容易地构建可扩展软件应用的现代软件平台。它在一个简单的包里组合了Scala语言、Akka、Play! Web框架和其它鲁棒的开发工具,能够与现有的Java基础设施无缝集成。

Typesafe Stack是完全开源的。

7、Typesafe控制台

在Typesafe Stack的顶端,我们还有名叫Typesafe控制台的商业产品,提供以下功能:

  1. 漂亮的Web界面,实时展示系统内部状态
  2. 通过Dashboard、JMX和REST进行管理
  3. 组件间及远程节点间消息的跟踪
  4. 实时统计
  5. 开销非常小的监控程序(生产系统中应该保持运行)
  6. 单节点上统计与日志信息的合并
  7. 统计数据的存储,以备后续处理
  8. 安装升级及滚动升级

二、为什么使用Akka

1、Akka平台提供哪些有竞争力的功能?

Akka提供可扩展的实时事务处理。

Akka是一个运行时与编程模型一致的系统,为以下目标设计:

  • 垂直扩展(并发)
  • 水平扩展(远程调用)
  • 高容错

在Akka的世界里,只有一个内容需要学习和管理,具有高内聚和高一致的语义。

Akka是一种高度可扩展的软件,这不仅仅表现在性能方面,也表现在它所适用的应用的大小。Akka的核心,Akka-actor是非常小的,可以非常方便地放进你的应用中,提供你需要的异步无锁并行功能,不会有任何困扰。

你可以任意选择Akka的某些部分集成到你的应用中,也可以使用完整的包——Akka 微内核,它是一个独立的容器,可以直接部署你的Akka应用。随着CPU核数越来越多,即使你只使用一台电脑,Akka也可作为一种提供卓越性能的选择。Akka还同时提供多种并发范型,允许用户选择正确的工具来完成工作。

2、什么场景下特别适合使用Akka?

我们看到Akka被成功运用在众多行业的众多大企业,从投资业到商业银行、从零售业到社会媒体、仿真、游戏和赌博、汽车和交通系统、数据分析等等等等。任何需要高吞吐率和低延迟的系统都是使用Akka的候选。

Actor使你能够进行服务失败管理(监管者),负载管理(缓和策略、超时和隔离),水平和垂直方向上的可扩展性(增加cpu核数和/或增加更多的机器)管理。

下面的链接中有一些Akka用户关于他们如何使用Akka的描述:http://stackoverflow.com/questions/4493001/good-use-case-for-akka

所有以上这些都在这个Apache2许可的开源软件中。


三、入门指南(scala)

1、序言

欢迎来到第一个使用Akka和Scala的指南。我们假设你已经知道Akka和Scala是什么,现在需要了解开始第一个项目的步骤。

本指南有两种方式:

  • 创建一个独立项目,从命令行运行
  • 创建SBT项目,在SBT中运行

因为这两种方式非常相像,我们都会进行讲解。

我们要创建的示例应用是使用actor来计算PI的值。计算PI是一项CPU密集的操作,我们将使用Akka Actor来编写一个可以垂直扩展到多个处理器核上的并发解决方案。在将来的指南中,这个示例应用将被扩展国使用Akka远程Actor来在水平到集群中的多台机器上。

我们所使用的算法叫“embarrassingly parallel” 意思是每个子任务是独立完成的,与其它子任务无关。这个算法可以高度并行化,所以非常适合使用actor模型。

以下是我们所使用的算法的公式:

../_images/pi-formula.png

在这个特定的算法中,有一个主actor将序列分割成段并发送给工作actor来进行计算。当工作actor完成自己的序列段的计算后将结果传给主actor,由主actor进行汇总。

源码

如果你不想把源码用键盘敲一遍而且/或不想创建SBT项目,你可以从Akka GitHub仓库中下载整个指南。它的位置在 akka-tutorials/akka-tutorial-first 模块. 你也可以到 这里在线浏览。实际的代码在 这里

要使用Git下载代码运行下面的命令:

Linux/Unix/Mac 系统:

       
       
  1. $ git clone git://github.com/akka/akka.git
  2. $ cd akka/akka-tutorials/akka-tutorial-first

Windows 系统:

       
       
  1. C:\Users\jboner\src> git clone git://github.com/akka/akka.git
  2. C:\Users\jboner\src> cd akka\akka-tutorials\akka-tutorial-first

2、准备工作

本指南假设你安装了Java 1.6或更高版本并且 java 命令在你的 PATH上. 你还需要知道如何在shell(ZSH, Bash, DOS 等等.)中运行命令,需要一个文本编辑器或IDE来输入Scala代码。

你必须保证 $JAVA_HOME 环境变量被正确设置为Java安装位置的根目录,还必须保证 $JAVA_HOME/bin 在你的PATH上。

Linux/Unix/Mac 系统:

      
      
  1. $ export JAVA_HOME=..root of Java distribution..
  2. $ export PATH=$PATH:$JAVA_HOME/bin

检测 java 正确安装:

      
      
  1. $ java -version
  2. java version "1.6.0_24"
  3. Java(TM) SE Runtime Environment (build 1.6.0_24-b07-334-10M3326)
  4. Java HotSpot(TM) 64-Bit Server VM (build 19.1-b02-334, mixed mode)

Windows 系统:

      
      
  1. C:\Users\jboner\src\akka> set JAVA_HOME=..root of Java distribution..
  2. C:\Users\jboner\src\akka> set PATH=%PATH%;%JAVA_HOME%/bin

检测 java 正确安装:

      
      
  1. C:\Users\jboner\src\akka> java -version
  2. java version "1.6.0_24"
  3. Java(TM) SE Runtime Environment (build 1.6.0_24-b07-334-10M3326)
  4. Java HotSpot(TM) 64-Bit Server VM (build 19.1-b02-334, mixed mode)

3、下载安装 Akka

要从命令行编译和运行本指南示例,需要下载Akka。如果你希望用SBT来编译和运行它,建议跳过这一部分直接看下一节。

从 http://akka.io/downloads/ 下载 akka-2.0.zip 发布包,它包含本指南所需的全部模块。下载完成后,将发布包解压至安装目录。我的安装目录下 /Users/jboner/tools/

要正确安装Akka,需要再做一件事:设置 AKKA_HOME 环境变量到安装目录的根.

Linux/Unix/Mac 系统:

      
      
  1. $ cd /Users/jboner/tools/akka-2.0
  2. $ export AKKA_HOME=`pwd`
  3. $ echo $AKKA_HOME
  4. /Users/jboner/tools/akka-2.0

Windows 系统:

      
      
  1. C:\Users\jboner\src\akka> cd akka-2.0
  2. C:\Users\jboner\src\akka\akka-2.0> set AKKA_HOME=%cd%
  3. C:\Users\jboner\src\akka\akka-2.0> echo %AKKA_HOME%
  4. C:\Users\jboner\src\akka\akka-2.0

安装路径的内容如下:

Linux/Unix/Mac 系统:

      
      
  1. $ ls -1
  2. bin
  3. config
  4. deploy
  5. doc
  6. lib
  7. src

Windows 系统:

      
      
  1. C:\Users\jboner\src\akka\akka-2.0> dir
  2. bin
  3. config
  4. deploy
  5. doc
  6. lib
  7. src
  • bin 目录中是用来启动Akka微内核的脚本。
  • In the config 目录中是Akka配置文件。
  • In the deploy 目录用来放置随微内核一起运行的应用。
  • In the doc 目录中是文档、API的jar包。
  • In the lib 目录中是Scala和Akka的jar包。
  • In the src 目录中是Akka源码jar包。

本指南所需要的唯一一个jar包 (除了 scala-library.jar 以外) 是 lib/akka 目录中的 akka-actor-2.0.jar. 这个jar包没有外部依赖,有了它我们就可以编写一个使用actor的系统。

Akka模块化做得很好,包含有实现不同功能的各个jar包,其模块包括:

  • akka-actor – Actor
  • akka-remote – 远程 Actor
  • akka-slf4j – SLF4J 事件处理监听器
  • akka-testkit – 测试actor的工具包
  • akka-kernel – 运行一个基础的最小应用服务器的微内核
  • akka-durable-mailboxes – 持久邮箱: 基于文件, MongoDB, Redis, Zookeeper
  • akka-amqp – AMQP 集成

4、下载安装 Scala

要从命令行编译和运行本指南示例,需要安装Scala发布包。如果你要使用SBT,在SBT中编译和运行,可以跳过这一部分直接进入下一节。

可以从 http://www.scala-lang.org/downloads下载Scala. 在那里下载 Scala 2.9.1 版本. 如果你选择的是 tgz 或zip 包则需要进行解压。如果选择的是IzPack安装程序,只需要双击它然后按照指示操作。

还必须保证 scala-2.9.1/bin (scala-2.9.1是你的Scala安装目录) 在你的 PATH上。

Linux/Unix/Mac 系统:

     
     
  1. $ export PATH=$PATH:scala-2.9.1/bin

Windows 系统:

      
      
  1. C:\Users\jboner\src\akka\akka-2.0> set PATH=%PATH%;scala-2.9.1\bin

你可以运行scala命令来测试你的安装是否正确。

Linux/Unix/Mac 系统:

      
      
  1. $ scala -version
  2. Scala code runner version 2.9.1.final -- Copyright 2002-2011, LAMP/EPFL

Windows 系统:

      
      
  1. C:\Users\jboner\src\akka\akka-2.0> scala -version
  2. Scala code runner version 2.9.1.final -- Copyright 2002-2011, LAMP/EPFL

看来一切顺利. 最后让我们创建一个源码文件 Pi.scala 并将它放在Akka发布版本的根目录下的 tutorial 目录 (如果这个目录不存在你需要创建它)。

有一些工具要求你设置 SCALA_HOME 环境变量为Scala发布包的根目录,不过Akka并没有这一要求。

5、下载安装SBT

SBT, Simple Build Tool的简称, 是用Scala语言编写的优秀的build工具。 它使用Scala语言来编写build脚本,功能非常强大。它拥有一个插件体系,已经有很多插件可供使用,我们很快就会用到它们。SBT是用来build用Scala编写的软件的推荐方法,而且可能是学习本指南的最简单的方法。如果你决定使用SBT,请按下面的指示操作,否则可以跳过这一部分和下一部分内容。

要安装SBT并创建本指南的项目,最简单的方法见 https://github.com/harrah/xsbt/wiki/Setup

现在我们需要创建我们的第一个Akka项目,你可以手动向build脚本中添加依赖,不过更简单的方法是使用下一部分中介绍的Akka SBT插件。

6、创建Akka SBT项目

如果你还没做过,那么现在就开始创建本指南所讲的SBT项目,所要做的是在你希望创建项目的目录下添加一个build.sbt 文件:

      
      
  1. name := "My Project"
  2.  
  3. version := "1.0"
  4.  
  5. scalaVersion := "2.9.1"
  6.  
  7. resolvers += "Typesafe Repository" at "http://repo.typesafe.com/typesafe/releases/"
  8.  
  9. libraryDependencies += "com.typesafe.akka" % "akka-actor" % "2.0"

再创建一个名为 src/main/scala 的目录来存放源码.

虽然本指南并不需要,但是你可能愿意添加除了 akka-actor 外的其它Akka模块, 这些是添加在 build.sbt 的libraryDependencies 部分。 注意其中的每一项之间必须有一个空行。下面是一个添加 akka-remote的例子:

      
      
  1. libraryDependencies += "com.typesafe.akka" % "akka-actor" % "2.0"
  2.  
  3. libraryDependencies += "com.typesafe.akka" % "akka-remote" % "2.0"

好,现在我们都弄好了。

SBT 自身有一堆依赖,不过我们的项目只需要其中一个: akka-actor-2.0.jar. SBT 会下载它。

7、开始编写代码

终于可以开始写代码了。

我们先创建 Pi.scala 文件并在文件顶部添加以下这些 import:

      
      
  1. import akka.actor._
  2. import akka.routing.RoundRobinRouter
  3. import akka.util.Duration
  4. import akka.util.duration._

如果你使用SBT,那么将 Pi.scala 放在 src/main/scala 目录下.

如果你使用命令行,那么可以将它放在随便哪儿。我是在Akka安装目录下创建了一个名为 tutorial 的目录, 也就是 $AKKA_HOME/tutorial/Pi.scala

8、创建消息

我们要做的设计是由一个  actor来启动整个计算过程,创建一组 工作 actor. 整个工作会被分割成具体的小段, 各小段会以round-robin的方式发送到不同的工作 actor. 主actor等待所有的工作actor完全各自的工作并将其回送的结果进行汇总。当计算完成以后,主actor将结果发送给 监听器 acotr, 由它来输出结果。

在这个基础上, 现在让我们创建在这个系统中流动的消息。我们需要4种不同的消息:

  • Calculate – 发送给  actor 来启动计算。
  • Work – 从  actor 发送给各 工作 actor,包含工作分配的内容。
  • Result – 从 工作 actors 发送给  actor,包含工作actor的计算结果。
  • PiApproximation – 从  actor发送给 监听器 actor,包含pi的最终计算结果和整个计算耗费的时间。

发送给actor的消息应该永远是不可变的,以避免共享可变状态。 在scala里我们有 ‘case classes’ 来构造完美的消息。现在让我们用case class创建3种消息。我们还为消息们创建一个通用的基础trait(定义为sealed以防止在我们不可控的地方创建消息):

      
      
  1. sealed trait PiMessage
  2. case object Calculate extends PiMessage
  3. case class Work(start: Int, nrOfElements: Int) extends PiMessage
  4. case class Result(value: Double) extends PiMessage
  5. case class PiApproximation(pi: Double, duration: Duration)

9、创建工作 actor

现在我们来创建工作 actor。 方法是混入 Actor trait 并定义其中的 receive 方法. receive 方法定义我们的消息处理器。我们让它能够处理 Work 消息,所以添加一个针对这种消息的处理器:

      
      
  1. class Worker extends Actor {
  2.  
  3. // calculatePiFor ...
  4.  
  5. def receive = {
  6. case Work(start, nrOfElements)
  7. sender ! Result(calculatePiFor(start, nrOfElements)) // perform the work
  8. }
  9. }

可以看到我们现在创建了一个 Actor 和一个 receive 方法作为 Work 消息的处理器. 在这个处理器中我们调用calculatePiFor(..) 方法, 将结果包在 Result 消息里并使用sender异步发送回消息的原始发送者。 在Akka里,sender引用是与消息一起隐式发送的,这样接收者可以随时回复或将sender引用保存起来以备将来使用。

现在在我们的 Worker actor 中唯一缺少的就是实现 calculatePiFor(..) 方法。 虽然在Scala里我们可以有很多方法来实现这个算法,在这个入门指南中我们选择了一种命令式的风格,使用了for写法和一个累加器:

      
      
  1. def calculatePiFor(start: Int, nrOfElements: Int): Double = {
  2. var acc = 0.0
  3. for (i start until (start + nrOfElements))
  4. acc += 4.0 * (1 - (i % 2) * 2) / (2 * i + 1)
  5. acc
  6. }

10、创建主actor

主actor会稍微复杂一些。 在它的构造方法里我们创建一个round-robin的路由器来简化将工作平均地分配给工作actor们的过程,先做这个:

      
      
  1. val workerRouter = context.actorOf(
  2. Props[Worker].withRouter(RoundRobinRouter(nrOfWorkers)), name = "workerRouter")

现在我们有了一个路由,可以在一个单一的抽象中表达所有的工作actor。现在让我们创建主actor. 传递给它三个整数变量:

  • nrOfWorkers – 定义我们会启动多少工作actor
  • nrOfMessages – 定义会有多少整数段发送给工作actor
  • nrOfElements – 定义发送给工作actor的每个整数段的大小

下面是主actor:

      
      
  1. class Master(nrOfWorkers: Int, nrOfMessages: Int, nrOfElements: Int, listener: ActorRef)
  2. extends Actor {
  3.  
  4. var pi: Double = _
  5. var nrOfResults: Int = _
  6. val start: Long = System.currentTimeMillis
  7.  
  8. val workerRouter = context.actorOf(
  9. Props[Worker].withRouter(RoundRobinRouter(nrOfWorkers)), name = "workerRouter")
  10.  
  11. def receive = {
  12. // handle messages ...
  13. }
  14.  
  15. }

有一些需要进一步解释的事。

注意我们向  actor传进了一个 ActorRef . 这是用来向外界报告最终的计算结果。

但是还没完。我们还缺少  actor的消息处理器. 这个处理器需要能够对两种消息进行响应:

  • Calculate – 用来启动计算过程
  • Result – 用来汇总不同的计算结果

Calculate 处理器会通过其路由器向所有的 工作 actor 发送工作内容.

Result 处理器从 Result 消息中获取值并汇总到我们的 pi 成员变量中. 我们还会记录已经接收的结果数据的数量,它是否与发送出去的任务数量一致 。 actor 发现计算完成了,会将最终结果发送给 监听者. 当整个过程都完成了,它会调用 context.stop(self) 方法来终止自己  它所监管的所有actor. 在本例中,主actor监管一个actor,我们的路由器,而路由器监管着所有 nrOfWorkers 个工作actors. 所有的actor都会在其监管者的stop方法被调用时自动终止,并会传递给所有它监管的子actor。

让我们在代码中实现这些:

      
      
  1. def receive = {
  2. case Calculate
  3. for (i 0 until nrOfMessages) workerRouter ! Work(i * nrOfElements, nrOfElements)
  4. case Result(value)
  5. pi += value
  6. nrOfResults += 1
  7. if (nrOfResults == nrOfMessages) {
  8. // Send the result to the listener
  9. listener ! PiApproximation(pi, duration = (System.currentTimeMillis - start).millis)
  10. // Stops this actor and all its supervised children
  11. context.stop(self)
  12. }
  13. }

11、创建计算结果监听者

监听者很简单,当它接收到从 Master发来的PiApproximation ,就将结果打印出来并关闭整个 Actor系统

      
      
  1. class Listener extends Actor {
  2. def receive = {
  3. case PiApproximation(pi, duration)
  4. println("\n\tPi approximation: \t\t%s\n\tCalculation time: \t%s"
  5. .format(pi, duration))
  6. context.system.shutdown()
  7. }
  8. }

12、启动计算

现在只剩下实现启动和运行计算的执行者了。我们创建一个调用 Pi的对象, 这里我们可以继承Scala中的 Apptrait, 这个trait使我们能够在命令行上直接运行这个应用.

Pi 对象是我们的actor和消息的很好的容器。所以我们把它们都放在这儿。我们还创建一个 calculate 方法来启动  actor 并等待它结束:

      
      
  1. object Pi extends App {
  2.  
  3. calculate(nrOfWorkers = 4, nrOfElements = 10000, nrOfMessages = 10000)
  4.  
  5. // actors and messages ...
  6.  
  7. def calculate(nrOfWorkers: Int, nrOfElements: Int, nrOfMessages: Int) {
  8. // Create an Akka system
  9. val system = ActorSystem("PiSystem")
  10.  
  11. // create the result listener, which will print the result and shutdown the system
  12. val listener = system.actorOf(Props[Listener], name = "listener")
  13.  
  14. // create the master
  15. val master = system.actorOf(Props(new Master(
  16. nrOfWorkers, nrOfMessages, nrOfElements, listener)),
  17. name = "master")
  18.  
  19. // start the calculation
  20. master ! Calculate
  21.  
  22. }
  23. }

以上的 calculate 方法创建一个 Actor系统,这是包括所有创建出的actor的 “上下文”。 如何在容器中创建actor的例子在calculate方法的 ‘system.actorOf(...)’ 这一行。 这里我们创建两个顶级actor. 如果你是在一个actor上下文(i.e. 在一个创建其它actor的actor中),你应该使用 context.actorOf(...). 这在以上的主actor代码中有所体现。

好了,终于完成了。

但是在打包和运行之前,让我们看看完整的代码,包括package定义,import:

      
      
  1. /**
  2. * Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
  3. */
  4. package akka.tutorial.first.scala
  5.  
  6. import akka.actor._
  7. import akka.routing.RoundRobinRouter
  8. import akka.util.Duration
  9. import akka.util.duration._
  10.  
  11. object Pi extends App {
  12.  
  13. calculate(nrOfWorkers = 4, nrOfElements = 10000, nrOfMessages = 10000)
  14.  
  15. sealed trait PiMessage
  16. case object Calculate extends PiMessage
  17. case class Work(start: Int, nrOfElements: Int) extends PiMessage
  18. case class Result(value: Double) extends PiMessage
  19. case class PiApproximation(pi: Double, duration: Duration)
  20.  
  21. class Worker extends Actor {
  22.  
  23. def calculatePiFor(start: Int, nrOfElements: Int): Double = {
  24. var acc = 0.0
  25. for (i start until (start + nrOfElements))
  26. acc += 4.0 * (1 - (i % 2) * 2) / (2 * i + 1)
  27. acc
  28. }
  29.  
  30. def receive = {
  31. case Work(start, nrOfElements)
  32. sender ! Result(calculatePiFor(start, nrOfElements)) // perform the work
  33. }
  34. }
  35.  
  36. class Master(nrOfWorkers: Int, nrOfMessages: Int, nrOfElements: Int, listener: ActorRef)
  37. extends Actor {
  38.  
  39. var pi: Double = _
  40. var nrOfResults: Int = _
  41. val start: Long = System.currentTimeMillis
  42.  
  43. val workerRouter = context.actorOf(
  44. Props[Worker].withRouter(RoundRobinRouter(nrOfWorkers)), name = "workerRouter")
  45.  
  46. def receive = {
  47. case Calculate
  48. for (i 0 until nrOfMessages) workerRouter ! Work(i * nrOfElements, nrOfElements)
  49. case Result(value)
  50. pi += value
  51. nrOfResults += 1
  52. if (nrOfResults == nrOfMessages) {
  53. // Send the result to the listener
  54. listener ! PiApproximation(pi, duration = (System.currentTimeMillis - start).millis)
  55. // Stops this actor and all its supervised children
  56. context.stop(self)
  57. }
  58. }
  59.  
  60. }
  61.  
  62. class Listener extends Actor {
  63. def receive = {
  64. case PiApproximation(pi, duration)
  65. println("\n\tPi approximation: \t\t%s\n\tCalculation time: \t%s"
  66. .format(pi, duration))
  67. context.system.shutdown()
  68. }
  69. }
  70.  
  71.  
  72. def calculate(nrOfWorkers: Int, nrOfElements: Int, nrOfMessages: Int) {
  73. // Create an Akka system
  74. val system = ActorSystem("PiSystem")
  75.  
  76. // create the result listener, which will print the result and shutdown the system
  77. val listener = system.actorOf(Props[Listener], name = "listener")
  78.  
  79. // create the master
  80. val master = system.actorOf(Props(new Master(
  81. nrOfWorkers, nrOfMessages, nrOfElements, listener)),
  82. name = "master")
  83.  
  84. // start the calculation
  85. master ! Calculate
  86.  
  87. }
  88. }

13、作为命令行程序运行

如果你是手动输入(或者拷贝粘贴)指南中的代码到 $AKKA_HOME/akka-tutorials/akka-tutorial-first/src/main/scala/akka/tutorial/first/scala/Pi.scala 那么现在轮到你了. 开启一个shell,并进入akka安装 (cd $AKKA_HOME).

首先我们需要编译源码文件。使用Scala编译器 scalac. 我们的应用依赖于 akka-actor-2.0.jar JAR包 , 所以编译时将它加入到编译器的classpath中.

Linux/Unix/Mac 系统:

      
      
  1. $ scalac -cp lib/akka/akka-actor-2.0.jar Pi.scala

Windows 系统:

      
      
  1. C:\Users\jboner\src\akka\akka-2.0> scalac -cp lib\akka\akka-actor-2.0.jar Pi.scala

编译完就可以运行了。使用 java 来运行但是同样,我们要先将 akka-actor-2.0.jar JAR 包加入到 classpath, 而这一次还需要添加Scala运行时库 scala-library.jar 和我们自己的代码编译出的class.

Linux/Unix/Mac 系统:

     
     
  1. $ java \
  2. -cp lib/scala-library.jar:lib/akka/akka-actor-2.0.jar:. \
  3. akka.tutorial.first.scala.Pi
  4.  
  5. Pi approximation: 3.1415926435897883
  6. Calculation time: 359 millis

Windows 系统:

     
     
  1. C:\Users\jboner\src\akka\akka-2.0> java \
  2. -cp lib/scala-library.jar;lib\akka\akka-actor-2.0.jar;. \
  3. akka.tutorial.first.scala.Pi
  4.  
  5. Pi approximation: 3.1415926435897883
  6. Calculation time: 359 millis

Ok!它能跑了。

14、在SBT中运行

如果你使用SBT,那么可以直接在SBT中运行本程序。先进行编译:

Linux/Unix/Mac 系统:

      
      
  1. $ sbt
  2. > compile
  3. ...

Windows 系统:

      
      
  1. C:\Users\jboner\src\akka\akka-2.0> sbt
  2. > compile
  3. ...

以上完成后直接在SBT中运行:

     
     
  1. > run
  2. ...
  3. Pi approximation: 3.1415926435897883
  4. Calculation time: 359 millis

Ok!它能跑了。

15、从外部修改配置 (可选)

示例项目的resources目录下包含一个 application.conf文件:

     
     
  1. akka.actor.deployment {
  2. /master/workerRouter {
  3. # 取消下面两行的注释来修改计算过程,使用10个工作actor,而不是4个:
  4. #router = round-robin
  5. #nr-of-instances = 10
  6. }
  7. }

如果你取消那两行的注释,你应该会看到性能上的变化,基本上应该是更好的性能(你可能需要增加代码中的消息数量来延长应用的运行时间)。需要提醒注意的是修改的配置只在给出了路由器类型的时候才有效,所以仅取消nr-of-instances 的注释将不起作用; 参阅 Routing (Scala) 了解细节.

注意

确保 application.conf 在运行应用的classpath上。如果在SBT中运行,那么这条件应该已经满足了,否则你需要把包含该文件的目录加入到jvm的 -classpath 参数.

总结

我们已经学习了如何创建第一个Akka项目,使用Akka actor来扩展到多核cpu上(也称为垂直扩展),为cpu密集型计算进行加速。我们还学习了在命令行上或在SBT中编译和运行Akka项目的方法。

如果你有一个多核的电脑,我建议你通过修改 nrOfWorkers来尝试不同数量的工作actor来观察性能上的改进。



四、入门指南(java)

1、Introduction

Welcome to the first tutorial on how to get started with Akka and Java. We assume that you already know what Akka and Java are and will now focus on the steps necessary to start your first project.

There are two variations of this first tutorial:

  • creating a standalone project and run it from the command line
  • creating a Maven project and running it from within Maven

Since they are so similar we will present them both.

The sample application that we will create is using actors to calculate the value of Pi. Calculating Pi is a CPU intensive operation and we will utilize Akka Actors to write a concurrent solution that scales out to multi-core processors. This sample will be extended in future tutorials to use Akka Remote Actors to scale out on multiple machines in a cluster.

We will be using an algorithm that is called “embarrassingly parallel” which just means that each job is completely isolated and not coupled with any other job. Since this algorithm is so parallelizable it suits the actor model very well.

Here is the formula for the algorithm we will use:

../_images/pi-formula.png

In this particular algorithm the master splits the series into chunks which are sent out to each worker actor to be processed. When each worker has processed its chunk it sends a result back to the master which aggregates the total result.

2、Tutorial source code

If you want don’t want to type in the code and/or set up a Maven project then you can check out the full tutorial from the Akka GitHub repository. It is in the akka-tutorials/akka-tutorial-first module. You can also browse it online here, with the actual source code here.

To check out the code using Git invoke the following command and you can then you can navigate down to the tutorial.

On Linux/Unix/Mac systems:

       
       
  1. $ git clone git://github.com/akka/akka.git
  2. $ cd akka/akka-tutorials/akka-tutorial-first

On Windows systems:

       
       
  1. C:\Users\jboner\src> git clone git://github.com/akka/akka.git
  2. C:\Users\jboner\src> cd akka\akka-tutorials\akka-tutorial-first

3、Prerequisites

This tutorial assumes that you have Java 1.6 or later installed on you machine and java on your PATH. You also need to know how to run commands in a shell (ZSH, Bash, DOS etc.) and a decent text editor or IDE to type in the Java code.

You need to make sure that $JAVA_HOME environment variable is set to the root of the Java distribution. You also need to make sure that the $JAVA_HOME/bin is on your PATH.

On Linux/Unix/Mac systems:

       
       
  1. $ export JAVA_HOME=..root of Java distribution..
  2. $ export PATH=$PATH:$JAVA_HOME/bin

You can test your installation by invoking java:

       
       
  1. $ java -version
  2. java version "1.6.0_24"
  3. Java(TM) SE Runtime Environment (build 1.6.0_24-b07-334-10M3326)
  4. Java HotSpot(TM) 64-Bit Server VM (build 19.1-b02-334, mixed mode)

On Windows systems:

       
       
  1. C:\Users\jboner\src\akka> set JAVA_HOME=..root of Java distribution..
  2. C:\Users\jboner\src\akka> set PATH=%PATH%;%JAVA_HOME%/bin

You can test your installation by invoking java:

       
       
  1. C:\Users\jboner\src\akka> java -version
  2. java version "1.6.0_24"
  3. Java(TM) SE Runtime Environment (build 1.6.0_24-b07-334-10M3326)
  4. Java HotSpot(TM) 64-Bit Server VM (build 19.1-b02-334, mixed mode)

4、Downloading and installing Akka

To build and run the tutorial sample from the command line, you have to download Akka. If you prefer to use SBT to build and run the sample then you can skip this section and jump to the next one.

Let’s get the akka-2.0.zip distribution of Akka from http://akka.io/downloads/ which includes everything we need for this tutorial. Once you have downloaded the distribution unzip it in the folder you would like to have Akka installed in. In my case I choose to install it in /Users/jboner/tools/, simply by unzipping it to this directory.

You need to do one more thing in order to install Akka properly: set the AKKA_HOME environment variable to the root of the distribution. In my case I’m opening up a shell, navigating down to the distribution, and setting theAKKA_HOME variable.

On Linux/Unix/Mac systems:

       
       
  1. $ cd /Users/jboner/tools/akka-2.0
  2. $ export AKKA_HOME=`pwd`
  3. $ echo $AKKA_HOME
  4. /Users/jboner/tools/akka-2.0

On Windows systems:

       
       
  1. C:\Users\jboner\src\akka> cd akka-2.0
  2. C:\Users\jboner\src\akka\akka-2.0> set AKKA_HOME=%cd%
  3. C:\Users\jboner\src\akka\akka-2.0> echo %AKKA_HOME%
  4. C:\Users\jboner\src\akka\akka-2.0

The distribution looks like this.

On Linux/Unix/Mac systems:

       
       
  1. $ ls -1
  2. bin
  3. config
  4. deploy
  5. doc
  6. lib
  7. src

On Windows systems:

       
       
  1. C:\Users\jboner\src\akka\akka-2.0> dir
  2. bin
  3. config
  4. deploy
  5. doc
  6. lib
  7. src
  • In the bin directory we have scripts for starting the Akka Microkernel.
  • In the config directory we have the Akka conf files.
  • In the deploy directory we can place applications to be run with the microkernel.
  • In the doc directory we have the documentation, API, and doc JARs.
  • In the lib directory we have the Scala and Akka JARs.
  • In the src directory we have the source JARs for Akka.

The only JAR we will need for this tutorial (apart from the scala-library.jar JAR) is the akka-actor-2.0.jarJAR in the lib/akka directory. This is a self-contained JAR with zero dependencies and contains everything we need to write a system using Actors.

Akka is very modular and has many JARs for containing different features. The modules are:

  • akka-actor – Actors
  • akka-remote – Remote Actors
  • akka-slf4j – SLF4J Event Handler Listener for logging with SLF4J
  • akka-testkit – Toolkit for testing Actors
  • akka-kernel – Akka microkernel for running a bare-bones mini application server
  • akka-durable-mailboxes – Durable mailboxes: file-based, MongoDB, Redis, Beanstalk and Zookeeper

5、Downloading and installing Maven

Maven is an excellent build system that can be used to build both Java and Scala projects. If you want to use Maven for this tutorial then follow the following instructions, if not you can skip this section and the next.

First browse to http://maven.apache.org/download.html and download the 3.0.3 distribution.

To install Maven it is easiest to follow the instructions on http://maven.apache.org/download.html#Installation.

6、Creating an Akka Maven project

If you have not already done so, now is the time to create a Maven project for our tutorial. You do that by stepping into the directory you want to create your project in and invoking the mvn command.

On Linux/Unix/Mac systems:

       
       
  1. $ mvn archetype:generate \
  2. -DgroupId=akka.tutorial.first.java \
  3. -DartifactId=akka-tutorial-first-java \
  4. -DarchetypeArtifactId=maven-archetype-quickstart \
  5. -DinteractiveMode=false

On Windows systems:

       
       
  1. C:\Users\jboner\src\akka\akka-2.0> mvn archetype:generate \
  2. -DgroupId=akka.tutorial.first.java \
  3. -DartifactId=akka-tutorial-first-java \
  4. -DarchetypeArtifactId=maven-archetype-quickstart \
  5. -DinteractiveMode=false

Now we have the basis for our Maven-based Akka project. Let’s step into the project directory.

On Linux/Unix/Mac systems:

       
       
  1. $ cd akka-tutorial-first-java

On Windows systems:

       
       
  1. C:\Users\jboner\src\akka\akka-2.0> cd akka-tutorial-first-java

Here is the layout that Maven created:

      
      
  1. akka-tutorial-first-jboner
  2. |-- pom.xml
  3. `-- src
  4. |-- main
  5. | `-- java
  6. | `-- akka
  7. | `-- tutorial
  8. | `-- first
  9. | `-- java
  10. | `-- App.java

As you can see we already have a Java source file called App.java, let’s now rename it to Pi.java.

We also need to edit the pom.xml build file. Let’s add the dependency we need as well as the Maven repository it should download it from. The Akka Maven repository can be found at http://repo.akka.io/releases/ and Typesafe provides http://repo.typesafe.com/typesafe/releases/ that proxies several other repositories, including akka.io. It should now look something like this:

       
       
  1. <?xml version="1.0" encoding="UTF-8"?>
  2. <project xmlns="http://maven.apache.org/POM/4.0.0"
  3. xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  4. xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
  5. http://maven.apache.org/xsd/maven-4.0.0.xsd">
  6. <modelVersion>4.0.0</modelVersion>
  7.  
  8. <name>akka-tutorial-first-java</name>
  9. <groupId>akka.tutorial.first.java</groupId>
  10. <artifactId>akka-tutorial-first-java</artifactId>
  11. <packaging>jar</packaging>
  12. <version>1.0-SNAPSHOT</version>
  13. <url>http://akka.io</url>
  14.  
  15. <dependencies>
  16. <dependency>
  17. <groupId>com.typesafe.akka</groupId>
  18. <artifactId>akka-actor</artifactId>
  19. <version>2.0</version>
  20. </dependency>
  21. </dependencies>
  22.  
  23. <repositories>
  24. <repository>
  25. <id>typesafe</id>
  26. <name>Typesafe Repository</name>
  27. <url>http://repo.typesafe.com/typesafe/releases/</url>
  28. </repository>
  29. </repositories>
  30.  
  31. <build>
  32. <plugins>
  33. <plugin>
  34. <groupId>org.apache.maven.plugins</groupId>
  35. <artifactId>maven-compiler-plugin</artifactId>
  36. <version>2.3.2</version>
  37. <configuration>
  38. <source>1.6</source>
  39. <target>1.6</target>
  40. </configuration>
  41. </plugin>
  42. </plugins>
  43. </build>
  44. </project>

7、Start writing the code

Now it’s about time to start hacking.

We start by creating a Pi.java file and adding these import statements at the top of the file:

       
       
  1. import akka.actor.ActorRef;
  2. import akka.actor.ActorSystem;
  3. import akka.actor.Props;
  4. import akka.actor.UntypedActor;
  5. import akka.actor.UntypedActorFactory;
  6. import akka.routing.RoundRobinRouter;
  7. import akka.util.Duration;
  8. import java.util.concurrent.TimeUnit;

If you are using Maven in this tutorial then create the file in the src/main/java/akka/tutorial/first/javadirectory.

If you are using the command line tools then create the file wherever you want. We will create it in a directory called tutorial at the root of the Akka distribution, e.g. in$AKKA_HOME/tutorial/akka/tutorial/first/java/Pi.java.

8、Creating the messages

The design we are aiming for is to have one Master actor initiating the computation, creating a set of Workeractors. Then it splits up the work into discrete chunks, and sends these chunks to the different workers in a round-robin fashion. The master waits until all the workers have completed their work and sent back results for aggregation. When computation is completed the master sends the result to the Listener, which prints out the result.

With this in mind, let’s now create the messages that we want to have flowing in the system. We need four different messages:

  • Calculate – sent to the Master actor to start the calculation
  • Work – sent from the Master actor to the Worker actors containing the work assignment
  • Result – sent from the Worker actors to the Master actor containing the result from the worker’s calculation
  • PiApproximation – sent from the Master actor to the Listener actor containing the the final pi result and how long time the calculation took

Messages sent to actors should always be immutable to avoid sharing mutable state. So let’s start by creating three messages as immutable POJOs. We also create a wrapper Pi class to hold our implementation:

       
       
  1. static class Calculate {
  2. }
  3.  
  4. static class Work {
  5. private final int start;
  6. private final int nrOfElements;
  7.  
  8. public Work(int start, int nrOfElements) {
  9. this.start = start;
  10. this.nrOfElements = nrOfElements;
  11. }
  12.  
  13. public int getStart() {
  14. return start;
  15. }
  16.  
  17. public int getNrOfElements() {
  18. return nrOfElements;
  19. }
  20. }
  21.  
  22. static class Result {
  23. private final double value;
  24.  
  25. public Result(double value) {
  26. this.value = value;
  27. }
  28.  
  29. public double getValue() {
  30. return value;
  31. }
  32. }
  33.  
  34. static class PiApproximation {
  35. private final double pi;
  36. private final Duration duration;
  37.  
  38. public PiApproximation(double pi, Duration duration) {
  39. this.pi = pi;
  40. this.duration = duration;
  41. }
  42.  
  43. public double getPi() {
  44. return pi;
  45. }
  46.  
  47. public Duration getDuration() {
  48. return duration;
  49. }
  50. }

9、Creating the worker

Now we can create the worker actor. This is done by extending in the UntypedActor base class and defining the onReceive method. The onReceive method defines our message handler. We expect it to be able to handle the Work message so we need to add a handler for this message:

       
       
  1. public static class Worker extends UntypedActor {
  2.  
  3. // calculatePiFor ...
  4.  
  5. public void onReceive(Object message) {
  6. if (message instanceof Work) {
  7. Work work = (Work) message;
  8. double result = calculatePiFor(work.getStart(), work.getNrOfElements());
  9. getSender().tell(new Result(result), getSelf());
  10. } else {
  11. unhandled(message);
  12. }
  13. }
  14. }

As you can see we have now created an UntypedActor with a onReceive method as a handler for the Workmessage. In this handler we invoke the calculatePiFor(..) method, wrap the result in a Result message and send it back to the original sender using getContext().reply(..). In Akka the sender reference is implicitly passed along with the message so that the receiver can always reply or store away the sender reference for future use.

The only thing missing in our Worker actor is the implementation on the calculatePiFor(..) method:

       
       
  1. private double calculatePiFor(int start, int nrOfElements) {
  2. double acc = 0.0;
  3. for (int i = start * nrOfElements; i <= ((start + 1) * nrOfElements - 1); i++) {
  4. acc += 4.0 * (1 - (i % 2) * 2) / (2 * i + 1);
  5. }
  6. return acc;
  7. }

10、Creating the master

The master actor is a little bit more involved. In its constructor we create a round-robin router to make it easier to spread out the work evenly between the workers. Let’s do that first:

       
       
  1. workerRouter = this.getContext().actorOf(new Props(Worker.class).withRouter(new RoundRobinRouter(nrOfWorkers)),
  2. "workerRouter");

Now we have a router that is representing all our workers in a single abstraction. So now let’s create the master actor. We pass it three integer variables:

  • nrOfWorkers – defining how many workers we should start up
  • nrOfMessages – defining how many number chunks to send out to the workers
  • nrOfElements – defining how big the number chunks sent to each worker should be

Here is the master actor:

       
       
  1. public static class Master extends UntypedActor {
  2. private final int nrOfMessages;
  3. private final int nrOfElements;
  4.  
  5. private double pi;
  6. private int nrOfResults;
  7. private final long start = System.currentTimeMillis();
  8.  
  9. private final ActorRef listener;
  10. private final ActorRef workerRouter;
  11.  
  12. public Master(final int nrOfWorkers, int nrOfMessages, int nrOfElements, ActorRef listener) {
  13. this.nrOfMessages = nrOfMessages;
  14. this.nrOfElements = nrOfElements;
  15. this.listener = listener;
  16.  
  17. workerRouter = this.getContext().actorOf(new Props(Worker.class).withRouter(new RoundRobinRouter(nrOfWorkers)),
  18. "workerRouter");
  19. }
  20.  
  21. public void onReceive(Object message) {
  22. // handle messages ...
  23. }
  24. }

A couple of things are worth explaining further.

Note that we are passing in a ActorRef to the Master actor. This is used to report the the final result to the outside world.

But we are not done yet. We are missing the message handler for the Master actor. This message handler needs to be able to react to two different messages:

  • Calculate – which should start the calculation
  • Result – which should aggregate the different results

The Calculate handler is sending out work to all the Worker via its router.

The Result handler gets the value from the Result message and aggregates it to our pi member variable. We also keep track of how many results we have received back, and if that matches the number of tasks sent out, the Master actor considers itself done and sends the final result to the listener. When done it also invokes the getContext().stop(getSelf()) method to stop itself and all its supervised actors. In this case it has one supervised actor, the router, and this in turn has nrOfWorkers supervised actors. All of them will be stopped automatically as the invocation of any supervisor’s stop method will propagate down to all its supervised ‘children’.

Let’s capture this in code:

       
       
  1. public void onReceive(Object message) {
  2. if (message instanceof Calculate) {
  3. for (int start = 0; start < nrOfMessages; start++) {
  4. workerRouter.tell(new Work(start, nrOfElements), getSelf());
  5. }
  6. } else if (message instanceof Result) {
  7. Result result = (Result) message;
  8. pi += result.getValue();
  9. nrOfResults += 1;
  10. if (nrOfResults == nrOfMessages) {
  11. // Send the result to the listener
  12. Duration duration = Duration.create(System.currentTimeMillis() - start, TimeUnit.MILLISECONDS);
  13. listener.tell(new PiApproximation(pi, duration), getSelf());
  14. // Stops this actor and all its supervised children
  15. getContext().stop(getSelf());
  16. }
  17. } else {
  18. unhandled(message);
  19. }
  20. }

11、Creating the result listener

The listener is straightforward. When it receives the PiApproximation from the Master it prints the result and shuts down the ActorSystem.

       
       
  1. public static class Listener extends UntypedActor {
  2. public void onReceive(Object message) {
  3. if (message instanceof PiApproximation) {
  4. PiApproximation approximation = (PiApproximation) message;
  5. System.out.println(String.format("\n\tPi approximation: \t\t%s\n\tCalculation time: \t%s",
  6. approximation.getPi(), approximation.getDuration()));
  7. getContext().system().shutdown();
  8. } else {
  9. unhandled(message);
  10. }
  11. }
  12. }

12、Bootstrap the calculation

Now the only thing that is left to implement is the runner that should bootstrap and run the calculation for us. We do that by adding a main method to the enclosing Pi class in which we create a new instance of Pi and invoke method calculate in which we start up the Master actor and wait for it to finish:

       
       
  1. public class Pi {
  2.  
  3. public static void main(String[] args) {
  4. Pi pi = new Pi();
  5. pi.calculate(4, 10000, 10000);
  6. }
  7.  
  8. // actors and messages ...
  9.  
  10. public void calculate(final int nrOfWorkers, final int nrOfElements, final int nrOfMessages) {
  11. // Create an Akka system
  12. ActorSystem system = ActorSystem.create("PiSystem");
  13.  
  14. // create the result listener, which will print the result and shutdown the system
  15. final ActorRef listener = system.actorOf(new Props(Listener.class), "listener");
  16.  
  17. // create the master
  18. ActorRef master = system.actorOf(new Props(new UntypedActorFactory() {
  19. public UntypedActor create() {
  20. return new Master(nrOfWorkers, nrOfMessages, nrOfElements, listener);
  21. }
  22. }), "master");
  23.  
  24. // start the calculation
  25. master.tell(new Calculate());
  26.  
  27. }
  28. }

As you can see the calculate method above it creates an ActorSystem and this is the Akka container which will contain all actors created in that “context”. An example of how to create actors in the container is the‘system.actorOf(...)’ line in the calculate method. In this case we create two top level actors. If you instead where in an actor context, i.e. inside an actor creating other actors, you should use getContext().actorOf(...). This is illustrated in the Master code above.

That’s it. Now we are done.

Before we package it up and run it, let’s take a look at the full code now, with package declaration, imports and all:

       
       
  1. /**
  2. * Copyright (C) 2009-2012 Typesafe Inc. <http://www.typesafe.com>
  3. */
  4.  
  5. package akka.tutorial.first.java;
  6.  
  7.  
  8. import akka.actor.ActorRef;
  9. import akka.actor.ActorSystem;
  10. import akka.actor.Props;
  11. import akka.actor.UntypedActor;
  12. import akka.actor.UntypedActorFactory;
  13. import akka.routing.RoundRobinRouter;
  14. import akka.util.Duration;
  15. import java.util.concurrent.TimeUnit;
  16.  
  17.  
  18. public class Pi {
  19.  
  20. public static void main(String[] args) {
  21. Pi pi = new Pi();
  22. pi.calculate(4, 10000, 10000);
  23. }
  24.  
  25. static class Calculate {
  26. }
  27.  
  28. static class Work {
  29. private final int start;
  30. private final int nrOfElements;
  31.  
  32. public Work(int start, int nrOfElements) {
  33. this.start = start;
  34. this.nrOfElements = nrOfElements;
  35. }
  36.  
  37. public int getStart() {
  38. return start;
  39. }
  40.  
  41. public int getNrOfElements() {
  42. return nrOfElements;
  43. }
  44. }
  45.  
  46. static class Result {
  47. private final double value;
  48.  
  49. public Result(double value) {
  50. this.value = value;
  51. }
  52.  
  53. public double getValue() {
  54. return value;
  55. }
  56. }
  57.  
  58. static class PiApproximation {
  59. private final double pi;
  60. private final Duration duration;
  61.  
  62. public PiApproximation(double pi, Duration duration) {
  63. this.pi = pi;
  64. this.duration = duration;
  65. }
  66.  
  67. public double getPi() {
  68. return pi;
  69. }
  70.  
  71. public Duration getDuration() {
  72. return duration;
  73. }
  74. }
  75.  
  76.  
  77. public static class Worker extends UntypedActor {
  78.  
  79. private double calculatePiFor(int start, int nrOfElements) {
  80. double acc = 0.0;
  81. for (int i = start * nrOfElements; i <= ((start + 1) * nrOfElements - 1); i++) {
  82. acc += 4.0 * (1 - (i % 2) * 2) / (2 * i + 1);
  83. }
  84. return acc;
  85. }
  86.  
  87.  
  88. public void onReceive(Object message) {
  89. if (message instanceof Work) {
  90. Work work = (Work) message;
  91. double result = calculatePiFor(work.getStart(), work.getNrOfElements());
  92. getSender().tell(new Result(result), getSelf());
  93. } else {
  94. unhandled(message);
  95. }
  96. }
  97. }
  98.  
  99.  
  100. public static class Master extends UntypedActor {
  101. private final int nrOfMessages;
  102. private final int nrOfElements;
  103.  
  104. private double pi;
  105. private int nrOfResults;
  106. private final long start = System.currentTimeMillis();
  107.  
  108. private final ActorRef listener;
  109. private final ActorRef workerRouter;
  110.  
  111. public Master(final int nrOfWorkers, int nrOfMessages, int nrOfElements, ActorRef listener) {
  112. this.nrOfMessages = nrOfMessages;
  113. this.nrOfElements = nrOfElements;
  114. this.listener = listener;
  115.  
  116. workerRouter = this.getContext().actorOf(new Props(Worker.class).withRouter(new RoundRobinRouter(nrOfWorkers)),
  117. "workerRouter");
  118. }
  119.  
  120. public void onReceive(Object message) {
  121. if (message instanceof Calculate) {
  122. for (int start = 0; start < nrOfMessages; start++) {
  123. workerRouter.tell(new Work(start, nrOfElements), getSelf());
  124. }
  125. } else if (message instanceof Result) {
  126. Result result = (Result) message;
  127. pi += result.getValue();
  128. nrOfResults += 1;
  129. if (nrOfResults == nrOfMessages) {
  130. // Send the result to the listener
  131. Duration duration = Duration.create(System.currentTimeMillis() - start, TimeUnit.MILLISECONDS);
  132. listener.tell(new PiApproximation(pi, duration), getSelf());
  133. // Stops this actor and all its supervised children
  134. getContext().stop(getSelf());
  135. }
  136. } else {
  137. unhandled(message);
  138. }
  139. }
  140. }
  141.  
  142.  
  143. public static class Listener extends UntypedActor {
  144. public void onReceive(Object message) {
  145. if (message instanceof PiApproximation) {
  146. PiApproximation approximation = (PiApproximation) message;
  147. System.out.println(String.format("\n\tPi approximation: \t\t%s\n\tCalculation time: \t%s",
  148. approximation.getPi(), approximation.getDuration()));
  149. getContext().system().shutdown();
  150. } else {
  151. unhandled(message);
  152. }
  153. }
  154. }
  155.  
  156.  
  157. public void calculate(final int nrOfWorkers, final int nrOfElements, final int nrOfMessages) {
  158. // Create an Akka system
  159. ActorSystem system = ActorSystem.create("PiSystem");
  160.  
  161. // create the result listener, which will print the result and shutdown the system
  162. final ActorRef listener = system.actorOf(new Props(Listener.class), "listener");
  163.  
  164. // create the master
  165. ActorRef master = system.actorOf(new Props(new UntypedActorFactory() {
  166. public UntypedActor create() {
  167. return new Master(nrOfWorkers, nrOfMessages, nrOfElements, listener);
  168. }
  169. }), "master");
  170.  
  171. // start the calculation
  172. master.tell(new Calculate());
  173.  
  174. }
  175. }

13、Run it as a command line application

If you have not typed in (or copied) the code for the tutorial as$AKKA_HOME/tutorial/akka/tutorial/first/java/Pi.java then now is the time. When that’s done open up a shell and step in to the Akka distribution (cd $AKKA_HOME).

First we need to compile the source file. That is done with Java’s compiler javac. Our application depends on the akka-actor-2.0.jar and the scala-library.jar JAR files, so let’s add them to the compiler classpath when we compile the source.

On Linux/Unix/Mac systems:

      
      
  1. $ javac -cp lib/scala-library.jar:lib/akka/akka-actor-2.0.jar tutorial/akka/tutorial/first/java/Pi.java

On Windows systems:

       
       
  1. C:\Users\jboner\src\akka\akka-2.0> javac -cp \
  2. lib/scala-library.jar;lib/akka/akka-actor-2.0.jar \
  3. tutorial/akka/tutorial/first/java/Pi.java

When we have compiled the source file we are ready to run the application. This is done with java but yet again we need to add the akka-actor-2.0.jar and the scala-library.jar JAR files to the classpath as well as the classes we compiled ourselves.

On Linux/Unix/Mac systems:

      
      
  1. $ java \
  2. -cp lib/scala-library.jar:lib/akka/akka-actor-2.0.jar:. \
  3. akka.tutorial.first.scala.Pi
  4.  
  5. Pi approximation: 3.1415926435897883
  6. Calculation time: 359 millis

On Windows systems:

      
      
  1. C:\Users\jboner\src\akka\akka-2.0> java \
  2. -cp lib/scala-library.jar;lib\akka\akka-actor-2.0.jar;. \
  3. akka.tutorial.first.scala.Pi
  4.  
  5. Pi approximation: 3.1415926435897883
  6. Calculation time: 359 millis

Yippee! It is working.

14、Run it inside Maven

If you used Maven, then you can run the application directly inside Maven. First you need to compile the project.

On Linux/Unix/Mac systems:

       
       
  1. $ mvn compile

On Windows systems:

       
       
  1. C:\Users\jboner\src\akka\akka-2.0> mvn compile

When this in done we can run our application directly inside Maven.

On Linux/Unix/Mac systems:

      
      
  1. $ mvn exec:java -Dexec.mainClass="akka.tutorial.first.java.Pi"
  2. ...
  3. Pi approximation: 3.1415926435897883
  4. Calculation time: 359 millis

On Windows systems:

      
      
  1. C:\Users\jboner\src\akka\akka-2.0> mvn exec:java \
  2. -Dexec.mainClass="akka.tutorial.first.java.Pi"
  3. ...
  4. Pi approximation: 3.1415926435897883
  5. Calculation time: 359 millis

Yippee! It is working.

15、Overriding Configuration Externally (Optional)

The sample project includes an application.conf file in the resources directory:

      
      
  1. akka.actor.deployment {
  2. /master/workerRouter {
  3. # Uncomment the following two lines to change the calculation to use 10 workers instead of 4:
  4. #router = round-robin
  5. #nr-of-instances = 10
  6. }
  7. }

If you uncomment the two lines, you should see a change in performance, hopefully for the better (you might want to increase the number of messages in the code to prolong the time the application runs). It should be noted that overriding only works if a router type is given, so just uncommenting nr-of-instances does not work; see Routing (Java) for more details.

Note

Make sure that your application.conf is on the class path when you run the application. If running from inside Maven that should already be the case, otherwise you need to add the directory containing this file to the JVM’s -classpath option.

Conclusion

We have learned how to create our first Akka project using Akka’s actors to speed up a computation-intensive problem by scaling out on multi-core processors (also known as scaling up). We have also learned to compile and run an Akka project using either the tools on the command line or the SBT build system.

If you have a multi-core machine then I encourage you to try out different number of workers (number of working actors) by tweaking the nrOfWorkers variable to for example; 2, 4, 6, 8 etc. to see performance improvement by scaling up.

Happy hakking.



五、用例与部署场景

我该如何使用和部署 Akka?

Akka 可以有几种使用方式:

  • 作为一个库: 以普通jar包的形式放在classpath上,或放到web应用中的 WEB-INF/lib位置
  • 作为一个独立的应用程序,使用 Microkernel(微内核),自己有一个main类来初始化Actor系统

将Akka作为一个库

如果你是编写web应用,你估计要使用这种方式。通过添加更多的模块,可以有多种以库的形式使用Akka的方式。

将Akka用作单独的微内核

Akka 也可以以独立微内核的形式使用. 参阅 微内核 获取更多信息.



  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值