本章描述了Actor如何被识别和定位在于一个分布式Actor系统。这与固有的主管层次一样是Actor系统的核心内容,Actor之间的通信是透明的而它们可能位于多个网络节点。
上图显示了Actor系统中最重要的实体之间的关系,请继续阅读相关细节。
什么是Actor引用
一个Actor的引用子型是ActorRef
,它的最重要的目的是支持发送消息到它所代表的Actor。每一个Actor将访问它的标准(本地)引用通过self
字段。这个引用也包括发送方引用在默认情况下所有消息发送给其他Actor。相反,Actor在消息处理期间能通过发送方法访问一个代表着发送者当前消息的引用。
根据Actor系统的配置衍生出几种不同类别的Actor引用:
- 纯本地的Actor引用不支持网络功能。这些引用不会作用于来自网络访问的远程JVM。
- 在远程启用时本地Actor在Actor系统中支持网络功能,它的Actor需在同样的JVM中。为了能重发到其它结点中,这些引用包括协议和远程地址信息。
- 有一种用于路由的本地Actor引用子类型(即Actor混合路由特点)。它的逻辑结构同上述的本地引用,但发送消息时用它们的子集代替直接分派。
- 远程Actor引用表示Actor可用远程通讯,即透明的传输消息并发到远程的JVM中。
- 有几种特殊类型的Actor引用,它们像本地Actor引用实现特定目的:
-
PromiseActorRef
表示Promise已完成来自于Actor的响应。akka.pattern.ask
建立这个引用。
-
-
DeadLetterActorRef
默认实现死信服务,这种服务指Akka 路由的消息目标不存在或已停止。 -
EmptyLocalActorRef
指Akka查找一个不存在的本地Actor路径。它等于DeadLetterActorRef
,但它保留了路径以便Akka通过网络发送且和其它存在的Actor引用比较,有一些在Actor结束前获得。 - 然而有一些看不到一次性的内部实现:
- 有一种Actor引用并不表示一个Actor仅仅是做为根监督的伪监测。我们称之谓吹泡泡时间。
- 在第一个日志服务启动前,事实上是Actor建立了一个仿真Actor引用接受日志事件并直接输出屏幕,它是
Logging.StandardOutLogger
。
Actor路径是什么
由于Actor严格按层次形式创建,就存在唯一的Actor名字的序列按监管链实现递归,从子到父直到根。 这个序列可以视为一个文件系统中封闭的文件夹,因此我们用“路径”引述它。尽管Actor系统与文件系统的层次有根本性的区别。
Actor路径由一个协议字,然后是路径元素,从根到设计的Actor。路径的名称是由遍历的Actor和斜杠组成。
Actor引用和路径的区别是什么?
Actor引用是特定的单个Actor,引用的生命周期与Actor相同。Actor路径表示一个名字,它可能对应也可能不对应Actor,且它没有它的生命周期,一直存在。在建立Actor路径时可以没有Actor,但建立Actor引用时一定有对应的Actor。
创建一个Actor再终止它,再用同样的路径创建新的Actor。新建的Actor是新的化身,与原来的并是同一个。原来的引用对新的无效。消息发送给老的引用新的并不能收到哪怕是用了相同的路径。
Actor路径协议字
每一个Actor路径有一个地址组件,描述协议和相应可达的Actor位置。在层次结构中从root起跟随的Actor名字。比如
1."akka://my-sys/user/service-a/worker1" //本地
2."akka.tcp://my-sys@host.example.com:5678/user/service-b" // 远程
这里akka.tcp
是2.4版默认的传输方式,其它传输方式可用插件化实现。主机和端口部分的说明(即host.example.com:5678
)取决于所使用的传输机制,但它必须遵守URI结构规则。
逻辑Actor路径
从父级主管到根的唯一路径称为逻辑路径。它是由上代Actor创建并精确匹配,所以一旦远程设置里配置(含地址和路径)它能确定Actor。
物理Actor路径
基于远程布署意味着Actor可以在不同的网络主机和不同的父级被创建,例如在不同的Actor系统。而逻辑路径只在一个系统中描述了功能位置。在这种情况下,按路径从根守护遍历网络是巨大的开销。因此每个Actor有一个物理路径,从根守护开始驻留实际的对象。查询其他Actor时作为发送者引用使用这个路径将使它们直接向这个Actor返回,使路由延时最小化。
要注意的是物理路径不跨越多个Actor系统或JVM。这就使Actor(层次主管)的逻辑路径和(Actor发布)的物理路径在上级是无远程主管的可能有分岔。
Actor 路径是别名或符号吗
在一些真实的文件系统中可能认为Actor的路径别名或符号连接,比如Actor能用一个以上的路径找到。然而Actor层次不同于文件系统层次。并不能自由创建象符号链接那样关联到任意Actor。如上节逻辑路径和物理路径描述的那样,Actor路径要么是体现监管层次的逻辑路径,要么是代表Actor发布的物理路径。
Actor引用如果获得
获得Actor引用有两类方法:建立Actor和查找Actor,后者更有利于从具体的路径建立引用并查询到逻辑层次。
创建Actor
Actor系统通常开始于在守卫Actor下用ActorSystem.actorOf
方法创建Actor随后用ActorContext.actorOf
方法创建Actor衍生Actor树。这些方法返回新建Actor的引用。每个Actor将通过引用直接访问它的父级,自身与子级。这些引用将发送消息到其它Actor,并使他们直接回复。
通过具体路径查询Actor
另外,Actor引用可以用ActorSystem.actorSelection
方法查询到。在接收到的每条消息中,选择到的内容能对通信间的Actor匹配相对应的内容。
发送一个消息到绑定到特定生命周期Actor上用ActorRef
实现,就象内置的“标识”消息Actor用sender()
回复引用。
绝对路径与相对路径
除了ActorSystem.actorSelection
和ActorContext.actorSelection
,还有用于所有Actor的context.actorSelection
。这就像它的兄弟ActorSystem
,但它是从Actor树的根开始到当前Actor止的查询。路径元素由两个点组成("..")用于访问父级Actor。可以像例子那样发送到相邻Actor中:
1. context.actorSelection("../brother") ! msg
绝对路径也有常用的查询方法,例如
1. context.actorSelection("/user/serviceA") ! msg
查询Actor逻辑层次
由于Actor系统的形式像文件系统层次,所以路径的形式就像Unix Shell的方式:可以用通配符《*》和《?》代替(部分)元素名字,结果将得到0个或多个Actor。因为结果不是单个Actor引用,与ActorSelection
类型不同,且不支持ActorRef
的操作集。用ActorSystem.actorSelection
和ActorContext.actorSelection
方法支持发送的内容:
1. context.actorSelection("../*") ! msg
将发送消息到所有邻近结点包括自已。用actorSelection
得到引用为完成消息发送将遍历主管层次。当消息正在到达接收者时选择到匹配的Actor集可能改变事件,因此无法观察到消息的实时改变。为了提取发送者引用,通过发送请求并收集所有应答者解决不确定性,然后观察所有的发现的具体的Actor。这个解决方案可能在未来版本中改进。
摘要:actorOf
和actorSelection
注意
上面的部分中描述的一些细节可以概括和容易记住如下:
-
actorOf
仅仅是创建一个新Actor,且创建它直接子集调用的上下文(它可能是任意的Actor或Actor系统) -
actorSelection
仅仅是消息收到时查找已存在Actor,即不创建Actor,或在建立选择时校验存在的Actor。
Actor引用等式与路径等式
ActorRef
等式目的是使ActorRef
能对应到目标的化身。两个引用在有同样的路径和指向同样的Actor化身视为相等。一个引用指向已终止的Actor无法与另一个用同样路径(重建)的Actor比较。注意,因错误导致重启的Actor将有同样的Actor化身即重启对ActorRef
的消费者不可见。
如果需要在集合里保持跟踪Actor引用且不关心那些用ActorPath
做关键字的精确的Actor化身,因为目标Actor的标识符在路径比较时不会包含在内。
重用Actor路径
当Actor终止时,其引用将指向死信邮箱,DeathWatch会实现最后的传输且一般不会复活(因为Actor生命周期不允许这么做)。可能在稍后的时间内创建一个完全相同路径的Actor,原因是无法执行相反的仅所有Actor保持有效创建。这不是个好做法:消息以actorSelection
发送给Actor,而它已经‘死亡’又突然再次工作,在传输和其它事件期间没有任何可靠的顺序,因此新的路径占用者可能会收到以前占用者的消息。
在非常特殊的情况下是正确的,而确信能精确的控制的是Actor的主管。因为这是唯一的在创建新子级失败前可测到名字被取消。
也有可能在测试过程中需要,当测试对象依赖于特别路径。这种情况下最好模拟主管,以便于终止消息产生自适当的点,使后者在适当的时候取消名字。
远程布署的相互作用
当Actor创建一个子级时,Actor系统布署者会决定新的Actor会驻留在同样的JMV中或新的节点中,第二种情形是创建Actor会触发不同JVM、跨越Actor系统的连接。远程系统会放置新的Actor路径而保留它的目的。新Actor的主管会成为远程Actor引用(表示Actor会触发它的创建)。在这种情况下,context.parent
(主管引用)和context.path.parent
(Actor路径上的父结点)表示不同Actor。然而主管查找子级的名字时会发现它在远程结点,这样能保护逻辑结构向无法解析的引用发送时。
地址的用途是什么
当通过网络发送一个Actor引用时,它由路径表示。因此,路径必须对所有必要的信息有完整编码在向低层Actor发送消息时。这是由协议编码,主机和端口及地址部分的路径字符串。当Actor系统收到从远程节点Actor路径时,将检查路径地址是否是匹配本系统。此时会被解析为本地引用,否则会表示为远程引用。
顶级范围的Actor路径
在路径层次的根部,根守护在所有的Actor之上,它的名字是“/",下一级包括如下内容:
- "/user" 是为所有用户创建的高级Actor的守护,Actor被
ActorSystem.actorOf
创建且在这个节点下。 - "/system"是为所有系统创建的高级Actor的守护,例如日志监听或Actor启动时按配置自动布署。
- "/deadLetters" 是死信Actor,在所有消息发送时停止或不存在时重新路由的目标(尽力而为,消息甚至在本地JVM时丢失)
- "/temp"是为所有系统创建的短期高级Actor的守护,例为用于实现
ActorRef.ask
- "/remote" 是一个人造路径在所有Actor之下,它将驻留远程引用。
Actor的名称空间需要这样的结构从中央出现且设计目标是:层次中都是Actor,所有Actor有同样的方法。因此,不仅可以查找自已创建的,也可以查找系统守护并向它发送消息(这种情况下会被放弃)。这非常有用,它使整个系统更一致。
如果想读到更多Actor系统的高级结构,看《顶级主管》一节。