`
沉沦的快乐
  • 浏览: 55712 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

akka学习之actor监护与监控

 
阅读更多

       在前面的章节akka学习之actor介绍中介绍了actor对ziactor有监护职责,同时actor对其他actor的生命周期等信息进行监控。这张将详细解释什么是监护和监控。

什么是监护(supervision)

在actor系统,一个actor(上司也可以成为监护人)可以创建子actor(下属),然后可以分配任务给他的下属去执行,同时也要对下属的错误负责,上司也是下属的监护者。当下属发生了错误,比如抛出了一个异常,下属会终止自己以及它的下属,然后发送一个消息给他的上司(监护人),表示发生了错误。根据具体的任务内容以及错误的类型,监护人可能会采取下面某种措施:

1.保存下属当前的状态,并让下属重试一次。

2.清除下属的当前状态,重启下属

3.永久的终止下属

4.把错误交给他的监护者来处理,并让自己处于错误状态。

    要理解akka监护的思想,必须牢记一点:把每一个actor视为整个akka监护层级体系的一部分,因为一个actor可能既是监护者,又是被监护者。终止一个actor,也同时终止了这个actor的所有下属,同样重启一个一个actor也同时重启了这个actor的所有下属。actor类的重启钩子preRestart方法的默认行为是在重启前终止它的所有下属,不过你也可以override这个方法。

      每一个监护者都必须实现一个错误处理方法,这个方法使用上面4个措施中的一种来处理所有可能的错误,必须注意的是,不要把发生错误的actor对象作为这个方法的输入。但是你可能发现这种错误处理的框架可能不够灵活,比如你想要实现针对不同的下属使用不同的处理策略。由于akka的监管方式是可以递归式得升级,在某个actor上做太多种的监控策略可能会导致你很难排查问题。比如你在一个actor中捕获并处理了N中不同的exception类型,那么你排查问题的时候就可能不知道这个异常是它哪个层级的下属抛出的。因此最好在系统中加入一个专门的监护层。

       akka的监护方式是一种“父监护”形式,就像父母是儿女的监护人一样。一个actor的创建只能通过其他的actor来创建(最顶层的actor是akka库提供的),创建者就是父actor,被创建者是子actor,一个actor的监护人就是创建它的actor。这种自上而下的树形结构使得akka的监护层级结构更加清晰,同时也保证了一个actor不会成为孤儿,或者拥有父actor之外的监护人。另外,这也使得关闭应用程序的步骤是自下而上逐层关闭,步骤更加清晰。

        父子actor之间监护关系是是通过特殊的系统消息来连接的,并且这些特殊的消息有自己单独的系统邮箱。因此这些特殊的消息与普通的用户消息之间没有明确的顺序,用户也没有办法来干预特殊消息和用户消息之间的顺序。

 

顶层监护者

       一个actor系统创建之后,他至少包含了3个actor:根守护actor,系统守护actor,用户守护actor。如下图所示:


/user:用户守护actor 

       所有用户actor的根actor都是用户守护actor,是用户创建的一个层级的actor的父actor和监护者,它的名字就是“/user”,所有通过system.actorOf()方法创建的actor都是他的子actor。当用户守护actor停止了,所有的用户创建的actor也将会停止,同时它的监护策略也决定了用户创建的第一层actor的错误是如何被处理的。在akka2.1之后的版本中可以通过akka.actor.guardian-supervisor-strategy(它使用SupervisorStrategyConfigurator的完整类名)来配置它的监护策略。当用户守护actor向它的监护者根守护actor提交了一个错误,根守护将会停止用户守护actor,进而关闭整个actor系统。

 

/system:系统守护actor

      在应用中,我们常常需要使用日志来记录整个系统模块的关闭顺序和行为,因此日志模块会在所有的actor停止之前还在工作,即使日志模块本身也是一个actor。为了实现这个功能,需要一个系统守护者来保证日志模块在最后关闭,已经管理整个系统在接收到停止消息之后的关闭行为。系统守护actor的监护策略是,在接收到除 ActorInitializationException 和ActorKilledException的所有异常时会提交到根守护者然后重启整个系统,ActorInitializationException 和ActorKilledException会停止发生错误的子actor。

 

/:根守护actor

根守护actor是所有所谓的顶层actor的祖父,它监护着所有的特殊actor,它的监护策略SupervisorStrategy.stoppingStrategy,它不管收到什么异常,都关闭出问题的子actor。前面说过每一个actor都有自己的监护者,但是根守护actor是整个系统的起点,那么它的监护者是谁呢?其实根守护actor是一个虚拟的actor引用,它会停止第一个向它汇报错误的子actor,然后在整个actor系统都停止之后把actor系统的isTerminated状态设置为true。

 

重启(restarting)的含义

    导致actor处理某个消息时发生错误的情况,大致可以分为3类:

1.处理某个消息时发生系统错误,比如数据不合法,导致空指针异常,类型转换错误等等。

2.依赖的外部资源发生错误

3.actor内部状态崩了

   第3种错误很难区分具体的情况,除非能够明确知道哪种错误,否则需要直接reset内部状态。如果监护者能确定某个子actor的错误不会对监护者本身以及其他子actor造成影响,那么最好重启这个错误actor。重启actor实际上就是重新创建一个这个actor的实例,并且把个actor的actor引用指向新的实例。这也是前面actor介绍中讲述的为什么actor要封装在actor引用里面的原因。然后新的actor实例重新开始出来邮箱中的消息,不过导致就的actor发生错误的那条消息不会再重新处理。所以actor的重启对外界来说是透明的。actor重启的精确的流程如下:

1.停止这个actor,不再处理任何消息,并且递归地停止它所有的子actor。

2.调用旧这个actorpreRestart钩子,默认行为是发送终止请求给所有的子actor,并且调用postStop钩子执行停止之后的行为。

3.等待所有接收到终止消息的子actor完全终止,当然这个等待是非阻塞的,只有最后一个终止的子actor发送终止消息消息过来之后,才开始执行下一步。

4.通过原来提供的actor工厂创建一个新的actor实例。

5.在新的实例中调用postRestart钩子(默认行为是调用preStart钩子)。

6.发送重启请求给所有在第3步中被杀死的子actor,递归地从第2步开始重启所有子actor。

7.重新开始执行actor的消息处理行为。

 

生命周期监控

    上面讲的监护关系是一种相当于父母对子女的监护关系,即只有创建它的父actor才能监护它。而监控(monitor)则不局限于血缘关系,只要一个actor能拿到另一个actor的引用,就可以监控它。由于actor活着创建出来以及重启对于监护者之外的actor是不可见的,所以监控者能到监控到的状态变化只能是从生到死的变化。相比于监护者是对被监护者的错误做出反应,而监控者是对被监控者的终止做出处理。

    生命周期的监控是通过监控者接收被监控者发送的终止消息来实现的。默认的是实现是抛出一个DeathPactException异常。开始终止消息的监听是通过ActorContext.watch(targetActorRef)来实现,而停止终止消息的监听则是调用ActorContext.unwatch(targetActorRef)。需要注意的是,终止消息的传递与被监控者终止行为的发生是没有顺序的,所以及时事实上被监控actor已经终止,监控者还是需要继续接收终止消息。

    当监护者不能通过简单的重启来恢复子actor而不得不终止它,那么监控就显得尤为重要。比如actor初始化错误,这种情况下就必须监控这些子actor,以便重新创建或者定期重试actor。另外一个监控比较有用和常见的场景是actor可能遇到所依赖的外部资源的缺失。当然也可能是它自己的子actor的缺失,比如第三方通过system.stop(child)方法或者致命毒丸终止了它的子actor,父actor也会受到影响。

 一对一策略与全部对一策略

       在阿akka中,监护策略大致可以分为两类:一对一策略与全部对一策略。这两类策略的使用都是通过配置监护者策略与对应的异常类型匹配,以及子actor在停止前允许发生的错误次数来实现。一对一策略是只对发生错误的子actor进行处理,而全部对一策略则是对发生错误的actor的兄弟姐妹也进行处理。

       一般情况下使用一对一策略就可以了,同时akka的默认方式也是一对一。全部对一策略的适用场景是actor的组装互相依赖于它的兄弟姐妹,这种场景下一个actor的错误会导致它的兄弟姐妹也发生错误。因为重启不会情况邮箱中的消息,所以最好是终止并且重建发生错误的actor,否则你要确保重启之后邮箱中的消息不会导致actor继续发生错误,不然你的actor将陷入无休止的重启中。

       在全部对一策略中一般终止一个子actor并不会自动的终止其他子actor。但是可以简单地通过监控生命周期来实现:如果监护者没有处理终止消息,监护者将抛出一个DeathPactException 并且重启自己,它默认的preRestart 方法将停止所有的子actor。当然也可以明确定义处理方法来实现。

    必须注意到,如果在使用全部对一策略的监护者中创建一次性的actor,那么这个临时性的actor发生错误会影响那么永久性的actor。如果不想这种情况发生,那么应该创建一个中间监护者:这很容易通过为工作者声明一个size为1的路由实现。

      

  • 大小: 22.7 KB
分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics