Akka笔记之子Actor及路径
Actor是纯粹的分层结构。你所创建出来的Actor必定是某个Actor的子Actor。
我们来简单地分析下:
路径
首先,我们通过ActorSystem.actOf方法来创建一个ActorRef并打印出它的路径。
val actorSystem=ActorSystem("SupervisionActorSystem")
val actorRef=actorSystem.actorOf(Props[BasicLifecycleLoggingTeacherActor])
println (actorRef.path) // (prints) akka://SupervisionActorSystem/user/$a
正如你所看到的,Actor路径跟文件系统的文件路径非常相似。
- 这里的akka前缀是固定的,因为它们是akka Actor的地址——就像是file://或者http://前缀一样(尽管这跟协议无关)
- SupervisionActorSystem就是你创建的ActorSystem的名字。
- 下一节中我们会讲到这个user到底是什么。
- $a是系统给你的Actor自动生成的名字。你会喜欢操作系统随机给你的文件生成名字吗?你肯定不希望这样,因为后续你还需要用这些名字去访问这些Actor。那么,我们来给它取一个有意义的合适的名字:
val actorRef=actorSystem.actorOf(Props[BasicLifecycleLoggingTeacherActor], "teacherActor")
println (actorRef.path) // (prints) akka://SupervisionActorSystem/user/teacherActor
完事了。现在这个路径终于像那么回事儿了。
子Actor
正如从ActorSystem中可以创建出顶层Actor一样,我们也可以通过ActorContext来创建出子Actor。事实上,Actor强大的容错能力恰恰就在于Actor是分层的,并且父Actor可以管理子Actor的生命周期。
假设现在有一个TeacherSupervisor,并且要新建一个TeacherActor作为它的子Actor,那么你就应该调用ActorContext.actorOf方法而非ActorSystem.actorOf:
class TeacherSupervisor extends Actor with ActorLogging {
val teacherActor=context.actorOf(Props[TeacherActor], "teacherActor")
...
...
事实上不管是什么应用,你所创建的子Actor都要远多于顶层Actor——也就是说,调用actorContext.actorOf的时候要远多于actorSystem.actorOf。
注意,这个子Actor的路径是akka://SupervisionActorSystem/user/teacherSupervisor/teacherActor,这跟你在父目录中创建一个子目录很像。
什么时候应该创建子Actor?
当某个任务由一个或多个子任务所组成的时候通常就会创建子Actor。或者当某个任务由父Actor执行比较容易出错,而你希望将它进行隔离的时候,也可以使用子Actor(这样当子Actor崩溃的时候,你还能够恢复它)。如果不存在父子Actor关系,就不要创建子Actor。
同样的,你也无法阻止子Actor继续创建子Actor来委派自己的子任务。创建Actor是非常廉价的,但他所带来的意义却非常深远(后面介绍到监督的时候还会继续讲到这点)。
那么路径中的user到底是什么?
由于缺少一个参考物,我们拿ActorSystem来跟Unix文件系统作一下比较——Unix文件系统有一个根目录/以及/etc,/usr,/bin等若干目录。
ActorSystem跟它非常类似。它会创建一些顶层的Actor——最重要的也就是根Actor,它对应路径/,用户Actor对应路径/usr,系统Actor则对应路径/system。(同样还有/deadLetters,它对应的是DeadLetterActorRef)。在我们前一篇文章中已经介绍过它了。
从代码层面来说,ActorSystem由内部的三个Actor组成(通过ActorRefProvider来创建的)。它们是ActorSystem创建的所有Actor的根Actor。
- systemGuardian Actor——它是/system下的所有Actor的根
- guardian Actor——它是/usr下面的所有Actor的根
- rootGuardian Actor——它是systemGuardian和guardian共同的根。
/**
* Reference to the supervisor of guardian and systemGuardian; ....
*/
def rootGuardian: InternalActorRef
/**
* Reference to the supervisor used for all top-level user actors.
*/
def guardian: LocalActorRef
/**
* Reference to the supervisor used for all top-level system actors.
*/
def systemGuardian: LocalActorRef
/usr用户守卫
程序中通ActorSystem的actorOf方法所创建的诸如StudentActor或者TeacherActor等所有Actor都挂在/usr路径下。这就是为什么在本文第一节中你所创建的TeacherActor会在/user/teacherActor下。
/system系统守卫
当系统守卫发现用户守卫已经停止运行的时候它会把自己也终止掉。为什么会这样?因为既然用户守卫都终止了,这说明它下面的所有的业务相关的Actor就都结束运行了,因此所有管理类的Actor也应该停止了。
来看下创建系统Actor的两个不同的场景——系统Actor指的是/system路径下的Actor。
- 如前所述,发送给已终止Actor的任何消息都会转发到一个叫DeadLetterActor的内部Actor的邮箱里。DeadLetterActor会将每条消息封装成一个DeadLetter消息并发布到EventStream中去。另一个叫DeadLetterListener的Actor会去消费这些DeadLetter消息并将它们作为日志打印出来。这里的DeadLetterListener就是一个系统级的Actor,它的路径是 /system/deadLetterListener。
- 还记得前篇笔记中我们创建的那个订阅日志消息的TestEventListener吗?它们也是系统Actor。事实上,所有的akka.loggers都是作为系统Actor来创建的。
class TeacherTest extends TestKit(ActorSystem("UniversityMessageSystem", ConfigFactory.parseString("""akka.loggers = ["akka.testkit.TestEventListener"]""")))
...
...
这里的文档说道,配置文件中所配置,创建并部署到ActorSystem中的任何Actor都会落到/system下面。如果后面我发现有什么例外的情况我再更新下这里所说的。
/ 根守卫
如前所述,/ Actor是用户及系统守卫的父Actor。
小知识
从技术上来讲,根Actor也有一个父Actor。它只负责做一件事情,就是当根Actor崩溃的时候去关掉整个ActorSystem。严格来说,由于在Actor结构中并没有提到它,因此Akka团队把它叫作:
private[akka] val theOneWhoWalksTheBubblesOfSpaceTime: InternalActorRef = new MinimalActorRef {
...