2.4 监管和监测
原文:http://doc.akka.io/docs/akka/2.3.6/general/supervision.html 译者:Vitas
2.4 监管和监测
本节将从概念层面上介绍有关监管的一些基本元素及其语义。至于在代码中如何具体实现,请参考API的相关章节。
2.4.1 什么是监管
正如角色系统一节所描述,监管描述了角色之间的相互依赖关系:监管者委派任务给它的子角色,并负责对它们的失败做出响应。当子角色监测到一个错误(例如,抛出一个异常),它将挂起它自己以及它所有的子角色,并且发消息通知它的监管者任务失败。根据被监测工作的性质以及失败的性质,监测者可以做出如下四种响应:
1.重启子角色,并保持它失败前的内部状态
2.重启子角色,并清空它的内部状态
3.永久性的停止子角色
4.向它的监管者报告错误信息,并终止自己
应该始终将角色看做是监管体系的一部分,这一点很重要。它也解释了为什么监管者可以做出上述四种响应(监管者其实也是它自身监管者的子角色),并且它还蕴含了前三种选择:恢复角色时,也恢复它所有的子角色;重启角色时,也重启它所有的子角色(下面将介绍更细节的一些内容);同理,终止一个角色也终止它所有的子角色。需要注意的是,角色类的preRestart钩子的默认行为是在重启之前先终止所有的子角色;在钩子执行完后再递归地重启所有的子角色。
每个监管者都配置了一个函数来将所有可能的错误(如异常)映射到上面提到的四种响应的一种;特别地,这个函数不需要知道失败角色的标识号。我们很容易想出很多的例子来证明这种处理方式并不是很灵活。例如,当我们希望对不同的子角色实施不同的策略时。此时,我们最需要理解的是,监管者将会形成一个递归的错误处理结构。如果你在同一层次中执行了太多的操作,那么这种递归的处理将很难延续。因此,在这种情况下我们推荐的处理方式是增加一级监管者。
Akka实现了一种特殊的形式叫“父层监控”。角色只能被其它角色创建——顶级的角色由程序库创建——并且被创建的角色将被它的父节点监控。这个限制隐式的形成了角色监控体系的形式,并且鼓励健全的设计决策。同时,这样也保证了角色不会成为孤儿或者依附于系统外的监控者,否则那样将会很难监控它们。另外,这种方式也使得(子)角色应用的关闭变的非常容易和彻底。
警告:父-子角色监管相关的通信是通过一种特殊的系统消息来实现的。并且每个角色都有一个与普通用户消息分开的信箱来存放该系统消息。这也就暗示着,监管事件和普通消息之间并没有一个确定的处理顺序。通常,用户是无法影响普通消息和失败消息的顺序的。如果想了解更多的细节和实例,请参考讨论:消息顺序一节。
2.4.2 顶级监管者
如上图所示,一个角色系统在它运行期间至少要启动三个角色。如果想了解更多的有关角色路径的信息,请参考角色路径的顶级范围一节。
/user: 守护角色
与这个角色打交道最多的就是用户创建角色的父角色。我们将其命名为“/user”。所有以system.actorOf()创建的角色都是它的子角色。这意味着当这个角色终止时,系统中所有的普通角色也都将被关闭。同时,这也意味着这个守护者的监管策略决定了最顶层的普通角色是怎样被监管的。从Akka版本2.1以后,可以通过配置akka.actor.guardian-supervisor-strategy来设置它。它的值必须是一个SupervisorStrategyConfigurator类的全路径。当该守护者向上汇报失败时,根守护者的响应就是终止该守护者,即停止整个角色系统。
/system:系统守护者
这个特殊的守护者是为了实现有序的关闭一系列的角色引入的。当所有的普通角色终止时,它会记录那些仍然活着的角色。日志的记录也是通过使用这类角色实现的。它的实现是通过让系统守护者监控用户守护者,并在收到Terminated消息时关闭自己。顶层的系统角色通过这样一种策略监控:当遇到Exceptoin时,它将无限次的重启角色。但ActorInitialzationException和ActorKilledException例外,遇到这两种异常时,它将终止有问题的子角色。对于那些被抛出去的异常将会被升级,从而导致整个角色系统被关闭。
/:根守护者
根守护者是那些所谓的“顶层”角色的祖父。它通过SupervisorStrategy.stoppingStrategy策略来监控所有在角色路径的顶级范围一节中提到的特殊角色。该策略的目的是当遇到任何异常时终止它的子角色。所有被抛出去的异常将会被升级…但是升级给谁呢?因为每个真正的角色都会有一个监控者,而根守护者的监控者不可能是一个真正的角色。这意味着“走到了气泡以外”,所以我们将其称作“气泡-行走者”。这是一个综合的角色引用,它可以在收到第一个错误信号的时候停止它的子角色,并且在根守护者终止时,将系统角色的isTerminated状态置为真(所有的子角色递归的停止)。
2.4.3 重启指的是什么
当一个角色在处理特定的消息失败时,通常引起失败的原因可以归为下列三类:
处理收到的特定消息的系统(如程序)错误
在消息处理过程中系统外部资源(短暂地)错误
角色内部状态崩溃
除非这个失败是具体可辩认的,否则我们并不能排除第三种错误。所以,我们得出一个结论:每次出错都要清空内部状态。如果监管者判断本次的崩溃不会影响到其它子角色或者它本身——如,因为故意的调用错误内核模式——那么最好重启这对应的子角色。这是通过创建角色类的一个新对象,然后在子角色的ActorRef中,由它来取代失败的对象来实现的;之所以可以这样实现,是因为在特定的引用中角色的封装特性。新的角色将开始继续处理它的信箱。这也就意味着,这个重启动作对外是不可见的,引起异常错误的消息将不会被重新处理
重启过程中各个事件的顺序如下所述:
1. 挂起角色(这也就意味着在重启之前它将不能出来正常的消息),并且递归的挂起所有子角色
2. 调用老实例的preRestart钩子(默认行为是向所有子角色发送终止请求,并且调用postStop) 3.等待在preRestart中请求终止的所有子角色真正终止;这个操作——像所有的角色操作一样——是非阻塞的,最后一个被杀死的子角色的终止消息将会影响接下来的操作。
4. 调用最初提供的工厂创建新的角色实例。
5. 在新实例上调用postRestart.(默认还会调用preStart)
6. 向第3步中未被杀掉的子角色发送重启请求;重启的子角色将递归的从第2步开始,按照相同的流程处理
7. 重启这个角色
2.4.4 生命周期监控是什么
提示: 在Akka中生命周期监控通常指的是DeathWatch
与前面介绍的父角色和子角色的关系相比,每个角色可能还监视着其它任意的角色。因为角色创建后,它活着的期间以及重启在它的监管者之外是看不到的,所以对监视者来说它能看到的状态变化就是从活着变到死亡。所以监视的目的是当一个角色终止时可以有另一个相关角色做出响应,而监管者的目的是对角色的失败做出响应。
监视角色通过接收Terminated消息来实现生命周期监控。如果没有其它的处理方式,默认的行为是抛出一个DeathPactException异常。为了能够监听Terminated消息,你需要调用ActorContext.watch(targetActorRef)。调用ActorContext.unwatch(targetActorRed)来取消对目标角色的监听。需要注意的是,Terminated消息的发送与监视角色注册的时间和被监视角色终止的时间顺序无关。例如,即使在你注册的时候目标角色已经死了,你仍然能够收到Terminated消息。 当监管者不能简单的重启子角色而必须终止它们时,监视将显得非常重要。例如,角色在初始化的时候报错。在这种情况下,它应该监视这些子角色并且重启它们或者稍后再做尝试。
另一个常见的应用案例是,一个角色或者它的子角色在无法获得需要的外部资源时需要失败。如果是第三方通过调用system.stop(child)方法或者发送PoisonPill消息来终止子角色是,监管者也将会受到影响。
2.4.5 一对一策略 VS. 多对一策略
Akka有两种类监管策略:一对一策略和多对一策略。它们都配置了从异常类型到监管命令的一个映射(参考上文),并且限制子角色在终止之前允许失败的次数。它们之间的不同是,前者只可以将指令实施在其失败的子角色上,而后者还可以将指令实施在它的兄弟姐妹之上。通常,你应该使用一对一策略,它也是默认的选项。
多对一策略(AllForOneStrategy)策略适用于子角色之间有相互依赖关系的场景,当一个子角色失败时,会影响到其它子角色的功能。例如,它们难见难分的链接在一起。因为重启并不会清空信箱,所以最好的选择通常是,终止失败的子角色,然后由监管者显式的重新创建它们(通过观察它们的生命周期);否则你必须能够确保每个重启的角色都能正确的处理重启之前入队的消息。
通常,在多对一策略中,停止一个子角色(例如,不响应失败)并不会自动的终止其他子角色;这个可以通过观察它们的生命周期很容易的实现:如果Terminated消息没有被监管者处理,它将会抛出一个DeathPactException(决定于它的监管者)来重启自己,默认的preStart行为将会终止它所有的子角色。当然这些也可以显式的实现。
需要注意的是,对于多对一监管者来说创建一次性的角色将会导致零时角色的失败被扩大化,从而影响永久性角色。如果这个不是你希望的,那么请创建一个中间监管者;这个可以通过创建一个大小为1的路由器来实现,具体参见3.6路由一节。
原创文章,转载请注明: 转载自并发编程网 – ifeve.com本文链接地址: 2.4 监管和监测
暂无评论