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
当我们停止first
Actor时,在停止前它会先去停止它的子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