6.入门指南-Part1: Actor架构

Part1: Actor架构

依赖

在你的项目中添加如下依赖:

<properties>
  <scala.binary.version>2.13</scala.binary.version>
</properties>
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>com.typesafe.akka</groupId>
      <artifactId>akka-bom_${scala.binary.version}</artifactId>
      <version>2.6.19</version>
      <type>pom</type>
      <scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>
<dependencies>
  <dependency>
    <groupId>com.typesafe.akka</groupId>
    <artifactId>akka-actor-typed_${scala.binary.version}</artifactId>
  </dependency>
</dependencies>

引言

使用 Akka 使您无需为 Actor 系统创建基础设施,也无需编写控制基本行为所需的低级代码。 为了理解这一点,让我们看看您在代码中创建的Actor与 Akka 在内部为您创建和管理的Actor之间的关系、Actor的生命周期以及如何处理故障。

Akka actor层次架构

在Akka中,一个Actor总是有一个父级。您可以通过调用ActorContext.spawn()来创建一个Actor。创建Actor的创建者是新创建出来的子Actor的父级。你可能会问,你创建的第一个Actor的父级是谁?
如下图所示,所有 Actor 都有一个共同的父级,用户守护者是在您启动 ActorSystem 时定义和创建的。 正如我们在快速入门指南中介绍的那样,创建Actor会返回一个有效 URL 的引用。 因此,例如,如果我们使用 context.spawn(someBehavior, "someActor") 从用户守护者创建一个名为 someActor 的演员,它的引用路径是 /user/someActor
在这里插入图片描述
实际上,你的第一个Actor启动前,Akka已经在系统中创建了两个Actor。这些内置Actor的名字包含守护对象。守护Actor包括:

  • / root守护对象。这是在系统中所有Actor的父级,也是在系统关闭时最后停止的一个。
  • /system 系统守护对象。Akka 或其他构建在 Akka 之上的库可以在系统命名空间中创建Actor。
  • /user 用户守护对象。这是您提供的用于启动应用程序中所有其他Actor的顶级Actor。

查看 Actor 层次结构的最简单方法是打印 ActorRef 实例。 在这个小实验中,我们创建了一个 actor,打印它的引用,创建这个 actor 的一个子节点,并打印该子节点的引用。 我们从 Hello World 项目开始,如果您还没有下载它,请从 Lightbend 技术中心下载 Quickstart 项目。

在您的Hello World项目中,进入到com.example包下,并为下面代码片段中的每个类创建一个 Java 文件并复制相应的内容。 保存文件并从构建工具或 IDE 运行 com.example.ActorHierarchyExperiments 以观察输出。

请注意消息要求第一个Actor完成工作的方式。 我们使用父级的引用发送消息:firstRef.tell("printit", ActorRef.noSender())。当代码执行时,输出包括第一个Actor的引用和它作为 printit 案例的一部分创建的子节点。 您的输出应类似于以下内容:

First: Actor[akka://testSystem/user/first-actor#96948015]
Second: Actor[akka://testSystem/user/first-actor/second-actor#-1516441762]

注意引用的结构:

  • 两个路径都是以akka://testSystem/开头。由于所有Actor引用都是有效的 URL,所以 akka:// 是协议字段的值。
  • 接下来,就像在万维网上一样,URL 标识系统。 在此示例中,系统名为 testSystem,但它可以是任何其他名称。 如果启用了多个系统之间的远程通信,则 URL 的这一部分包含主机名,以便其他系统可以在网络上找到它。
  • 因为第二个Actor的引用包含路径/first-actor/,所以它将它其标识为第一个Actor的子节点。
  • Actor引用的最后一部分,#96948015#-1516441762 是一个唯一标识符,在大多数情况下您可以忽略它。

现在您了解了Actor层次结构的样子,您可能想知道:为什么我们需要这个层次结构? 它是干什么用的?

层次结构的一个重要作用是安全地管理Actor的生命周期。 接下来让我们考虑一下,看看这些知识如何帮助我们编写更好的代码。

Actor生命周期

Actor 在创建时出现,然后在用户请求时停止。 每当一个actor停止时,它的所有子节点也会递归停止。 这种行为极大地简化了资源清理,并有助于避免诸如由打开的套接字和文件引起的资源泄漏。 事实上,在处理低级多线程代码时,一个经常被忽视的难点是各种并发资源的生命周期管理。

要停止Actor,推荐的模式是在Actor内部返回 Behaviors.stopped() 以停止自身,通常作为对某些用户定义的停止消息的响应或当Actor完成其工作时。 通过从父级调用 context.stop(childRef) 来停止子actor在技术上是可能的,但不可能以这种方式停止任意(非子)actor。

Akka actor API 公开了一些生命周期触发机制,例如 PostStop 在 actor 停止后立即发送。 在此之后不会处理任何消息。

让我们在一个简单的实验中使用 PostStop 生命周期触发机制来观察我们停止Actor时的行为。 首先,将以下 2 个Actor类添加到您的项目中:

class StartStopActor1 extends AbstractBehavior<String> {

  static Behavior<String> create() {
    return Behaviors.setup(StartStopActor1::new);
  }

  private StartStopActor1(ActorContext<String> context) {
    super(context);
    System.out.println("first started");

    context.spawn(StartStopActor2.create(), "second");
  }

  @Override
  public Receive<String> createReceive() {
    return newReceiveBuilder()
        .onMessageEquals("stop", Behaviors::stopped)
        .onSignal(PostStop.class, signal -> onPostStop())
        .build();
  }

  private Behavior<String> onPostStop() {
    System.out.println("first stopped");
    return this;
  }
}

class StartStopActor2 extends AbstractBehavior<String> {

  static Behavior<String> create() {
    return Behaviors.setup(StartStopActor2::new);
  }

  private StartStopActor2(ActorContext<String> context) {
    super(context);
    System.out.println("second started");
  }

  @Override
  public Receive<String> createReceive() {
    return newReceiveBuilder().onSignal(PostStop.class, signal -> onPostStop()).build();
  }

  private Behavior<String> onPostStop() {
    System.out.println("second stopped");
    return this;
  }
}

然后像上面一样创建一个Main类,用来启动Actor然后发送stop消息:

ActorRef<String> first = context.spawn(StartStopActor1.create(), "first");
first.tell("stop");

您也可以使用sbt来启动程序。输出应如下所示:

first started
second started
second stopped
first stopped

当我们停止firstActor时,在停止前它会先去停止它的子Actorsecond。这个顺序是严格的,所有子节点的 PostStop 信号在处理父节点的 PostStop 信号之前被处理。

异常处理

父子Actor在他们的整个生命周期中都是相互联系的。 每当Actor失败(抛出异常或从 Receive 中冒出未处理的异常)时,失败信息就会传播到监督策略,然后由监督策略决定如何处理由Actor引起的异常。 监督策略通常由父 Actor 在生成子 Actor 时定义。 这样,父Actor就充当了子Actor的监督者。 默认的主管策略是停止子Actor。 如果您不定义策略,所有失败都会导致停止。

让我们在一个简单的实验中观察重启监督策略。 将以下类添加到您的项目中,就像您对之前的类所做的那样:

class SupervisingActor extends AbstractBehavior<String> {

  static Behavior<String> create() {
    return Behaviors.setup(SupervisingActor::new);
  }

  private final ActorRef<String> child;

  private SupervisingActor(ActorContext<String> context) {
    super(context);
    child =
        context.spawn(
            Behaviors.supervise(SupervisedActor.create()).onFailure(SupervisorStrategy.restart()),
            "supervised-actor");
  }

  @Override
  public Receive<String> createReceive() {
    return newReceiveBuilder().onMessageEquals("failChild", this::onFailChild).build();
  }

  private Behavior<String> onFailChild() {
    child.tell("fail");
    return this;
  }
}

class SupervisedActor extends AbstractBehavior<String> {

  static Behavior<String> create() {
    return Behaviors.setup(SupervisedActor::new);
  }

  private SupervisedActor(ActorContext<String> context) {
    super(context);
    System.out.println("supervised actor started");
  }

  @Override
  public Receive<String> createReceive() {
    return newReceiveBuilder()
        .onMessageEquals("fail", this::fail)
        .onSignal(PreRestart.class, signal -> preRestart())
        .onSignal(PostStop.class, signal -> postStop())
        .build();
  }

  private Behavior<String> fail() {
    System.out.println("supervised actor fails now");
    throw new RuntimeException("I failed!");
  }

  private Behavior<String> preRestart() {
    System.out.println("supervised will be restarted");
    return this;
  }

  private Behavior<String> postStop() {
    System.out.println("supervised stopped");
    return this;
  }
}

然后运行:

ActorRef<String> supervisingActor =
    context.spawn(SupervisingActor.create(), "supervising-actor");
supervisingActor.tell("failChild");

您应该会看到类似于以下内容的输出:

supervised actor started
supervised actor fails now
supervised actor will be restarted
supervised actor started
[ERROR] [11/12/2018 12:03:27.171] [ActorHierarchyExperiments-akka.actor.default-dispatcher-2] [akka://ActorHierarchyExperiments/user/supervising-actor/supervised-actor] Supervisor akka.actor.typed.internal.RestartSupervisor@1c452254 saw failure: I failed!
java.lang.Exception: I failed!
	at typed.tutorial_1.SupervisedActor.onMessage(ActorHierarchyExperiments.scala:113)
	at typed.tutorial_1.SupervisedActor.onMessage(ActorHierarchyExperiments.scala:106)
	at akka.actor.typed.scaladsl.AbstractBehavior.receive(AbstractBehavior.scala:59)
	at akka.actor.typed.Behavior$.interpret(Behavior.scala:395)
	at akka.actor.typed.Behavior$.interpretMessage(Behavior.scala:369)
	at akka.actor.typed.internal.InterceptorImpl$$anon$2.apply(InterceptorImpl.scala:49)
	at akka.actor.typed.internal.SimpleSupervisor.aroundReceive(Supervision.scala:85)
	at akka.actor.typed.internal.InterceptorImpl.receive(InterceptorImpl.scala:70)
	at akka.actor.typed.Behavior$.interpret(Behavior.scala:395)
	at akka.actor.typed.Behavior$.interpretMessage(Behavior.scala:369)

我们看到,在失败后,受监督的 Actor 被停止并立即重新启动。 我们还看到一个日志条目报告已处理的异常,在这种情况下,是我们的测试异常。 在此示例中,我们还使用了在重新启动之前处理的 PreRestart 信号。

如果您想更多了解,我们还建议您查看容错参考页面以获取更深入的详细信息。

总结

我们已经了解了 Akka 如何在父Actor监督子Actor及异常处理的层次结构中管理Actor。 我们看到了如何创建一个非常简单的Actor及其子Actor。 接下来,我们将通过对从设备Actor获取信息所需的通信进行建模,将这些知识应用到我们的示例用例中。 稍后,我们将讨论如何管理组中的Actor。

Next:Part2: 创建第一个Actor

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值