目录
本节描述如何在一个可能的分布式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并向它发送消息(虽然它会把你的消息丢掉)。这个原则意味着没有特例需要额外记忆,使得整个系统更加统一和一致。