监管与监控

Let it Crash


akka引用了erlang的容错理念,也就是let it crash。引入了Supervisor,抽象出了Supervisor Strategy监管策略,主要分为两种:One For One和All For One。

一对一策略 vs. 多对一策略

Akka中有两种类型的监管策略:OneForOneStrategyAllForOneStrategy。第一种策略就是说当有一个actor发生错误或者异常的时候,监控者会对出错的这个actor进行单独的处理。第二种策略是说当有一个actor发生错误或者异常时候,监控者会对下面监控的所有actor执行指定的处理。通常情况下,你应该使用OneForOneStrategy,这也是默认的策略。

AllForOneStrategy适用的情况是,子actor之间有很紧密的依赖,以至于一个actor的失败会影响其他孩子,即他们是不可分开的。由于重启不清除邮箱,所以往往最好是失败时终止孩子并在监管者显式地重建它们(通过观察孩子们的生命周期);否则你必须确保重启前入队的消息在重启后处理是没有问题的。

7.2

基于所监管的工作的性质和失败的性质,监管者可以有4种基本选择:

  1. 恢复下属,保持下属当前积累的内部状态
  2. 重启下属,清除下属的内部状态
  3. 永久地停止下属
  4. 升级失败(沿监管树向上传递失败),由此失败自己

7.1

顶级监管者

一个actor系统在其创建过程中至少要启动三个actor,如上图所示。有关actor路径及相关信息请参见Actor路径的顶级作用域

/user: 守护Actor

这个名为"/user"的守护者,作为所有用户创建actor的父actor,可能是需要打交道最多的。使用system.actorOf()创建的actor都是其子actor。这意味着,当该守护者终止时,系统中所有的普通actor都将被关闭。同时也意味着,该守护者的监管策略决定了普通顶级actor是如何被监督的。当这个守护者上升一个失败,根守护者的响应是终止该守护者,从而关闭整个actor系统。

/system: 系统守护者

这个特殊的守护者被引入,是为了实现正确的关闭顺序,即日志(logging)要保持可用直到所有普通actor终止,即使日志本身也是用actor实现的。其实现方法是:系统守护者观察user守护者,并在收到Terminated消息初始化其自己的关闭过程。顶级的系统actor被监管的策略是,对收到的除ActorInitializationExceptionActorKilledException之外的所有Exception无限地执行重启,这也将终止其所有子actor。所有其他Throwable被上升,然后将导致整个actor系统的关闭。

/: 根守护者

根守护者所谓“顶级”actor的祖父,它监督所有在Actor路径的顶级作用域中定义的特殊actor,使用发现任何Exception就终止子actor的SupervisorStrategy.stoppingStrategy策略。其他所有Throwable都会被上升……但是上升给谁?所有的真实actor都有一个监管者,但是根守护者没有父actor,因为它就是整个树结构的根。因此这里使用一个虚拟的ActorRef,在发现问题后立即停掉其子actor,并在根守护者完全终止之后(所有子actor递归停止),立即把actor系统的isTerminated置为true

重启的含义

当actor在处理某条消息时失败时,失败的原因可以分成以下三类:

  • 对收到的特定消息的系统错误(即程序错误)
  • 处理消息时一些外部资源的(临时性)失败
  • actor内部状态崩溃了

除非故障能被专门识别,否则所述的第三个原因不能被排除,从而引出内部状态需要被清除的结论。如果监管者确定它的其他子actor或本身不会受到崩溃的影响——例如使用了错误内核模式的能够自我恢复的应用——那么最好只重启这个孩子。具体实现是通过建立底层Actor类的新实例,并用新的ActorRef更换故障实例;能做到这一点是因为将actor都封装载了特殊的引用中。然后新actor恢复处理其邮箱,这意味着该启动在actor外部是不可见的,显著的异常是在发生失败期间的消息不会被重新处理。

重启过程中所发生事件的精确次序是:

  1. actor被挂起(意味着它不会处理正常消息直到被恢复),并递归挂起其所有子actor
  2. 调用旧实例的 preRestart hook (缺省实现是向所有子actor发送终止请求并调用 postStop)
  3. 等待所有子actor终止(使用context.stop())直到 preRestart 最终结束;这里所有的actor操作都是非阻塞的,最后被杀掉的子actor的终止通知会影响下一步的执行
  4. 再次调用原来提供的工厂生成actor的新实例
  5. 调用新实例的postRestart方法(其默认实现是调用preStart方法)
  6. 对步骤3中没有被杀死的所有子actor发送重启请求;重启的actor会遵循相同的过程,从步骤2开始
  7. 恢复这个actor

缺省的监管机制

如果定义的监管机制没有覆盖抛出的异常,将使用Escalate上溯机制。

如果某个actor没有定义监管机制,下列异常将被缺省地处理为:

  • ActorInitializationException将终止出错的子actor
  • ActorKilledException将终止出错的子actor
  • Exception将重启出错的子actor
  • 其它的Throwable将被上溯传给父actor

如果异常一直被上溯到根监管者,在那儿也会用上述缺省方式进行处理。

你可以将自己的策略与默认策略结合:

actor失败的日志记录

默认情况下SupervisorStrategy会日志记录失败,除非他们被上溯升级。升级的失败应该被树形结构中更高级的监管者处理,即可能在那里记录日志。

当实例化时,你可以通过将loggingEnabled设置为false来取消SupervisorStrategy的默认日志记录。自定义日志记录可以在Decider内完成。请注意当SupervisorStrategy是在监管actor内声明的时候,

参阅示例


SupervisorApplication.java

Monitoring监控


实现方式是通过monitoring actor接受到Terminated消息,默认的处理行为是抛出一个特殊的DeathPactException如果没有其它处理逻辑。如果要开始监听Terminated消息,需要调用context.watch(targetActorRef)。如果要停止监听Terminated消息,可以调用context.unwatch(targetActorRef)。

总结一下就是Supervisor用来监控actor在运行时候发生异常的情况,然后对异常做出处理,monitoring用来对actor进行DeathWatch,通过context.watch和unwatch,这样可以接收到子actor的Ternimated消息然后进行处理。工程环境中两者结合使用。

为了监控Actor的终止,actor必须将自己注册为Monitor Actor. 7.3

参阅示例


MonitorApplication.java