My First Akka Program

在Akka官网推荐2个入门项目,都是TypeSafe的templates。其中,入门:Akka Main in Scala;深入:Hello Akka!


入门级:Akka Main in Scala

The Obligatory Hello World: HelloWorld.scala
  1. The HelloWorld actor is the application’s “main” class; when it terminates the application will shut down—more on that later. (HelloWorld actor是应用的Main class当它终止时应用就会关闭-稍后详细介绍。)
  2. The main business logic happens in the preStart method, where aGreeteractor is created and instructed to issue that greeting we crave for. (主要业务逻辑在preStart方法中,在这个方法里一个Greeter actor被创建并指导发送我们渴求的问候。)
  3. When the greeter is done it will tell us so by sending back a message, and when that message has been received it will be passed into the behavior described by thereceivemethod where we can conclude the demonstration by stopping theHelloWorldactor. (当greeter完成了,它会通过返回一个消息来告诉我们。当消息被接收了,它会被传递到receive方法描述的行为,可以用停止HelloWorldactor来示范。)

class HelloWorld extends Actor {

  override def preStart(): Unit = {
    // create the greeter actor
    val greeter = context.actorOf(Props[Greeter], "greeter")
    // tell it to perform the greeting
    greeter ! Greeter.Greet
  }

  def receive = {
    // when the greeter is done, stop this actor and with it the application
    case Greeter.Done => context.stop(self)
  }
}

The Greeter: Greeter.scala

This is extremely simple now: after its creation this actor will not do anything until someone sends it a message, and if that happens to be an invitation to greet the world then theGreetercomplies and informs there quester that the deed has been done. (极其简单:这个actor创建后什么都不做,直到有actor向它发送消息。如果发生的是一个向world问候的邀请,然后Greeter 依从并通知发送请求者行动完成。)

object Greeter {
  case object Greet
  case object Done
}

class Greeter extends Actor {
  def receive = {
    case Greeter.Greet =>
      println("Hello World!")
      sender() ! Greeter.Done
  }
}


Main Class: Main.scala

Main.class is actually just a small wrapper around the generic launcher classakka.Main,which expects only one argument: the class name of the application’s main actor. (Main.class实际上只是一个通用启动器akka.Main的简单包装类,只要一个参数:应用的main actor的class name。)

This main method will then create the infrastructure needed for running the actors, start the given main actor and arrange for the whole application to shut down once the main actor terminates. (main方法会创建运行actors的基本结构,启动main actor,并且一旦main actor终止,安排整个应用关闭。)

Thus you will be able to run the application with a command similar to the following: 

java -classpath  akka.Main sample.hello.HelloWorld

This conveniently assumes placement of the above class definitions in packagesample.hello and it further assumes that you have the required JAR files forscala-library,typesafe-config andakka-actor available.

object Main {
  def main(args: Array[String]): Unit = {
    akka.Main.main(Array(classOf[HelloWorld].getName))
  }
}


If you need more control of the startup code than what is provided by akka.Mainyou can easily write your own main class such asMain2.scala (如果需要akka.Main的更多方法来控制启动代码,请见下面代码:)

Main2 Class: Main2.scala
object Main2 {

  def main(args: Array[String]): Unit = {
    // 创建ActorSystem
    val system = ActorSystem("Hello")
    // 创建顶层 helloWorld actor
    val a = system.actorOf(Props[HelloWorld], "helloWorld")
    // 创建 监控helloWorld actor 的terminator actor    
    system.actorOf(Props(classOf[Terminator], a), "terminator")
  }

  // 监控Actor: 一旦actor停止,log and system.shutdown。
  class Terminator(ref: ActorRef) extends Actor with ActorLogging {
    // 监控Actor    
    context watch ref
    // 收到actor,然后做出一些行为。
    def receive = {
      case Terminated(_) =>
        log.info("{} has terminated, shutting down system", ref.path)
        context.system.shutdown()
    }
  }

}

深入:Hello World!

HelloAkkaScala.scala

import akka.actor.{ ActorRef, ActorSystem, Props, Actor, Inbox }
import scala.concurrent.duration._

case object Greet
case class WhoToGreet(who: String)
case class Greeting(message: String)

class Greeter extends Actor {
  var greeting = ""

  def receive = {
    case WhoToGreet(who) => greeting = s"hello, $who"
    case Greet           => sender ! Greeting(greeting) // Send the current greeting back to the sender
  }
}

object HelloAkkaScala extends App {

  // Create the 'helloakka' actor system
  val system = ActorSystem("helloakka")

  // Create the 'greeter' actor
  val greeter = system.actorOf(Props[Greeter], "greeter")

  // Create an "actor-in-a-box"
  val inbox = Inbox.create(system)

  // Tell the 'greeter' to change its 'greeting' message
  greeter.tell(WhoToGreet("akka"), ActorRef.noSender)

  // Ask the 'greeter for the latest 'greeting'
  // Reply should go to the "actor-in-a-box"
  inbox.send(greeter, Greet)

  // Wait 5 seconds for the reply with the 'greeting' message
  val Greeting(message1) = inbox.receive(5.seconds)
  println(s"Greeting: $message1")

  // Change the greeting and ask for it again
  greeter.tell(WhoToGreet("typesafe"), ActorRef.noSender)
  inbox.send(greeter, Greet)
  val Greeting(message2) = inbox.receive(5.seconds)
  println(s"Greeting: $message2")

  val greetPrinter = system.actorOf(Props[GreetPrinter])
  // after zero seconds, send a Greet message every second to the greeter with a sender of the greetPrinter
  system.scheduler.schedule(0.seconds, 1.second, greeter, Greet)(system.dispatcher, greetPrinter)
  
}

// prints a greeting
class GreetPrinter extends Actor {
  def receive = {
    case Greeting(message) => println(message)
  }
}

Tutorial:

source code:

以上simple由一个Greeter Actor组成-持有新定义的greeting字符串;可以响应2个请求;设置一个新greeting字符串并返回这个最新的greeting。

Define our Message:

Actor没有一个公共API让我们像方法一样调用。顶替API的是,定义actor操作的可传递的消息。Messages可以是任意类型(Java或Scala)。这意味着可以发送装箱的原始值(String,Integer,Boolean等等)像消息或者像arrays,集合类型的普通数据结构。然而,自从消息被当成Actors的公共API,需要用一个适当的名字、丰富的语义、域特定的意义来定义消息,即使消息只是包装你的数据类型。这样就可以更加简单的使用、理解、debug基于actor的系统。

现在,我们要定义三个不同的消息:

  • WhoToGreet (重新定义新greeting)
  • Greet (向Actor请求最新的greeting)
  • Greeting (返回最新的greeting)
// Scala code

case object Greet
case class WhoToGreet(who: String)
case class Greeting(message: String)
通过Scala样例类和样例对象创建优异的消息,并且获得模式匹配支持-有时我们会获得益处当Actor匹配它收到的消息时。样例类的另一个优点是,它们默认是可序列化(serializable)的。

(Why case class?:1.Actor要先判断接受类型然后去行动;Scala中模式匹配最适合,so需要样例类。2.样例类默认序列花。)

(Java这里就不介绍了,可见原文。)

Dedine our Actor:

Actor是Akka的执行单元。Actors理念上是面向对象的,封装了状态和行为;但是它又很不同于Java或Scala的常规对象。Actor模型阻止在Actors之间传递状态,查看另一个Actor状态的唯一方法是向它发送一个请求状态的消息。Actors是非常轻量级的,只会因为内存约束它,每个actor只消耗几百bytes-意味着你可以创建几百万个并发Actors在一个单应用中。它们有独立的原则,联合事件驱动(我们一会儿详谈)和位置透明,可以通过直观的方式很轻松的解决困难的并发和会扩展的问题。

现在,让我们用Scala实现Actor。

class Greeter extends Actor {
  var greeting = ""

  def receive = {
    case WhoToGreet(who) => greeting = s"hello, $who"
    case Greet           => sender ! Greeting(greeting) // Send the current greeting back to the sender
  }
}

不是必须的行为,通过称为偏函数表示的receive方法定义;意味着所有没被case匹配的消息会被认为没有被操控,Akka自动的把它们传递到unhandled()方法。

(未匹配的消息会被自动的获取。)

Create our Actor:

我们已经定义了Actor和消息。现在,让我们创建一个Actor实例。在Akka中,不能使用new这种正规的方式创建Actor实例,替代的是创建一个工厂。工厂返回的并不是Actor实例,而是一个ActorRef(Actor引用)指向我们的Actor实例

这个中间层增加了许多功能和灵活性。ActorRef  使位置透明成为可能,保持相同的语义,表示一个在线或远程运行的actor实例。即位置不重要。这也意味着,在系统运行时需要被优化,也可以通过改变actor的位置或在应用的拓扑来优化。另一件这个中间层提供的是,失败管理的"let it crash"模型,通过崩溃和重启错误actors来自愈的系统。

ActorSystem 就时Akka的工厂,它有些类似于Spring的BeanFactroy-也可以起到Actors容器的作用,管理它们的生命周期之类的。可以通过工厂方法actorOf 创建一个Actor。这个方法接收一个配置对象-Props,一个Actor名字。在Akka中,Actor(ActorSystem) 名字很重要,例如:查询Actors;或在configuration file文件配置actors,所以需要花时间给出一个合适的名字。

(ActorRef使actor的位置(路径)不再重要;全权代表了Actor,无论它在哪里。)

  // Create the 'helloakka' actor system
  val system = ActorSystem("helloakka")

  // Create the 'greeter' actor
  val greeter = system.actorOf(Props[Greeter], "greeter")
现在,我们运行了一个Actor实例 Greeter

Tell the Actor(to do something):

所有Actors间的交流都是通过异步消息传递完成的。这就是使Actors响应式和事件驱动的方式。Actor在被告知做某事之前什么都不做,你要通过发送消息去告知它做什么。异步的发送消息,意味着发送者作为接收者时,不会停留等待让程序继续的消息。替代的是,Actor传递这个消息到接收者邮箱,然后自由的去做一些事,比等待消息对接收者有作用更重要的事。Actors的邮箱本质上是个有顺序语义的消息队列,这保证了同一个Actor发送的多个消息的顺序可以被保存,虽然它们可以被其它Actor发送的消息交织在一起。

你可能会疑惑:当不处理消息时,Actor会做些什么,是实际工作么?它处在一个挂起状态,除了内存不会消耗任何资源。

通过把要让Actor做的事情放入消息,传递给ActorRef中的tell方法。这个方法把消息放如actor的邮箱里,然后立即返回。

  // Tell the 'greeter' to change its 'greeting' message
  greeter.tell(WhoToGreet("akka"), ActorRef.noSender)
或者使用自定义操作符"!",这是更加常用的做法。
  greeter ! WhoToGreet("akka") 
注:对于 ActorRef . tell方法和 ActorRef.! 方法 的源代码探索。

<pre name="code" class="java"><pre name="code" class="java">// 此方法是抽象方法,会被其子类重写。
def !(message: Any)(implicit sender: ActorRef = Actor.noSender): Unit

 // 可以看出 tell方法依赖于 ! 方法。final def tell(msg: Any, sender: ActorRef): Unit = this.!(msg)(sender) 
 由上面代码我们可以知道以下几点: 

  1. ! 方法会使用currying方式加载一个隐含变量sender
  2. 只是发送消息,而不准备回复的话,不给出sender,其就会默认Actor.noSender(源码中可知其是null);如果发送消息后,还准备恢复一个Actor的话,可以给出sender。例如:
    sender.!(msg)(sender):
Relying to an Actor:
  • 自身引用(self):有时沟通模式不只是单向的沟通,而是本身对于请求的答复。一个明确的方式就是:添加自身的引用做为消息的一部分,那么接收者就能使用这个引用发送恢复给你了。这是Akka支持的常见方案-你发送的每条消息你都可以选择传递sender(ActorRef)引用。如果你从一个Actor中发送消息,那么你可以通过self引用访问你自己的ActorRef请注意:不能使用this。(Java中使用getSelf()方法获得self引用,Scala中使用self方法。)。Scala,可以直接使用隐含变量self获得自身ActorRef引用;Akka源码如下:
    implicit final val self = context.self 
    Scala使用隐含变量自动的把self加入方法中了。例如,在Actor 'A'中发送消息,Actor 'A'的自身ActorRef引用就自动加入方法中:
    // Actor A 调用
    greeter ! Greet  // 等同于 greeter.!(Greet)(self) 也等同于 greeter.tell(Greet, self)
    
    注:如果没有给出sender,那么dead-letterActor的引用就会成为sender被使用。dead-letter Actor是所有为处理消息的终点站;可以使用Akka的Event Bus订阅它们。
  • 发送者引用(sender):在接收者Actor处理消息时,可以使用sender引用。因为每条消息都有唯一的sender引用与之匹配,当前的sender引用就会随着你处理每条新消息的改变而改变。如果因为某些原因,你要稍后再使用一个特别的sender引用,那么你需要持有它,存储到内存或其它类似的地方。(Scala中,函数sender会返回这个sender引用。)
    // From within the Greeter Actor
    sender ! Greeting(greeting)
Using Inbox:

大部分现实工作中的Actor应用都使用超过一个Actor。Actor模型的创造者Carl Hewitt最近在一次采访中说:“One Actor is no Actor. Actors come in systems.”。这是一个重要的名言。要真正的利用Actor模型,需要使用大量的Actors。在Actor编程中,每个困难问题都能通过增加更多Actor来解决;把问题分解成子任务,委派给新Actors。

然而,为简单起见,我们只用了一个单Actor在这个例子中。这意味着如果我们在一个main程序中与Actor通信,那么我们就没有sender,我们没有从其它Actor中与这个Actor通信。幸运的是Akka对这个问题,有一个很好的解决方案:Inbox

Inbox允许你创建一个"actor-in-a-box",它包含一个Actor-即:向其它Actors发送消息、接受回复的傀儡。Inbox使用Inbox.create 来创建对象;使用 inbox.send 发送消息。内部Actor把收到的消息放入队列,可以通过调用 inbox.receive 取得回复消息;如果队列是空的,那么调用阻塞,直到有可用的消息。

如你所知,阻塞很影像性能、约束扩展性,你需要很谨慎小心。那么说,我们使用它在这个例子,因为它简化了这个例子,很容易跟随消息流。

// Create an "actor-in-a-box"
val inbox = Inbox.create(system)

// Tell the 'greeter' to change its 'greeting' message
greeter tell WhoToGreet("akka")

// Ask the 'greeter for the latest 'greeting'
// Reply should go to the mailbox
inbox.send(greeter, Greet)

// Wait 5 seconds for the reply with the 'greeting' message
val Greeting(message) = inbox.receive(5.seconds)
println(s"Greeting: $message")

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值