Akka官方文档2.5.17(四)——Actor引用、路径、地址

本文深入探讨了Actor模型中Actor引用和路径的概念,解释了两者之间的区别,如何获取Actor引用,以及Actor路径的组成和作用。同时,文章还讨论了Actor路径的别名问题,如何重用Actor路径,以及Actor系统在远程部署时的互操作性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

什么是Actor引用?

什么是Actor路径?

Actor引用和路径之间有什么区别?

Actor路径anchor

Actor逻辑路径

Actor物理路径

Actor路径别名或者符号链接?

怎么获取Actor引用

创建Actors

通过具体地址查找Actor

 查询Actor逻辑层次结构

Actor引用和路径相等

重用Actor路径

与远程部署的互操作

地址部分的作用是什么?

Actor路径的顶层作用域


本节描述如何在一个可能的分布式Actor系统中识别和定位Actor。

上图展现了一个Actor系统中最重要的实体之间的关系,请继续阅读以获得更详细的信息。

什么是Actor引用?

一个Actor引用是ActorRef的子类,其最主要的目的是向它所表示的Actor发送消息。每个Actor都可以通过self字段访问它的canonical(本地)引用;当发送消息给其他Actors,这个引用也将作为默认的发送引用。相反地,在处理消息时,Actor可以通过sender()方法获取到当前消息发送者的Actor引用。

依据Actor系统的配置,可以支持几种不同类型的Actor引用:

  • 纯本地Actor引用在未配置支持网络功能的Actor系统中使用。这些Actor引用通过网络传输到远程JVM时,不会起任何作用
  • 本地Actor引用,当启用远程功能时,被Actor系统用来支持那些代表同一JVM中的Actor引用的网络功能。为了能够访问其他网络节点,这些引用包含了协议和远程地址的信息
  • 有一种本地Actor引用的子类被用来当作路由(Actor中混入Router特质),它的逻辑结构与前面提到的本地引用相同,但是向它们发送消息会直接转发给它们的一个子节点
  • 远程Actor引用表示使用远程通信可达的Actor,即向它们发送消息时,会透明地序列化该消息并将它们发送到远程的JVM
  • 有几种特殊类型的actor引用,其行为类似于本地actor引用:
    • PromiseActorRef是Promise的特殊表示,它的目的是接收Actor返回的响应。akka.pattern.ask会创建该类Actor引用
    • DeadLetterActorRef是dead letters服务的默认实现,Akka会将所有接收者已经关闭或不存在的消息路由到该服务
    • EmptyLocalActorRef是Akka在查找不存在的本地Actor路径时返回的东西:它等同于DeadLetterActorRef,但它保留了它的路径,以便Akka可以通过网络发送它并将其路径与其他存在的Actor引用路径进行比较,这些Actor引用可能在该Actor消失之前得到
  • 然后有一些一次性的内部实现:
    • 有一个Actor引用不代表一个Actor,但作为Root Guardian的伪监督者,我们称之为“bubble-walker”
    • 在实际启动Actor的创建之前启动的第一个日志记录服务是一个伪Actor引用,它接受日志事件并将它们直接打印到标准输出; 它是Logging.StandardOutLogger

什么是Actor路径?

由于Actor是以严格的层次方式创建的,每个Actor存在一个独特的序列,这个序列是通过递归的从根节点朝下查找定义的。此序列可以看作是文件系统中的文件夹,因此我们采用名称“路径”来表示它,尽管Actor层次结构与文件系统层次结构存在一些根本上的区别。

一个Actor路径包含了anchor,它标识了Actor系统,然后是路径元素,从Root Guardian到指定的Actor; 路径元素是遍历所经过的Actor的名字,并用斜杠分隔。

Actor引用和路径之间有什么区别?

一个Actor引用指定了一个单一的Actor,引用的生命周期与Actor的生命周期匹配;一个Actor路径代表了一个名字,该名字可能是也可能不是指向一个特定的Actor,而且路径本身没有生命周期,永远不会失效。你可以创建一个Actor路径而不创建一个Actor,但不创建Actor就无法创建Actor引用。

你可以创建一个Actor,终止它,然后创建一个具有相同Actor路径的新Actor。 新创建的Actor是Actor的一个新实例, 这两个不是同一个Actor。一个指向旧实例的Actor引用对新实例不起作用。即使具有相同的路径,发送到旧Actor引用的消息,也不会传递给新的实例。

Actor路径anchor

每个Actor路径都有一个地址部分,描述了访问这个Actor时所需要的协议和位置,然后是从根遍历到指定Actor的路径上的Actor名字(以斜杠分隔),如以下示例:

"akka://my-sys/user/service-a/worker1"                          //纯本地
"akka.tcp://my-sys@host.example.com:5678/user/service-b"         //远程

这里,akka.tcp是2.4版本的默认远程传输协议,其他传输协议也是可行的。主机和端口部分的解析(即示例中的host.example.com:5678)取决于所使用的传输协议的机制, 但它必须遵守URI结构规范。

Actor逻辑路径

通过遵循从指定Actor沿着父监督者链接到Root Guardian得到的唯一路径称为Actor的逻辑路径。因此只要设置了Actor系统的远程配置(以及路径的地址部分),它就可以完全确定了。

Actor物理路径

虽然Actor逻辑路径描述了一个Actor系统内的功能位置,但是基于配置的远程部署方式意味着一个Actor可能在另外一台网络主机上被创建,即在另一个Actor系统内创建远程Actor。在这种情况下,遵循从Root Guardian到指定Actor的Actor路径需要访问网络,这是一项代价高昂的操作。因此,每个Actor也有一个物理路径,从Actor对象实际所在的Actor系统的Root Guardian开始。在查询其他Actor时使用此路径作为发送方引用将允许它们直接回复此Actor,从而最大限度地减少路由引起的延迟。

Actor路径别名或者符号链接?

在某些真实的文件系统中,你可能会想到一个Actor可能拥有“路径别名”或“符号链接”,即一个Actor可以使用多个路径访问。但是,你应该注意到Actor层次结构与文件系统层次结构不同。你不能自由地像创建符号链接那样创建Actor路径来指向任意的Actor。如上面Actor逻辑和物理路径部分所述,Actor路径必须是表示监督层次结构的逻辑路径,或表示Actor部署的物理路径。

怎么获取Actor引用

一共有两种方式获取Actor引用:通过创建或者通过查找,后面一种又可以具体分为两种形式:通过具体的Actor路径获取Actor引用和查询Actor的逻辑层次

创建Actors

Actor系统通常是通过使用ActorSystem.actorOf方法在Guardian Actor下创建Actor,然后在创建出的Actor中使用ActorContext.ac-torOf来扩展Actor树。这些方法返回新创建的Actor的Actor引用。 每个Actor都可以直接访问(通过其ActorContext)其父Actor,自身及其子Actor的引用。这些引用可以通过消息发送给其他Actor,使得它们能够直接回复。

通过具体地址查找Actor

此外,可以使用ActorSystem.actorSelection方法查找Actor引用。selection可以用于发出selection请求的Actor与返回的Actor进行通信,这种selection在每次发送消息时都会重新获取返回的Actor

要获取绑定到特定Actor生命周期的ActorRef,你需要向Actor发送消息(例如内置的Identify消息),返回的sender()即为所求。

绝对路径 vs 相对路径

除了ActorSystem.actorSelection之外,还存在ActorContext.actorSelection,在任何Actor中都可通过context.actorSelection使用。这会产生一个Actor查找,就像在ActorSystem上查找一样,但它不是从Actor层级结构的根路径开始查找,而是从当前Actor开始。由两个点(“..”)组成的路径元素可用于访问父Actor。 例如:向指定的兄弟发送消息:

context.actorSelection("../brother") ! msg

也可以使用绝对路径的方式在上下文中查找,即 

context.actorSelection("/user/serviceA") ! msg

 查询Actor逻辑层次结构

由于Actor系统结构类似文件系统,因此可以采用与Unix shell支持的相同方式匹配路径: 你可以用通配符(*«*»*和«?»)替换(部分)路径元素名称来匹配零个或多个真正的Actors。因为结果不是单一的Actor引用,所以它返回ActorSelection类型,并且不支持ActorRef的完整操作集。可以使用ActorSystem.actorSelection和ActorContext.actorSelection方法制定选择,并支持发送消息:

context.actorSelection("../*") ! msg

这将会把msg发送给包括当前Actor在内的所有兄弟姐妹。 对于使用actorSelection获得的引用,通过遍历监督层次结构以获得需要执行消息发送的Actor引用。由于消息在发送过程时,与selection匹配的Actor集合也可能会发生变化,因此无法检测到选择的动态变化。为了做到这一点,通过发送请求并收集所有响应,提取发送者的引用,然后监控所有发现的具体Actor来解决不确定性。在将来的版本中可以改进这种解析选择的方案。

总结:actorOf vs. actorSelection

Note

以上部分详细描述的内容可以概括和记忆如下:

  • actorOf只创建一个新的Actor,并将其创建为调用此方法的上下文的直接子节点(可以是任何Actor或Actor系统)
  • actorSelection仅在传递消息时查找现有的Actor,即在选择创建时不创建Actors或验证Actors的存在

Actor引用和路径相等

ActorRef的相等性与ActorRef对应于目标Actor实例的相等性相匹配。当两个Actor引用具有相同的路径并且指向同一个Actor的实例时,它们相等。指向已终止Actor的Actor引用与具有相同路径的另一个(重新创建的)Actor的Actor引用不相等。请注意,由失败引起的Actor重启仍然意味着它是相同的Actor实例,即对于ActorRef的使用者来说,重启是不可见的。

如果你需要跟踪集合中的Actor引用并且不关心确切的Actor实例,那么可以使用ActorPath作为键,因为在比较Actor路径时不考虑目标Actor的标识符。

重用Actor路径

当Actor被终止时,它的引用将指向dead letter邮箱,DeathWatch将进行最后的处理,并且通常不会再次恢复到活动状态(因为Actor生命周期不允许这样)。虽然有可能在以后创建一个具有相同路径的Actor——不过这并不是一个很好的实践:通过actorSelection获取的Actor引用在死亡之后又突然重新开始工作了,并且这个转变与任何其他的事件没有任何的顺序保证,因此新的Actor可能接收到发送给之前停掉的Actor的消息。

在非常特殊的情况下做这件事可能是正确的,但是必须确保只有这个Actor的监督者才有权做这种处理。因为只有它的监督者才能可靠的保证在新的Actor创建之前,旧的Actor名字从系统中注销了。

当测试项依赖于特殊路径的实例时,也可能需要复用Actor路径。在这种场景下,最好mock它的监督者,以便在合适的时机传递Terminated消息给测试流程,并确保后续复用的Actor路径等待路径名字被注销。

与远程部署的互操作

当一个Actor创建子Actor时,Actor系统的部署者知道(通过配置文件或编程方式配置)新的Actor对象是在同一个JVM中还是在另一个节点上。在第二种情况下,Actor的创建将通过网络连接触发,新的Actor在不同的JVM中、不同的Actor系统中被创建。远程系统将会把该Actor对象放置在专门为远程创建的Actor保留的特殊路径上,它的监督者是触发创建这个Actor的那个远程Actor。在这种场景下,context.parent(监督者的引用)和context.path.parent(在Actor路径中的父节点)代表不同的Actor对象。然而,通过查看其Actor名字时会发现它在远程节点上,而且仍然保持了逻辑结构。

地址部分的作用是什么?

通过网络发送Actor引用时,是由其路径表示。 因此路径中的地址字符串必须包含协议、host地址、端口号等所有必要的信息才能正确发送消息给底层的Actor。 当Actor系统接收到远程节点的Actor路径时,它会检查该路径的地址是否与Actor系统的地址匹配,如果匹配,它将被解析为Actor的本地引用。 否则,它会被当作远程Actor引用。

Actor路径的顶层作用域

在Actor路径层级结构的根部是Root Guardian,通过它可以找到所有的Actor。根路径的名字是一个斜杠“/”,下一层包含了以下Guardian:

  • “/user”是所有用户创建的顶层Actor的Guardian Actor。通过ActorSystem.actorOf创建的Actor在“/user”的下一层
  • “/system”是所有系统创建的顶层Actor的Guardian Actor。比如:日志监听器或Actor系统启动时通过配置创建的Actors
  • “/deadLetters”是dead letter Actor,所有发送到停掉的或者不存在的Actor的消息会被重新路由到该Actor(基于尽力交付:即使在同一个JVM中,消息也可能丢失)
  • “/temp”是所有系统创建的临时的Actor的Guardian。比如:ActorRef.ask中用到的PromiseActorRef
  • “/remote”是一个伪路径,所有监督者是远程Actor引用的Actor都存放在这个路径下

上面这套构造Actor的命名空间的需求源于一个非常简单集中的目的:系统层级中的每一项都是一个Actor,并且所有的Actor都以同样的方式工作。因此,你不仅仅能查找你自己创建的Actor,你还可以查找System Guardian并向它发送消息(虽然它会把你的消息丢掉)。这个原则意味着没有特例需要额外记忆,使得整个系统更加统一和一致。

内容简介 本书将尝试帮助入门级、中级以及高级读者理解基本的分布式计算概念,并且展示 如何使用 Akka 来构建具备高容错性、可以横向扩展的分布式网络应用程序。Akka 是一 个强大的工具集,提供了很多选项,可以对在本地机器上处理或网络远程机器上处理的 某项工作进行抽象封装,使之对开发者不可见。本书将介绍各种概念,帮助读者理解 网络上各系统进行交互的困难之处,并介绍如何使用 Akka 提供的解决方案来解决这些 问题。 作者简介 Jason Goodwin 是一个基本上通过自学成才的开发者。他颇具企业家精神,在学校 学习商学。不过他从 15 岁起就开始学习编程,并且一直对技术保持着浓厚的兴趣。这对 他的职业生涯产生了重要的影响,从商学转向了软件开发。现在他主要从事大规模分布 式系统的开发。在业余时间,他喜欢自己原创电子音乐。 他在 mDialog 公司第一次接触到 Akka 项目。mDialog 是一家使用 Scala/Akka 的公司, 为主流出版商提供视频广告插入软件。这家公司最终被 Google 收购。他同时还是一名很 有影响力的“技术控”,将 Akka 引入加拿大一家主要的电信公司,帮助该公司为客户提 供容错性更高、响应更及时的软件。除此之 外,他还为该公司中的一些团队教授 Akka、 函数式以及并发编程等知识。 目录 第 1 章 初识 ActorAkka 工具集以及 Actor 模型的介绍。 第 2Actor 与并发:响应式编程。Actor 与 Future 的使用。 第 3 章 传递消息:消息传递模式。 第 4 章 Actor 的生命周期—处理状态与错误:Actor 生命周期、监督机制、Stash/ Unstash、Become/Unbecome 以及有限自动机。 第 5 章 纵向扩展:并发编程、Router Group/Pool、Dispatcher、阻塞 I/O 的处理以 及 API。 第 6 章 横向扩展—集群化:集群、CAP 理论以及 Akka Cluster。 第 7 章 处理邮箱问题:加大邮箱负载、不同邮箱的选择、熔断机制。 第 8 章 测试与设计:行为说明、领域驱动设计以及 Akka Testkit。 第 9 章 尾声:其他 Akka 特性。下一步需要学习的知识。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值