【AKKA 官方文档翻译】第一部分:Actor架构

第一部分:Actor架构

akka版本2.5.8
版权声明:本文为博主原创文章,未经博主允许不得转载。

Akka为你提供了创建actor系统的一些基础功能,并且已经实现了控制基本行为必需的底层代码。为了体会这一点,我们来看看你创建的actor角色与akka内部创建的管理角色的关系,顺便了解下actor的生命周期和失败处理方式。

akka actor的层次结构

akka里的actor总是属于其父母。通常你通过调用 context.actorOf() 创建一个actor。这种方式向现有的actor树内加入了一个新的actor,这个actor的创建者就成为了这个actor的父actor。你可能会问,谁是你创建的第一个actor的父actor呢?

如下图所示,所有actor都有一个共同的家长,称为user guardian。可以通过调用 system.actorOf() 来创建属于它新actor实例。正如我们在快速入门指南中所介绍的,创建actor将返回一个有效的URL的引用,因此,如果我们通过调用system.actorOf(…, "someActor")创建一个名为someActor的actor时,其引用将包含路径/user/someActor

这里写图片描述

实际上在你调用你创建actor的代码之前,akka已经在系统中创建了三个actor。这些内置的actor名字都包含guardian,因为它们会监管它们各自路径下的所有子actor。guardian actor包括:

/所谓的根监护人。这是系统中所有actor的父亲,当系统被终止时,它也是最后一个被停止的。

/user 这是所有用户创建的actor的父亲。不要被user这个名字所迷惑,他与最终用户没有关系,也和用户处理无关。你使用akka库所创建的所有actor的路径都将以/user/开头
/system系统监护人

在Hello World示例中,我们已经看到了如何使用system.actorOf()来创建一个在/user路径下的actor。尽管它只是在用户创建的层次的最高级actor,但是我们把它称作顶级actor。在你的ActorSystem里,你通常只有一个(或者非常少)顶级actor。我们在已有的actor里通过调用context.actorOf()来创建新的非顶级actor,即子actor。context.actorOf()的方法签名和system.actorOf()相同。

查看actor层级结构最简单的方式就是打印ActorRef的实例。在这个小实验中,我们创建了一个actor,打印了它的引用,为他创建了一个子actor,并打印其子actor的引用。我们从Hello World工程开始,如果你还没有下载它,请从Lightbend Tech Hub下载Quickstart工程。

在你的Hello World工程里,导航到com.lightbend.akka.sample包的位置,在这里创建一个新Scala文件ActorHierarchyExperiments.scala。复制粘贴下面的代码到这个新文件里。保存文件并运行sbt "runMain com.lightbend.akka.sample.ActorHierarchyExperiments",然后观察输出。

package com.lightbend.akka.sample

import akka.actor.{ Actor, Props, ActorSystem }
import scala.io.StdIn

class PrintMyActorRefActor extends Actor {
  override def receive: Receive = {
    case "printit"val secondRef = context.actorOf(Props.empty, "second-actor")
      println(s"Second: $secondRef")
  }
}

object ActorHierarchyExperiments extends App {
  val system = ActorSystem("testSystem")

  val firstRef = system.actorOf(Props[PrintMyActorRefActor], "first-actor")
  println(s"First: $firstRef")
  firstRef ! "printit"

  println(">>> Press ENTER to exit <<<")
  try StdIn.readLine()
  finally system.terminate()
}

注意一点,即如何给第一个actor发送一个消息来使它开始工作。我们使用父类引用来发送信息:firstRef ! "printit"。当程序运行的时候,输出信息会包含第一个actor的引用,也包含在printit中创建的子actor的引用。你的输出应该看上去像这样:

First: Actor[akka://testSystem/user/first-actor#1053618476]

Second: Actor[akka://testSystem/user/first-actor/second-actor#-1544706041]

请注意引用结构:

两个路径都以akka://testSystem/开头,所有的actor引用都是合法的URL。akka://是协议字段。

接下来,就像在World Wide Web上一样,URL是一个系统的标识。在这个示例里,系统的名称为testSystem,它也可以被命名为任意名字。如果开启了多系统间的远程通信,URL里就会包含主机名,以便其他系统可以在网络上找到它。

因为第二个actor引用的路径里包含/first-actor/,这表明它是第一个actor的子actor。

actor引用的最后一段,#1053618476#-1544706041是一个actor的唯一标识符uid,你在大多数场景下可以忽略它的存在。

现在你似乎了解了actor系统的层次结构,你可能会想:为什么我们需要这个层次结构?他是干什么用的呢?

层次结构的一个很重要的功能是来安全地管理actor的生命周期,接下来让我们思考一下怎么利用它来写出优秀的代码。

actor的生命周期

actor在被创建后存在,并且在用户请求关闭时消失。当actor被关闭后,其所有的子actor都将被递归地关闭。这个特性极大简化了我们的资源清理工作,并且防止资源泄露(套接字或者文件等)。实际上,在进行底层多线程编程编程时,我们经常会小看对各种并发资源生命周期管理的难度。

为了关闭actor,我们推荐在actor内部调用context.stop(self)来自我关闭,这个代码经常放在用户定义的结束信息回应里,或者在actor已经做完了其工作后自行调用。停止其他的actor在技术上是允许的,可以通过调用context.stop(actorRef)来实现。但是用这种方式来停止一个actor是一个坏习惯,但是你可以给这个actor发送PoisonPill或者自定义关闭消息来关闭它。

Akka的actor提供了很对生命周期API,你可以在实现actor时重载这些方法。最常用的是preStart()postStop()

preStart()会在actor启动后,并在它处理第一个消息之前被调用

postStop()会在actor将要被关闭前被调用,在它之后,actor不会再处理任何消息了

让我们简单实验下如何使用preStart()postStop()生命周期钩子去观察actor关闭时的行为。首先,在你的工程里添加以下两个actor类:

class StartStopActor1 extends Actor {
  override def preStart(): Unit = {
    println("first started")
    context.actorOf(Props[StartStopActor2], "second")
  }
  override def postStop(): Unit = println("first stopped")

  override def receive: Receive = {
    case "stop" ⇒ context.stop(self)
  }
}

class StartStopActor2 extends Actor {
  override def preStart(): Unit = println("second started")
  override def postStop(): Unit = println("second stopped")

  // Actor.emptyBehavior 是一个有用的占位符
  // 在我们不想用这个actor处理任何信息是使用它
  override def receive: Receive = Actor.emptyBehavior
}

像之前一样创建一个主类(main),负责创建actor并且在之后给它们发送"stop"消息。

val first = system.actorOf(Props[StartStopActor1], "first")
first ! "stop"

你可以再次使用sbt来启动这个程序,得到的输出应该是这样的:

first started

second started

second stopped

first stopped

当我们停止firstactor时,它在自己被关闭前关闭了它的子actor:second。这个顺序是严格被执行的,所有子actor的postStop()钩子都将在它们的父actorpostStop()调用之前被调用。

Akka参考手册中Actor生命周期部分提供了有关整个生命周期钩子的详细信息。

失败处理

父actor和子actor在整个声明周期内都保持着联系。当一个actor失败了(抛出异常或者在receive里冒出一个未处理的异常),他会被暂时地挂起。就像之前提到的一样,失败信息会被传递到父actor中,然后由父actor来决定如何处理这个子actor产生的异常。在这种方式下,父actor就是子actor的监管者,默认的监管策略就是停止并且重启子actor。如果你没有修改默认的监管策略,那么所有的失败都会导致重启actor。

让我们在一个简单的实验中来测试下默认的监管策略。就像之前一样,在你的工程里添加以下的类。

class SupervisingActor extends Actor {
  val child = context.actorOf(Props[SupervisedActor], "supervised-actor")

  override def receive: Receive = {
    case "failChild" ⇒ child ! "fail"
  }
}

class SupervisedActor extends Actor {
  override def preStart(): Unit = println("supervised actor started")
  override def postStop(): Unit = println("supervised actor stopped")

  override def receive: Receive = {
    case "fail" ⇒
      println("supervised actor fails now")
      throw new Exception("I failed!")
  }
}

并且在mian里用以下代码运行:

val supervisingActor = system.actorOf(Props[SupervisingActor], "supervising-actor")
supervisingActor ! "failChild"

你应该会看到类似如下输出:

supervised actor started
supervised actor fails now
supervised actor stopped
supervised actor started
[ERROR] [03/29/2017 10:47:14.150] [testSystem-akka.actor.default-dispatcher-2] [akka://testSystem/user/supervising-actor/supervised-actor] I failed!
java.lang.Exception: I failed!
        at tutorial_1.SupervisedActor$$anonfun$receive$4.applyOrElse(ActorHierarchyExperiments.scala:57)
        at akka.actor.Actor$class.aroundReceive(Actor.scala:513)
        at tutorial_1.SupervisedActor.aroundReceive(ActorHierarchyExperiments.scala:47)
        at akka.actor.ActorCell.receiveMessage(ActorCell.scala:519)
        at akka.actor.ActorCell.invoke(ActorCell.scala:488)
        at akka.dispatch.Mailbox.processMailbox(Mailbox.scala:257)
        at akka.dispatch.Mailbox.run(Mailbox.scala:224)
        at akka.dispatch.Mailbox.exec(Mailbox.scala:234)
        at akka.dispatch.forkjoin.ForkJoinTask.doExec(ForkJoinTask.java:260)
        at akka.dispatch.forkjoin.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1339)
        at akka.dispatch.forkjoin.ForkJoinPool.runWorker(ForkJoinPool.java:1979)
        at akka.dispatch.forkjoin.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:107)

我们看到在actor失败后,被监管的actor被立即停止并重启,我们也看到了一条这个异常被处理的日志。在测试中,我们使用preStart()postStop()钩子,这些钩子可以在actor被重启前后被调用,所以我们不能用它来区分actor是第一次启动还是被重启。重启actor在大多数情况是正确的,重启可以让actor恢复到一个已知的正确状态上,也就是启动时的干净状态。在内部真正发生的是:preRestart()postRestart()方法被调用,如果它们没有被重载,则它们分别会调用postStop()preStart()你可以尝试重载这些方法,看看输出的改变。

如果你已经不耐烦了,那么你可以在监督参考页面里获得更多深入的细节。

总结

我们已经学到了Akka是如何管理actor的,actor是有层级结构的,并且父actor监管它们的子actor,并处理子actor的异常。我们看到了如何创建一个非常简单的actor和子actor。接下来,我们会通过通信建模将这些知识应用在我们的实例里,以便从设备actor里获取信息。之后,我们会学到如何在组里管理actor。

转载于:https://www.cnblogs.com/wangbinquan/p/9061242.html

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值